Repository: stateful/vscode-runme
Pull request #1642 (2024-09-10 21:40:29)

Initial Feature flag config
This approach utilizes the package.json file as a centralized location for adding or migrating features, including the respective conditions for rendering and simplifying overriding. 

This proposal aims to improve the separation of data/state in the Extension host and the Notebook Output UI. The Components can access the Features State through the Notebooks Communication API ([postKernelMessage and onDidReceiveKernelMessage](https://code.visualstudio.com/api/extension-guides/notebook#interactive-notebooks-communicating-with-the-controller))


Additionally, this PR enables the Stateful Authentication for Runme Extension, it will be prompted on-demand when a user hits the Save/Share button.

Closes: #1557 

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [64.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [65.8% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [65.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [65.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [65.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [66.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [66.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=stateful_vscode-runme&pullRequest=1642&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [68.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=stateful_vscode-runme&pullRequest=1642&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=stateful_vscode-runme&pullRequest=1642)



Patch:
diff --git a/.github/scripts/overwrites/stateful.sh b/.github/scripts/overwrites/stateful.sh
index dd7ead50..d3f04d40 100755
--- a/.github/scripts/overwrites/stateful.sh
+++ b/.github/scripts/overwrites/stateful.sh
@@ -7,8 +7,6 @@ npm pkg set description="DevOps Notebooks built on Runme, connected for collabor
 npm pkg set homepage="https://stateful.com"
 npm pkg set contributes.configuration[0].properties[runme.app.baseDomain].default="platform.stateful.com"
 npm pkg set contributes.configuration[0].properties[runme.app.platformAuth].default=true --json
-npm pkg set contributes.configuration[0].properties[runme.app.enableShare].default=true --json
-npm pkg set contributes.configuration[0].properties[runme.experiments.escalationButton].default=true --json
 npm pkg set contributes.configuration[0].properties[runme.server.lifecycleIdentity].default=1 --json
 npm pkg set contributes.configuration[0].properties[runme.app.notebookAutoSave].default="yes"
 npm pkg set contributes.configuration[0].properties[runme.app.panel.runme.cloud].default="\"\"" --json
diff --git a/__mocks__/vscode.ts b/__mocks__/vscode.ts
index 32cd0025..2ea1673b 100644
--- a/__mocks__/vscode.ts
+++ b/__mocks__/vscode.ts
@@ -322,3 +322,5 @@ export enum EndOfLine {
    */
   CRLF = 2
 }
+
+export const version = '9.9.9'
diff --git a/package-lock.json b/package-lock.json
index d7d460cd..5a7299f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "runme",
-  "version": "3.7.5",
+  "version": "3.7.6-edge.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "runme",
-      "version": "3.7.5",
+      "version": "3.7.6-edge.0",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
@@ -63,8 +63,9 @@
         "@graphql-codegen/typescript": "4.0.9",
         "@octokit/rest": "^19.0.13",
         "@types/node": "^18.19.14",
+        "@types/semver": "^7.5.8",
         "@types/uuid": "^10.0.0",
-        "@types/vscode": "1.72.0",
+        "@types/vscode": "1.78.0",
         "@types/vscode-notebook-renderer": "^1.72.3",
         "@types/webpack-env": "^1.18.1",
         "@typescript-eslint/eslint-plugin": "^7.18.0",
@@ -104,7 +105,7 @@
         "webpack-cli": "^5.1.4"
       },
       "engines": {
-        "vscode": "^1.72.0"
+        "vscode": "^1.78.0"
       }
     },
     "node_modules/@actions/exec": {
@@ -7179,6 +7180,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/semver": {
+      "version": "7.5.8",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+      "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
+      "dev": true
+    },
     "node_modules/@types/shimmer": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz",
@@ -7213,9 +7220,10 @@
       "dev": true
     },
     "node_modules/@types/vscode": {
-      "version": "1.72.0",
-      "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.72.0.tgz",
-      "integrity": "sha512-WvHluhUo+lQvE3I4wUagRpnkHuysB4qSyOQUyIAS9n9PYMJjepzTUD8Jyks0YeXoPD0UGctjqp2u84/b3v6Ydw=="
+      "version": "1.78.0",
+      "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.0.tgz",
+      "integrity": "sha512-LJZIJpPvKJ0HVQDqfOy6W4sNKUBBwyDu1Bs8chHBZOe9MNuKTJtidgZ2bqjhmmWpUb0TIIqv47BFUcVmAsgaVA==",
+      "dev": true
     },
     "node_modules/@types/vscode-notebook-renderer": {
       "version": "1.72.3",
@@ -23540,6 +23548,11 @@
         "tangle": "^4.0.0"
       }
     },
+    "node_modules/vscode-telemetry/node_modules/@types/vscode": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.72.0.tgz",
+      "integrity": "sha512-WvHluhUo+lQvE3I4wUagRpnkHuysB4qSyOQUyIAS9n9PYMJjepzTUD8Jyks0YeXoPD0UGctjqp2u84/b3v6Ydw=="
+    },
     "node_modules/vscode-uri": {
       "version": "3.0.8",
       "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
diff --git a/package.json b/package.json
index e96fd07f..185e88e2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "runme",
-  "version": "3.7.5",
+  "version": "3.7.6-edge.0",
   "publisher": "stateful",
   "displayName": "Runme Notebooks for DevOps",
   "description": "Notebooks for DevOps written in Markdown. Runnable, configurable commands/code and embedded terminals. Integrated with cloud consoles, containers and pipelines.",
@@ -20,7 +20,7 @@
   },
   "private": true,
   "engines": {
-    "vscode": "^1.72.0"
+    "vscode": "^1.78.0"
   },
   "galleryBanner": {
     "color": "#0D003D",
@@ -56,6 +56,55 @@
     "onStartupFinished"
   ],
   "main": "./out/extension.js",
+  "runme": {
+    "features": {
+      "ForceLogin": {
+        "enabled": true,
+        "conditions": {
+          "os": "All",
+          "vsCodeVersion": ">=1.58.0",
+          "enabledForExtensions": {
+            "stateful.platform": true,
+            "stateful.runme": false
+          }
+        }
+      },
+      "Escalate": {
+        "enabled": true,
+        "conditions": {
+          "os": "All",
+          "vsCodeVersion": ">=1.58.0",
+          "statefulAuthRequired": true,
+          "enabledForExtensions": {
+            "stateful.platform": true,
+            "stateful.runme": false
+          }
+        }
+      },
+      "Share": {
+        "enabled": true,
+        "conditions": {
+          "os": "All",
+          "vsCodeVersion": ">=1.58.0",
+          "enabledForExtensions": {
+            "stateful.platform": true,
+            "stateful.runme": true
+          }
+        }
+      },
+      "Gist": {
+        "enabled": true,
+        "conditions": {
+          "os": "All",
+          "vsCodeVersion": ">=1.58.0",
+          "enabledForExtensions": {
+            "stateful.platform": false,
+            "stateful.runme": true
+          }
+        }
+      }
+    }
+  },
   "contributes": {
     "commands": [
       {
@@ -563,12 +612,6 @@
             "title": "AI Auto Cell",
             "markdownDescription": "If set to `true`, enable AI auto generation of cells. Reload the window after changing this setting."
           },
-          "runme.experiments.escalationButton": {
-            "type": "boolean",
-            "scope": "window",
-            "default": false,
-            "markdownDescription": "If set to `true`, the secondary action button after saving a call will display Save or Escalate depending on the exit code."
-          },
           "runme.experiments.smartEnvStore": {
             "type": "boolean",
             "scope": "window",
@@ -810,18 +853,13 @@
           },
           "runme.app.baseDomain": {
             "type": "string",
-            "default": "",
+            "default": "platform.stateful.com",
             "scope": "window",
             "markdownDescription": "Base domain to be use for Runme app"
           },
-          "runme.app.enableShare": {
-            "type": "boolean",
-            "default": false,
-            "markdownDescription": "Enable collaboration in notebook terminal"
-          },
           "runme.app.panel.runme.cloud": {
             "type": "string",
-            "default": "runme.placeholder",
+            "default": "runme.optin",
             "scope": "window",
             "markdownDescription": "Customize cloud panel assignment"
           },
@@ -861,7 +899,7 @@
           "runme.app.platformAuth": {
             "type": "boolean",
             "scope": "window",
-            "default": false,
+            "default": true,
             "markdownDescription": "If set to `true` enables Stateful Authentication Provider"
           },
           "runme.aiBaseURL": {
@@ -972,7 +1010,7 @@
         {
           "id": "runme.cloud",
           "type": "webview",
-          "name": "Cloud",
+          "name": "Commands",
           "visibility": "visible"
         },
         {
@@ -1125,8 +1163,9 @@
     "@graphql-codegen/typescript": "4.0.9",
     "@octokit/rest": "^19.0.13",
     "@types/node": "^18.19.14",
+    "@types/semver": "^7.5.8",
     "@types/uuid": "^10.0.0",
-    "@types/vscode": "1.72.0",
+    "@types/vscode": "1.78.0",
     "@types/vscode-notebook-renderer": "^1.72.3",
     "@types/webpack-env": "^1.18.1",
     "@typescript-eslint/eslint-plugin": "^7.18.0",
diff --git a/src/client/components/terminal/share.ts b/src/client/components/terminal/actionButton.ts
similarity index 52%
rename from src/client/components/terminal/share.ts
rename to src/client/components/terminal/actionButton.ts
index 96cdfa3a..5a46f586 100644
--- a/src/client/components/terminal/share.ts
+++ b/src/client/components/terminal/actionButton.ts
@@ -5,16 +5,25 @@ import { when } from 'lit/directives/when.js'
 import { ShareIcon } from '../icons/share'
 import { SaveIcon } from '../icons/save'
 
-@customElement('share-cell')
-export class ShareCell extends LitElement {
+@customElement('action-button')
+export class ActionButton extends LitElement {
   @property({ type: String })
-  shareText: string = 'Copy'
+  text: string = 'Copy'
+
+  @property({ type: String })
+  loadingText: string = 'Saving...'
+
+  @property({ type: Boolean, reflect: true })
+  loading: boolean = false
 
   @property({ type: Boolean, reflect: true })
   disabled: boolean = false
 
   @property({ type: Boolean, reflect: true })
-  displayShareIcon: boolean = false
+  shareIcon: boolean = false
+
+  @property({ type: Boolean, reflect: true })
+  saveIcon: boolean = false
 
   /* eslint-disable */
   static styles = css`
@@ -39,33 +48,32 @@ export class ShareCell extends LitElement {
     }
   `
 
-  private onShareClick(e: Event) {
+  private onClick(e: Event) {
     if (e.defaultPrevented) {
       e.preventDefault()
     }
-    const event = new CustomEvent('onShare')
+
+    const event = new CustomEvent(this.disabled ? 'onClickDisabled' : 'onClick')
+
     this.dispatchEvent(event)
   }
 
   render() {
-    const className = this.shareText.toLocaleLowerCase()
-    return when(
-      this.disabled,
-      () => html`
-        <vscode-button
-          class=${className}
-          disabled
-          appearance="secondary"
-          @click=${this.onShareClick}
-        >
-          ${ShareIcon} ${this.shareText}
-        </vscode-button>
-      `,
-      () => html`
-        <vscode-button class=${className} appearance="secondary" @click=${this.onShareClick}>
-          ${this.displayShareIcon ? ShareIcon : SaveIcon} ${this.shareText}
-        </vscode-button>
-      `,
-    )
+    const className = this.text.toLocaleLowerCase()
+    return html`
+      <vscode-button
+        ?disabled=${this.loading}
+        class=${className}
+        appearance="secondary"
+        @click=${this.onClick}
+      >
+        ${when(this.shareIcon, () => ShareIcon)} ${when(this.saveIcon, () => SaveIcon)}
+        ${when(
+          this.loading,
+          () => html`${this.loadingText}`,
+          () => html`${this.text}`,
+        )}
+      </vscode-button>
+    `
   }
 }
diff --git a/src/client/components/terminal/index.ts b/src/client/components/terminal/index.ts
index 787960f4..c3a8c908 100644
--- a/src/client/components/terminal/index.ts
+++ b/src/client/components/terminal/index.ts
@@ -1,5 +1,5 @@
 import { LitElement, css, html, PropertyValues, unsafeCSS } from 'lit'
-import { customElement, property } from 'lit/decorators.js'
+import { customElement, property, state } from 'lit/decorators.js'
 import { when } from 'lit/directives/when.js'
 import { Disposable, TerminalDimensions } from 'vscode'
 import { ITheme, Terminal as XTermJS } from '@xterm/xterm'
@@ -16,7 +16,7 @@ import { APIMethod } from '../../../types'
 import type { TerminalConfiguration } from '../../../utils/configuration'
 import '../closeCellButton'
 import '../copyButton'
-import './share'
+import './actionButton'
 import './gistCell'
 import './open'
 import {
@@ -24,6 +24,12 @@ import {
   CreateExtensionCellOutputMutation,
   UpdateCellOutputMutation,
 } from '../../../extension/__generated-platform__/graphql'
+import {
+  isFeatureActive,
+  loadFeatureSnapshot,
+  FeatureObserver,
+  FeatureName,
+} from '../../../features'
 
 interface IWindowSize {
   width: number
@@ -48,6 +54,8 @@ const LISTEN_TO_EVENTS = [
   ClientMessages.optionsMessage,
   ClientMessages.onCopyTextToClipboard,
   ClientMessages.onProgramClose,
+  ClientMessages.featuresResponse,
+  ClientMessages.featuresUpdateAction,
 ]
 
 const ANSI_COLORS = [
@@ -73,10 +81,6 @@ const ANSI_COLORS = [
 @customElement(RENDERERS.TerminalView)
 export class TerminalView extends LitElement {
   protected copyText = 'Copy'
-  protected shareText = 'Save'
-  protected saveText = 'Save'
-  protected shareEnabledText = 'Share'
-  protected escalateEnabledText = 'Escalate'
 
   static styles = css`
     .xterm {
@@ -319,9 +323,16 @@ export class TerminalView extends LitElement {
   protected fitAddon?: FitAddon
   protected serializer?: SerializeAddon
   protected windowSize: IWindowSize
-
   protected rows: number = 10
 
+  protected platformId?: string
+  protected exitCode?: number | void
+  protected isSlackReady?: boolean
+  protected isShareReady: boolean = false
+
+  @state()
+  protected featureState$?: FeatureObserver
+
   @property({ type: String })
   id!: string
 
@@ -356,20 +367,11 @@ export class TerminalView extends LitElement {
   lastLine?: number // TODO: Get the last line of the terminal and store it.
 
   @property({ type: Boolean })
-  isCloudApiLoading: boolean = false
-
-  @property()
-  cloudId?: string
+  isLoading: boolean = false
 
   @property()
   shareUrl?: string
 
-  @property({ type: Boolean })
-  enableShareButton: boolean = false
-
-  @property({ type: Boolean })
-  isShareReady: boolean = false
-
   @property({ type: Boolean })
   isUpdatedReady: boolean = false
 
@@ -449,6 +451,10 @@ export class TerminalView extends LitElement {
         }
 
         switch (e.type) {
+          case ClientMessages.featuresResponse:
+          case ClientMessages.featuresUpdateAction:
+            this.featureState$ = loadFeatureSnapshot(e.output.snapshot)
+            break
           case ClientMessages.activeThemeChanged:
             this.#updateTerminalTheme()
             break
@@ -471,7 +477,7 @@ export class TerminalView extends LitElement {
               if (e.output.id !== this.id) {
                 return
               }
-              this.isCloudApiLoading = false
+              this.isLoading = false
               if (e.output.hasErrors) {
                 return postClientMessage(ctx, ClientMessages.errorMessage, e.output.data)
               }
@@ -480,21 +486,20 @@ export class TerminalView extends LitElement {
                 e.output.data.hasOwnProperty('displayShare') &&
                 e.output.data.displayShare === false
               ) {
-                this.shareText = this.saveText
                 return
               }
 
               const data = (e.output.data?.data || {}) as CreateExtensionCellOutputMutation &
                 CreateCellExecutionMutation &
                 UpdateCellOutputMutation
-              const { escalationButton: escalationButtonEnabled } = e.output
               // TODO: Remove createCellExecution once the transition is complete and tested enough.
               if (data.createExtensionCellOutput || data.createCellExecution) {
                 const objData = data.createCellExecution || data.createExtensionCellOutput || {}
-                const { exitCode, id, htmlUrl } = objData
-                this.cloudId = id
+                const { exitCode, id, htmlUrl, isSlackReady } = objData
+                this.platformId = id
                 this.shareUrl = htmlUrl || ''
-                this.shareText = this.getSecondaryButtonLabel(exitCode, escalationButtonEnabled)
+                this.exitCode = exitCode
+                this.isSlackReady = !!isSlackReady
                 this.isShareReady = true
                 // Dispatch tangle update event
                 return postClientMessage(ctx, ClientMessages.tangleEvent, {
@@ -506,11 +511,12 @@ export class TerminalView extends LitElement {
               }
               if (data.updateCellOutput) {
                 const {
-                  updateCellOutput: { exitCode },
+                  updateCellOutput: { exitCode, isSlackReady },
                 } = data
                 this.isUpdatedReady = true
-                this.shareText = this.getSecondaryButtonLabel(exitCode, escalationButtonEnabled)
-                this.#displayShareDialog(this.shareText)
+                this.exitCode = exitCode
+                this.isSlackReady = !!isSlackReady
+                this.#displayShareDialog()
               }
             }
             break
@@ -521,7 +527,7 @@ export class TerminalView extends LitElement {
                 return
               }
               const answer = e.output.option
-              this.isCloudApiLoading = false
+              this.isLoading = false
               switch (answer) {
                 case MessageOptions.OpenLink: {
                   return postClientMessage(ctx, ClientMessages.openExternalLink, {
@@ -545,12 +551,16 @@ export class TerminalView extends LitElement {
             return postClientMessage(ctx, ClientMessages.infoMessage, 'Link copied!')
           }
           case ClientMessages.onProgramClose: {
-            const { 'runme.dev/id': id, code, escalationButton: escalationButtonEnabled } = e.output
-            if (id !== this.id || !this.isAutoSaveEnabled) {
+            const { 'runme.dev/id': id, code } = e.output
+            if (id !== this.id) {
+              return
+            }
+            this.exitCode = code
+
+            if (!this.isAutoSaveEnabled) {
               return
             }
-            const btnSecondaryText = this.getSecondaryButtonLabel(code, escalationButtonEnabled)
-            this.shareText = this.isAutoSaveEnabled ? btnSecondaryText : this.saveText
+
             return this.#shareCellOutput(false)
           }
         }
@@ -562,13 +572,8 @@ export class TerminalView extends LitElement {
         }),
       ),
     )
-  }
 
-  getSecondaryButtonLabel(code: number | null | void, escalationButtonEnabled = false): string {
-    if (!escalationButtonEnabled) {
-      return this.shareEnabledText
-    }
-    return code === 0 ? this.shareEnabledText : this.escalateEnabledText
+    postClientMessage(ctx, ClientMessages.featuresRequest, {})
   }
 
   disconnectedCallback(): void {
@@ -744,13 +749,12 @@ export class TerminalView extends LitElement {
     })
   }
 
-  async #displayShareDialog(btnText?: string): Promise<boolean | void> {
+  async #displayShareDialog(): Promise<boolean | void> {
     const ctx = getContext()
     if (!ctx.postMessage || !this.shareUrl) {
       return
     }
-    this.shareText = btnText ?? this.shareEnabledText
-    this.isShareReady = true
+
     return postClientMessage(ctx, ClientMessages.optionsMessage, {
       title:
         'Please share link with caution. Anyone with the link has access. Click "Open" to toggle visibility.',
@@ -788,10 +792,10 @@ export class TerminalView extends LitElement {
         return this.#displayShareDialog()
       }
       if (this.isShareReady) {
-        this.isCloudApiLoading = true
+        this.isLoading = true
         await postClientMessage(ctx, ClientMessages.platformApiRequest, {
           data: {
-            id: this.cloudId,
+            id: this.platformId,
           },
           id: this.id!,
           method: APIMethod.UpdateCellExecution,
@@ -799,7 +803,7 @@ export class TerminalView extends LitElement {
         return
       }
 
-      this.isCloudApiLoading = true
+      this.isLoading = true
       const contentWithAnsi =
         this.serializer?.serialize({ excludeModes: true, excludeAltBuffer: true }) ?? ''
       await postClientMessage(ctx, ClientMessages.platformApiRequest, {
@@ -811,7 +815,7 @@ export class TerminalView extends LitElement {
         method: APIMethod.CreateCellExecution,
       })
     } catch (error) {
-      this.isCloudApiLoading = false
+      this.isLoading = false
       postClientMessage(
         ctx,
         ClientMessages.infoMessage,
@@ -828,6 +832,11 @@ export class TerminalView extends LitElement {
     postClientMessage(getContext(), ClientMessages.openLink, this.shareUrl!)
   }
 
+  #onEscalateDisabled(): void {
+    const message = 'There is no Slack integration configured yet. \nOpen Dashboard to configure it'
+    postClientMessage(getContext(), ClientMessages.errorMessage, message)
+  }
+
   // Render the UI as a function of component state
   render() {
     return html`<section>
@@ -848,31 +857,51 @@ export class TerminalView extends LitElement {
           }}"
         ></copy-button>
         ${when(
-          this.isSessionOutputsEnabled && !this.isPlatformAuthEnabled,
+          this.isSessionOutputsEnabled && isFeatureActive(FeatureName.Gist, this.featureState$),
           () => {
             return html`<gist-cell @onGist="${this.#openSessionOutput}"></gist-cell>`
           },
           () => {},
         )}
         ${when(
-          this.enableShareButton,
+          (this.exitCode === undefined || this.exitCode === 0 || !this.platformId) &&
+            isFeatureActive(FeatureName.Share, this.featureState$),
+          () => {
+            return html` <action-button
+              ?loading=${this.isLoading}
+              ?shareIcon="${!!this.platformId}"
+              ?saveIcon="${!this.platformId}"
+              text="${this.platformId ? 'Share' : 'Save'}"
+              @onClick="${this.#triggerShareCellOutput}"
+            >
+            </action-button>`
+          },
+          () => {},
+        )}
+        ${when(
+          this.exitCode !== 0 &&
+            this.platformId &&
+            isFeatureActive(FeatureName.Escalate, this.featureState$),
           () => {
-            return html` <share-cell
-              ?disabled=${this.isCloudApiLoading}
-              ?displayShareIcon=${this.isShareReady}
-              shareText="${this.isCloudApiLoading ? 'Saving ...' : this.shareText}"
-              @onShare="${this.#triggerShareCellOutput}"
+            return html` <action-button
+              ?disabled=${!this.isSlackReady}
+              ?loading=${this.isLoading}
+              ?saveIcon="${true}"
+              text="Escalate"
+              @onClick="${this.#triggerShareCellOutput}"
+              @onClickDisabled=${this.#onEscalateDisabled}
             >
-            </share-cell>`
+            </action-button>`
           },
           () => {},
         )}
         ${when(
-          this.cloudId && !this.isCloudApiLoading,
+          this.platformId && !this.isLoading,
           () => {
             return html` <open-cell
-            ?disabled=${!this.isPlatformAuthEnabled}
-            @onOpen="${this.#triggerOpenCellOutput}"></share-cell>`
+              ?disabled=${!this.isPlatformAuthEnabled}
+              @onOpen="${this.#triggerOpenCellOutput}"
+            ></open-cell>`
           },
           () => {},
         )}
diff --git a/src/client/index.ts b/src/client/index.ts
index 9bc0b582..5a533622 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -121,12 +121,6 @@ export const activate: ActivationFunction<void> = (context) => {
           if (typeof payload.output.scrollback === 'number') {
             terminalElement.setAttribute('scrollback', payload.output.scrollback.toString())
           }
-          if (payload.output.enableShareButton) {
-            terminalElement.setAttribute(
-              'enableShareButton',
-              payload.output.enableShareButton.toString(),
-            )
-          }
           if (payload.output.initialRows !== undefined) {
             terminalElement.setAttribute('initialRows', payload.output.initialRows.toString())
           }
@@ -135,7 +129,7 @@ export const activate: ActivationFunction<void> = (context) => {
             terminalElement.setAttribute('initialContent', payload.output.content)
           }
 
-          if (payload.output.isAutoSaveEnabled && payload.output.enableShareButton) {
+          if (payload.output.isAutoSaveEnabled) {
             terminalElement.setAttribute(
               'isAutoSaveEnabled',
               payload.output.isAutoSaveEnabled.toString(),
diff --git a/src/constants.ts b/src/constants.ts
index e12a41b2..19c94f27 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -68,6 +68,9 @@ export enum ClientMessages {
   gcpServicesLoaded = 'gcp:servicesLoaded',
   daggerSyncState = 'dagger:syncState',
   daggerCliAction = 'dagger:cliAction',
+  featuresUpdateAction = 'features:updateAction',
+  featuresRequest = 'features:request',
+  featuresResponse = 'features:response',
 }
 
 // [pretty print, languageId, destination]
@@ -1110,7 +1113,7 @@ export const CATEGORY_SEPARATOR = ','
 export const EXECUTION_CELL_STORAGE_KEY = 'executionCell'
 export const EXECUTION_CELL_CREATION_DATE_STORAGE_KEY = 'executionCellCreationDate'
 export const SAVE_CELL_LOGIN_CONSENT_STORAGE_KEY = 'loginConsent'
-export const CLOUD_USER_SIGNED_IN = 'userSignedIn'
+export const GITHUB_USER_SIGNED_IN = 'userSignedIn'
 export const PLATFORM_USER_SIGNED_IN = 'platformUserSignedIn'
 
 export const RUNME_FRONTMATTER_PARSED = 'runme.dev/frontmatterParsed'
diff --git a/src/extension/cell.ts b/src/extension/cell.ts
index 44bcd78f..821a9b5a 100644
--- a/src/extension/cell.ts
+++ b/src/extension/cell.ts
@@ -16,7 +16,7 @@ import {
 } from 'vscode'
 
 import {
-  CLOUD_USER_SIGNED_IN,
+  GITHUB_USER_SIGNED_IN,
   NOTEBOOK_AUTOSAVE_ON,
   OutputType,
   PLATFORM_USER_SIGNED_IN,
@@ -36,7 +36,6 @@ import {
   getNotebookTerminalConfigurations,
   getSessionOutputs,
   isPlatformAuthEnabled,
-  isRunmeAppButtonsEnabled,
 } from '../utils/configuration'
 
 import { RUNME_TRANSIENT_REVISION } from './constants'
@@ -245,7 +244,7 @@ export class NotebookCellOutputManager {
 
             const isSignedIn = isPlatformAuthEnabled()
               ? ContextState.getKey(PLATFORM_USER_SIGNED_IN)
-              : ContextState.getKey(CLOUD_USER_SIGNED_IN)
+              : ContextState.getKey(GITHUB_USER_SIGNED_IN)
 
             const isSessionOutputsEnabled = getSessionOutputs()
 
@@ -255,7 +254,6 @@ export class NotebookCellOutputManager {
                 'runme.dev/id': cellId,
                 content: stdoutBase64,
                 initialRows: terminalRows || terminalConfigurations.rows,
-                enableShareButton: isRunmeAppButtonsEnabled(),
                 isAutoSaveEnabled: isSignedIn ? ContextState.getKey(NOTEBOOK_AUTOSAVE_ON) : false,
                 isPlatformAuthEnabled: isPlatformAuthEnabled(),
                 isSessionOutputsEnabled,
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index 63b2980d..63ea7b91 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -27,7 +27,6 @@ import {
   getCLIUseIntegratedRunme,
   getTLSEnabled,
   isNotebookTerminalEnabledForCell,
-  isRunmeAppButtonsEnabled,
 } from '../../utils/configuration'
 import { Kernel } from '../kernel'
 import {
@@ -247,7 +246,7 @@ export function runCLICommand(kernel: Kernel, extensionBaseUri: Uri, grpcRunner:
   }
 }
 
-function openDocumentAs(doc: { text?: TextDocument; notebook?: NotebookDocument }) {
+async function openDocumentAs(doc: { text?: TextDocument; notebook?: NotebookDocument }) {
   const openIn = getActionsOpenViewInEditor()
   switch (openIn) {
     case OpenViewInEditorAction.enum.toggle:
@@ -258,11 +257,11 @@ function openDocumentAs(doc: { text?: TextDocument; notebook?: NotebookDocument
     default:
       {
         if (doc.notebook) {
-          window.showNotebookDocument(doc.notebook, {
+          await window.showNotebookDocument(doc.notebook, {
             viewColumn: ViewColumn.Active,
           })
         } else if (doc.text) {
-          window.showTextDocument(doc.text, {
+          await window.showTextDocument(doc.text, {
             viewColumn: ViewColumn.Beside,
           })
         }
@@ -271,12 +270,14 @@ function openDocumentAs(doc: { text?: TextDocument; notebook?: NotebookDocument
   }
 }
 
-export function openAsRunmeNotebook(doc: NotebookDocument) {
-  openDocumentAs({ notebook: doc })
+export async function openAsRunmeNotebook(uri: Uri) {
+  const notebook = await workspace.openNotebookDocument(uri)
+  await openDocumentAs({ notebook })
 }
 
-export function openSplitViewAsMarkdownText(doc: TextDocument) {
-  openDocumentAs({ text: doc })
+export async function openSplitViewAsMarkdownText(uri: Uri) {
+  const text = await workspace.openTextDocument(uri)
+  await openDocumentAs({ text })
 }
 
 export async function askNewRunnerSession(kernel: Kernel) {
@@ -387,7 +388,7 @@ export async function addToRecommendedExtensions(context: ExtensionContext) {
 }
 
 export async function toggleAutosave(autoSaveIsOn: boolean) {
-  if (autoSaveIsOn && isRunmeAppButtonsEnabled()) {
+  if (autoSaveIsOn) {
     await promptUserSession()
   }
   return ContextState.addKey(NOTEBOOK_AUTOSAVE_ON, autoSaveIsOn)
diff --git a/src/extension/executors/runner/index.ts b/src/extension/executors/runner/index.ts
index bee0cddf..4f603ef2 100644
--- a/src/extension/executors/runner/index.ts
+++ b/src/extension/executors/runner/index.ts
@@ -193,7 +193,6 @@ export const executeRunner: IKernelRunner = async ({
     postClientMessage(messaging, ClientMessages.onProgramClose, {
       'runme.dev/id': cellId,
       code,
-      escalationButton: kernel.hasExperimentEnabled('escalationButton', false)!,
     })
     if (!background) {
       return
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 773b8a75..a779d12f 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -9,6 +9,7 @@ import {
   env,
   Uri,
   NotebookCell,
+  authentication,
 } from 'vscode'
 import { TelemetryReporter } from 'vscode-telemetry'
 import Channel from 'tangle/webviews'
@@ -19,9 +20,9 @@ import {
   getForceNewWindowConfig,
   getRunmeAppUrl,
   getSessionOutputs,
-  isPlatformAuthEnabled,
 } from '../utils/configuration'
-import { WebViews } from '../constants'
+import { AuthenticationProviders, WebViews } from '../constants'
+import { FeatureName } from '../features'
 
 import { Kernel } from './kernel'
 import KernelServer from './server/kernelServer'
@@ -36,6 +37,7 @@ import {
   bootFile,
   resetNotebookSettings,
   getPlatformAuthSession,
+  getGithubAuthSession,
   openFileAsRunmeNotebook,
 } from './utils'
 import { RunmeTaskProvider } from './provider/runmeTask'
@@ -74,7 +76,7 @@ import * as survey from './survey'
 import { RunmeCodeLensProvider } from './provider/codelens'
 import CloudPanel from './panels/cloud'
 import { createDemoFileRunnerForActiveNotebook, createDemoFileRunnerWatcher } from './handler/utils'
-import { CloudAuthProvider } from './provider/cloudAuth'
+import { GithubAuthProvider } from './provider/githubAuth'
 import { StatefulAuthProvider } from './provider/statefulAuth'
 import { IPanel } from './panels/base'
 import { NotebookPanel as EnvStorePanel } from './panels/notebook'
@@ -345,26 +347,6 @@ export class RunmeExtension {
       ),
     )
 
-    if (isPlatformAuthEnabled()) {
-      context.subscriptions.push(new StatefulAuthProvider(context, uriHandler))
-      getPlatformAuthSession(true).then((session) => {
-        if (session) {
-          const openDashboardStr = 'Open Dashboard'
-          window
-            .showInformationMessage('Logged into the Stateful Platform', openDashboardStr)
-            .then((answer) => {
-              if (answer === openDashboardStr) {
-                const dashboardUri = getRunmeAppUrl(['app'])
-                const uri = Uri.parse(dashboardUri)
-                env.openExternal(uri)
-              }
-            })
-        }
-      })
-    } else {
-      context.subscriptions.push(new CloudAuthProvider(context))
-    }
-
     await bootFile(context)
 
     if (kernel.hasExperimentEnabled('shellWarning', false)) {
@@ -378,7 +360,7 @@ export class RunmeExtension {
         }
       }
 
-      const logger = getLogger('runme.beta.shellWarning')
+      const logger = getLogger('runme.experiments.shellWarning')
 
       kernel
         .runProgram('echo $SHELL')
@@ -400,6 +382,60 @@ export class RunmeExtension {
           showUnsupportedShellMessage()
         })
     }
+
+    context.subscriptions.push(new GithubAuthProvider(context))
+    context.subscriptions.push(new StatefulAuthProvider(context, uriHandler))
+
+    getGithubAuthSession(false).then((session) => {
+      kernel.updateFeatureState('githubAuth', !!session)
+    })
+
+    const createIfNone = kernel.isFeatureActive(FeatureName.ForceLogin)
+    const silent = createIfNone ? undefined : true
+
+    getPlatformAuthSession(createIfNone, silent)
+      .then((session) => {
+        if (session) {
+          const openDashboardStr = 'Open Dashboard'
+          window
+            .showInformationMessage('Logged into the Stateful Platform', openDashboardStr)
+            .then((answer) => {
+              if (answer === openDashboardStr) {
+                const dashboardUri = getRunmeAppUrl(['app'])
+                const uri = Uri.parse(dashboardUri)
+                env.openExternal(uri)
+              }
+            })
+        }
+      })
+      .catch((error) => {
+        let message
+        if (error instanceof Error) {
+          message = error.message
+        } else {
+          message = String(error)
+        }
+
+        // https://github.com/microsoft/vscode/blob/main/src/vs/workbench/api/browser/mainThreadAuthentication.ts#L238
+        // throw new Error('User did not consent to login.')
+        // Calling again to ensure User Menu Badge
+        if (createIfNone && message === 'User did not consent to login.') {
+          getPlatformAuthSession(false)
+        }
+      })
+
+    authentication.onDidChangeSessions((e) => {
+      if (e.provider.id === AuthenticationProviders.Stateful) {
+        getPlatformAuthSession(false, true).then((session) => {
+          kernel.updateFeatureState('statefulAuth', !!session)
+        })
+      }
+      if (e.provider.id === AuthenticationProviders.GitHub) {
+        getGithubAuthSession(false).then((session) => {
+          kernel.updateFeatureState('githubAuth', !!session)
+        })
+      }
+    })
   }
 
   protected handleMasking(kernel: Kernel, maskingIsOn: boolean): (e: NotebookUiEvent) => void {
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index d58a8a4d..4c20e68d 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -1,4 +1,5 @@
 import path from 'node:path'
+import os from 'node:os'
 
 import {
   Disposable,
@@ -22,9 +23,11 @@ import {
   NotebookEditorSelectionChangeEvent,
   CancellationToken,
   NotebookData,
+  version,
 } from 'vscode'
 import { TelemetryReporter } from 'vscode-telemetry'
 import { UnaryCall } from '@protobuf-ts/runtime-rpc'
+import { map } from 'rxjs/operators'
 
 import {
   type ActiveTerminal,
@@ -45,6 +48,16 @@ import {
 import { API } from '../utils/deno/api'
 import { postClientMessage } from '../utils/messaging'
 import { getNotebookExecutionOrder, registerExtensionEnvVarsMutation } from '../utils/configuration'
+import {
+  isFeatureActive,
+  FeatureContext,
+  getFeatureSnapshot,
+  loadFeaturesState,
+  updateFeatureContext,
+  FEATURES_CONTEXT_STATE_KEY,
+  FeatureName,
+  ExtensionName,
+} from '../features'
 
 import getLogger from './logger'
 import executor, {
@@ -112,6 +125,7 @@ export class Kernel implements Disposable {
   static readonly type = 'runme' as const
 
   readonly #experiments = new Map<string, boolean>()
+  readonly #featuresSettings = new Map<string, boolean>()
 
   #disposables: Disposable[] = []
   #controller = notebooks.createNotebookController(
@@ -132,6 +146,7 @@ export class Kernel implements Disposable {
   protected panelManager: PanelManager
   protected serializer?: SerializerBase
   protected reporter?: GrpcReporter
+  protected featuresState$?
 
   readonly onVarsChangeEvent: EnvVarsChangedEvent
 
@@ -139,11 +154,9 @@ export class Kernel implements Disposable {
     const config = workspace.getConfiguration('runme.experiments')
 
     this.onVarsChangeEvent = new EnvVarsChangedEvent()
-
     this.#experiments.set('grpcSerializer', config.get<boolean>('grpcSerializer', true))
     this.#experiments.set('grpcRunner', config.get<boolean>('grpcRunner', true))
     this.#experiments.set('grpcServer', config.get<boolean>('grpcServer', true))
-    this.#experiments.set('escalationButton', config.get<boolean>('escalationButton', false))
     this.#experiments.set('smartEnvStore', config.get<boolean>('smartEnvStore', false))
     this.#experiments.set('aiLogs', config.get<boolean>('aiLogs', false))
     this.#experiments.set('shellWarning', config.get<boolean>('shellWarning', false))
@@ -170,6 +183,7 @@ export class Kernel implements Disposable {
 
     this.messaging.postMessage({ from: 'kernel' })
     this.panelManager = new PanelManager(context)
+
     this.#disposables.push(
       this.messaging.onDidReceiveMessage(this.#handleRendererMessage.bind(this)),
       workspace.onDidOpenNotebookDocument(this.#handleOpenNotebook.bind(this)),
@@ -180,6 +194,55 @@ export class Kernel implements Disposable {
       this.onVarsChangeEvent,
       this.registerTerminalProfile(),
     )
+
+    const packageJSON = context?.extension?.packageJSON || {}
+    const featContext: FeatureContext = {
+      os: os.platform(),
+      vsCodeVersion: version as string,
+      extensionVersion: packageJSON?.version,
+      githubAuth: false,
+      statefulAuth: false,
+      extensionId: context?.extension?.id as ExtensionName,
+    }
+
+    this.featuresState$ = loadFeaturesState(packageJSON, featContext, this.#featuresSettings)
+
+    if (this.featuresState$) {
+      const features = workspace.getConfiguration('runme.features')
+      const featureNames = Object.keys(FeatureName).map((f) => f.toLowerCase())
+      if (features) {
+        featureNames.forEach((feature) => {
+          if (features.has(feature)) {
+            this.#featuresSettings.set(feature, features.get<boolean>(feature, false))
+          }
+        })
+      }
+
+      const subscription = this.featuresState$
+        .pipe(map((_state) => getFeatureSnapshot(this.featuresState$)))
+        .subscribe((snapshot) => {
+          ContextState.addKey(FEATURES_CONTEXT_STATE_KEY, snapshot)
+          postClientMessage(this.messaging, ClientMessages.featuresUpdateAction, {
+            snapshot: snapshot,
+          })
+        })
+
+      this.#disposables.push({
+        dispose: () => subscription.unsubscribe(),
+      })
+    }
+  }
+
+  isFeatureActive(featureName: FeatureName): boolean {
+    if (!this.featuresState$) {
+      return false
+    }
+
+    return isFeatureActive(featureName, this.featuresState$)
+  }
+
+  updateFeatureState<K extends keyof FeatureContext>(key: K, value: FeatureContext[K]) {
+    updateFeatureContext(this.featuresState$, key, value, this.#featuresSettings)
   }
 
   registerNotebookCell(cell: NotebookCell) {
@@ -285,8 +348,7 @@ export class Kernel implements Disposable {
           action,
         )
         if (taken && taken === action) {
-          const doc = await workspace.openTextDocument(notebookDocument.uri)
-          openSplitViewAsMarkdownText(doc)
+          openSplitViewAsMarkdownText(notebookDocument.uri)
         }
       }
     }
@@ -569,6 +631,12 @@ export class Kernel implements Disposable {
       return insertCodeCell(message.output.cellId, editor, cellText, 'sh', false)
     } else if (message.type.startsWith('terminal:')) {
       return
+    } else if (message.type === ClientMessages.featuresRequest) {
+      const snapshot = getFeatureSnapshot(this.featuresState$)
+      postClientMessage(this.messaging, ClientMessages.featuresResponse, {
+        snapshot: snapshot,
+      })
+      return
     }
 
     log.error(`Unknown kernel event type: ${message.type}`)
diff --git a/src/extension/messages/platformRequest/saveCellExecution.ts b/src/extension/messages/platformRequest/saveCellExecution.ts
index bd312909..00f01700 100644
--- a/src/extension/messages/platformRequest/saveCellExecution.ts
+++ b/src/extension/messages/platformRequest/saveCellExecution.ts
@@ -41,13 +41,10 @@ export default async function saveCellExecution(
 
   log.info('Saving cell execution')
 
-  const escalationButton = kernel.hasExperimentEnabled('escalationButton', false)!
   const cacheId = GrpcSerializer.getDocumentCacheId(editor.notebook.metadata) as string
   const plainSessionOutput = await kernel.getPlainCache(cacheId)
   const maskedSessionOutput = await kernel.getMaskedCache(cacheId)
 
-  log.info(`escalationButton: ${escalationButton ? 'enabled' : 'disabled'}`)
-
   const vsEnv = {
     appHost: env.appHost,
     appName: env.appName,
@@ -75,14 +72,12 @@ export default async function saveCellExecution(
     let data:
       | FetchResult<CreateExtensionCellOutputMutation>
       | FetchResult<CreateCellExecutionMutation>
-    let showEscalationButton: boolean
 
     if (!session) {
       return postClientMessage(messaging, ClientMessages.platformApiResponse, {
         data: {
           displayShare: false,
         },
-        escalationButton,
         id: message.output.id,
       })
     }
@@ -154,8 +149,6 @@ export default async function saveCellExecution(
         },
       })
       data = result
-      showEscalationButton = !!(result.data as CreateExtensionCellOutputMutation)
-        ?.createExtensionCellOutput?.isSlackReady
     }
     // TODO: Remove the legacy createCellExecution mutation once the reporter is fully tested.
     else {
@@ -252,19 +245,14 @@ export default async function saveCellExecution(
       })
 
       data = result
-      showEscalationButton = !!(result.data as CreateCellExecutionMutation)?.createCellExecution
-        ?.isSlackReady
     }
 
     log.info('Cell execution saved')
 
-    log.info(`showEscalationButton: ${showEscalationButton ? 'enabled' : 'disabled'}`)
-
     TelemetryReporter.sendTelemetryEvent('app.save')
     return postClientMessage(messaging, ClientMessages.platformApiResponse, {
       data,
       id: message.output.id,
-      escalationButton: showEscalationButton,
     })
   } catch (error) {
     log.error('Error saving cell execution', (error as Error).message)
@@ -272,7 +260,6 @@ export default async function saveCellExecution(
     return postClientMessage(messaging, ClientMessages.platformApiResponse, {
       data: (error as any).message,
       id: message.output.id,
-      escalationButton,
       hasErrors: true,
     })
   }
diff --git a/src/extension/messages/platformRequest/updateCellExecution.ts b/src/extension/messages/platformRequest/updateCellExecution.ts
index 9bdeb623..a6164058 100644
--- a/src/extension/messages/platformRequest/updateCellExecution.ts
+++ b/src/extension/messages/platformRequest/updateCellExecution.ts
@@ -15,14 +15,11 @@ const log = getLogger('UpdateCell')
 
 export default async function updateCellExecution(
   requestMessage: APIRequestMessage,
-  kernel: Kernel,
+  _kernel: Kernel,
 ): Promise<void | boolean> {
   const { messaging, message } = requestMessage
   log.info('Updating cell execution', message.output.data.id)
 
-  const escalationButton = kernel.hasExperimentEnabled('escalationButton', false)!
-  log.info(`escalationButton: ${escalationButton ? 'enabled' : 'disabled'}`, message.output.data.id)
-
   try {
     const session = await getPlatformAuthSession()
 
@@ -43,17 +40,10 @@ export default async function updateCellExecution(
     })
     log.info('Cell execution updated', message.output.data.id)
 
-    const showEscalationButton = !!result.data?.updateCellOutput?.isSlackReady
-    log.info(
-      `showEscalationButton: ${showEscalationButton ? 'enabled' : 'disabled'}`,
-      message.output.data.id,
-    )
-
     TelemetryReporter.sendTelemetryEvent('app.update')
     return postClientMessage(messaging, ClientMessages.platformApiResponse, {
       data: result,
       id: message.output.id,
-      escalationButton: showEscalationButton,
     })
   } catch (error) {
     log.error('Error updating cell execution', message.output.data.id, (error as Error).message)
@@ -61,7 +51,6 @@ export default async function updateCellExecution(
     return postClientMessage(messaging, ClientMessages.platformApiResponse, {
       data: (error as any).message,
       id: message.output.id,
-      escalationButton,
       hasErrors: true,
     })
   }
diff --git a/src/extension/provider/cloudAuth.ts b/src/extension/provider/githubAuth.ts
similarity index 54%
rename from src/extension/provider/cloudAuth.ts
rename to src/extension/provider/githubAuth.ts
index 5a27745b..c6ccfe49 100644
--- a/src/extension/provider/cloudAuth.ts
+++ b/src/extension/provider/githubAuth.ts
@@ -1,13 +1,13 @@
 import { Disposable, ExtensionContext, authentication } from 'vscode'
 
 import { checkSession } from '../utils'
-import { CLOUD_USER_SIGNED_IN } from '../../constants'
+import { GITHUB_USER_SIGNED_IN } from '../../constants'
 import ContextState from '../contextState'
 
-export class CloudAuthProvider implements Disposable {
+export class GithubAuthProvider implements Disposable {
   constructor(context: ExtensionContext) {
-    const userSignedIn = context.globalState.get(CLOUD_USER_SIGNED_IN, false)
-    ContextState.addKey(CLOUD_USER_SIGNED_IN, userSignedIn)
+    const userSignedIn = context.globalState.get(GITHUB_USER_SIGNED_IN, false)
+    ContextState.addKey(GITHUB_USER_SIGNED_IN, userSignedIn)
     authentication.onDidChangeSessions(() => {
       checkSession(context)
     })
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index 47339072..b4862015 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -41,7 +41,7 @@ import {
   SERVER_ADDRESS,
   CATEGORY_SEPARATOR,
   NOTEBOOK_AUTOSAVE_ON,
-  CLOUD_USER_SIGNED_IN,
+  GITHUB_USER_SIGNED_IN,
   NOTEBOOK_OUTPUTS_MASKED,
 } from '../constants'
 import {
@@ -55,8 +55,13 @@ import {
   getTLSDir,
   getTLSEnabled,
   isPlatformAuthEnabled,
-  isRunmeAppButtonsEnabled,
 } from '../utils/configuration'
+import {
+  FeatureName,
+  FEATURES_CONTEXT_STATE_KEY,
+  isFeatureActive,
+  loadFeatureSnapshot,
+} from '../features'
 
 import CategoryQuickPickItem from './quickPickItems/category'
 import getLogger from './logger'
@@ -65,7 +70,6 @@ import { BOOTFILE, BOOTFILE_DEMO } from './constants'
 import { IRunnerEnvironment } from './runner/environment'
 import { setCurrentCellExecutionDemo } from './handler/utils'
 import ContextState from './contextState'
-import { RunmeService } from './services/runme'
 import { GCPResolver } from './resolvers/gcpResolver'
 import { AWSResolver } from './resolvers/awsResolver'
 
@@ -552,63 +556,41 @@ export function convertEnvList(envs: string[]): Record<string, string | undefine
   )
 }
 
-export function getAuthSession(createIfNone: boolean = true) {
+export function getGithubAuthSession(createIfNone: boolean = true) {
   return authentication.getSession(AuthenticationProviders.GitHub, ['user:email'], {
     createIfNone,
   })
 }
 
-export async function getPlatformAuthSession(createIfNone: boolean = true) {
+export async function getPlatformAuthSession(createIfNone: boolean = true, silent?: boolean) {
   const scopes = ['profile', 'offline_access']
-  const options: AuthenticationGetSessionOptions = { createIfNone }
+  const options: AuthenticationGetSessionOptions = {}
+
+  if (silent !== undefined) {
+    options.silent = silent
+  } else {
+    options.createIfNone = createIfNone
+  }
 
   return await authentication.getSession(AuthenticationProviders.Stateful, scopes, options)
 }
 
 export async function resolveAuthToken(createIfNone: boolean = true) {
   let session: AuthenticationSession | undefined
-
-  if (isPlatformAuthEnabled()) {
-    session = await getPlatformAuthSession(createIfNone)
-    if (!session) {
-      throw new Error('You must authenticate with your Stateful account')
-    }
-
-    return session.accessToken
-  }
-
-  session = await getAuthSession(createIfNone)
+  session = await getPlatformAuthSession(createIfNone)
   if (!session) {
-    throw new Error('You must authenticate with your GitHub account')
-  }
-
-  const service = new RunmeService({ githubAccessToken: session.accessToken })
-  const response = await service.getUserToken()
-  if (!response) {
-    throw new Error('Unable to retrieve an access token')
+    throw new Error('You must authenticate with your Stateful account')
   }
 
-  return response.token
+  return session.accessToken
 }
 
 export async function resolveAppToken(createIfNone: boolean = true) {
-  if (isPlatformAuthEnabled()) {
-    const session = await getPlatformAuthSession(createIfNone)
-    if (!session) {
-      return null
-    }
-    return { token: session.accessToken }
-  }
-
-  const session = await getAuthSession(createIfNone)
-
-  if (session) {
-    const service = new RunmeService({ githubAccessToken: session.accessToken })
-    const userToken = await service.getUserToken()
-    return await service.getAppToken(userToken)
+  const session = await getPlatformAuthSession(createIfNone)
+  if (!session) {
+    return null
   }
-
-  return null
+  return { token: session.accessToken }
 }
 
 export function fetchStaticHtml(appUrl: string) {
@@ -735,7 +717,7 @@ export async function resolveUserSession(
 ): Promise<AuthenticationSession | undefined> {
   return isPlatformAuthEnabled()
     ? await getPlatformAuthSession(createIfNone)
-    : await getAuthSession(createIfNone)
+    : await getGithubAuthSession(createIfNone)
 }
 
 /**
@@ -745,12 +727,16 @@ export async function resolveUserSession(
  * @returns AuthenticationSession
  */
 export async function promptUserSession(): Promise<AuthenticationSession | undefined> {
-  let session: AuthenticationSession | undefined = await resolveUserSession(false)
-  const displayLoginPrompt = getLoginPrompt() && isRunmeAppButtonsEnabled()
+  let session = await resolveUserSession(false)
+
+  const provider = isPlatformAuthEnabled() ? 'Stateful' : 'GitHub'
+  const featureState$ = loadFeatureSnapshot(ContextState.getKey(FEATURES_CONTEXT_STATE_KEY))
+  const displayLoginPrompt = getLoginPrompt() && isFeatureActive(FeatureName.Share, featureState$)
+
   if (!session && displayLoginPrompt !== false) {
     const option = await window.showInformationMessage(
       `Securely store your cell outputs.
-      Sign in with ${isPlatformAuthEnabled() ? 'Stateful' : 'GitHub'} is required, do you want to proceed?`,
+      Sign in with ${provider} is required, do you want to proceed?`,
       'Yes',
       'No',
       'Open Settings',
@@ -765,7 +751,7 @@ export async function promptUserSession(): Promise<AuthenticationSession | undef
 
     session = await resolveUserSession(true)
     if (!session) {
-      throw new Error('You must authenticate with your GitHub account')
+      throw new Error(`You must authenticate with your ${provider} account`)
     }
   }
 
@@ -773,9 +759,9 @@ export async function promptUserSession(): Promise<AuthenticationSession | undef
 }
 
 export async function checkSession(context: ExtensionContext) {
-  const session = await getAuthSession(false)
-  context.globalState.update(CLOUD_USER_SIGNED_IN, !!session)
-  ContextState.addKey(CLOUD_USER_SIGNED_IN, !!session)
+  const session = await getGithubAuthSession(false)
+  context.globalState.update(GITHUB_USER_SIGNED_IN, !!session)
+  ContextState.addKey(GITHUB_USER_SIGNED_IN, !!session)
 }
 
 export function editJsonc(
diff --git a/src/features.ts b/src/features.ts
new file mode 100644
index 00000000..e730892f
--- /dev/null
+++ b/src/features.ts
@@ -0,0 +1,267 @@
+import { BehaviorSubject } from 'rxjs'
+import { satisfies } from 'semver'
+
+export const FEATURES_CONTEXT_STATE_KEY = 'features'
+
+export type FeatureContext = {
+  os?: string
+  vsCodeVersion?: string
+  runmeVersion?: string
+  extensionVersion?: string
+  githubAuth?: boolean // `true`, `false`, or `undefined`
+  statefulAuth?: boolean // `true`, `false`, or `undefined`
+  extensionId?: ExtensionName
+}
+
+export enum ExtensionName {
+  StatefulRunme = 'stateful.runme',
+  StatefulPlatform = 'stateful.platform',
+}
+
+type EnabledForExtensions = Partial<Record<ExtensionName, boolean>>
+
+export type FeatureCondition = {
+  os?: string
+  vsCodeVersion?: string
+  runmeVersion?: string
+  extensionVersion?: string
+  githubAuthRequired?: boolean
+  statefulAuthRequired?: boolean
+  enabledForExtensions?: EnabledForExtensions
+}
+
+export enum FeatureName {
+  Gist = 'Gist',
+  Share = 'Share',
+  Escalate = 'Escalate',
+  ForceLogin = 'ForceLogin',
+}
+
+export type Feature = {
+  enabled: boolean
+  activated: boolean
+  conditions: FeatureCondition
+}
+
+export type Features = Partial<Record<keyof typeof FeatureName, Feature>>
+
+export type FeatureState = {
+  features: Features
+  context?: FeatureContext
+}
+
+export type FeatureObserver = BehaviorSubject<FeatureState>
+
+function loadFeaturesFromPackageJson(packageJSON: any): Features {
+  const features = (packageJSON?.runme?.features || {}) as Features
+  return features
+}
+
+export function loadFeaturesState(
+  packageJSON: any,
+  context?: FeatureContext,
+  overrides: Map<string, boolean> = new Map(),
+): FeatureObserver {
+  const initialFeatures = loadFeaturesFromPackageJson(packageJSON)
+  const state = new BehaviorSubject<FeatureState>({
+    features: initialFeatures,
+    context,
+  })
+  updateFeatureState(state, context, overrides)
+  return state
+}
+
+function checkEnabled(enabled?: boolean, contextEnabled?: boolean): boolean {
+  if (contextEnabled !== undefined) {
+    return contextEnabled === true
+  }
+
+  return enabled === true
+}
+
+function checkOS(os?: string, contextOS?: string): boolean {
+  return !os || os === 'All' || contextOS === os
+}
+
+function checkVersion(requiredVersion?: string, actualVersion?: string): boolean {
+  if (!requiredVersion) {
+    return true
+  }
+
+  if (!actualVersion) {
+    return true
+  }
+
+  return satisfies(actualVersion, requiredVersion)
+}
+
+function checkAuth(required?: boolean, isAuthenticated?: boolean): boolean {
+  if (!required) {
+    return true
+  }
+
+  if (!isAuthenticated) {
+    return false
+  }
+
+  return true
+}
+
+function checkExtensionId(
+  enabledForExtensions?: EnabledForExtensions,
+  extensionId?: ExtensionName,
+): boolean {
+  if (!enabledForExtensions) {
+    return true
+  }
+
+  if (!extensionId) {
+    return false
+  }
+
+  return enabledForExtensions[extensionId] === true
+}
+
+function isActive(
+  featureName: FeatureName,
+  feature: Feature,
+  context?: FeatureContext,
+  overrides: Map<string, boolean> = new Map(),
+): boolean {
+  const {
+    os,
+    vsCodeVersion,
+    runmeVersion,
+    extensionVersion,
+    githubAuthRequired,
+    statefulAuthRequired,
+    enabledForExtensions,
+  } = feature.conditions
+
+  if (!checkEnabled(feature.enabled, overrides.get(featureName.toLowerCase()))) {
+    console.log(`Feature "${featureName}" is inactive due to checkEnabled.`)
+    return false
+  }
+
+  if (!checkOS(os, context?.os)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkOS. Expected OS: ${os}, actual OS: ${context?.os}`,
+    )
+    return false
+  }
+
+  if (!checkVersion(vsCodeVersion, context?.vsCodeVersion)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkVersion (vsCodeVersion). Expected: ${vsCodeVersion}, actual: ${context?.vsCodeVersion}`,
+    )
+    return false
+  }
+
+  if (!checkVersion(runmeVersion, context?.runmeVersion)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkVersion (runmeVersion). Expected: ${runmeVersion}, actual: ${context?.runmeVersion}`,
+    )
+    return false
+  }
+
+  if (!checkVersion(extensionVersion, context?.extensionVersion)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkVersion (extensionVersion). Expected: ${extensionVersion}, actual: ${context?.extensionVersion}`,
+    )
+    return false
+  }
+
+  if (!checkAuth(githubAuthRequired, context?.githubAuth)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkAuth (githubAuth). Required: ${githubAuthRequired}, actual: ${context?.githubAuth}`,
+    )
+    return false
+  }
+
+  if (!checkAuth(statefulAuthRequired, context?.statefulAuth)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkAuth (statefulAuth). Required: ${statefulAuthRequired}, actual: ${context?.statefulAuth}`,
+    )
+    return false
+  }
+
+  if (!checkExtensionId(enabledForExtensions, context?.extensionId)) {
+    console.log(
+      `Feature "${featureName}" is inactive due to checkExtensionId. Expected: ${enabledForExtensions}, actual: ${context?.extensionId}`,
+    )
+    return false
+  }
+
+  console.log(`Feature "${featureName} is active`)
+  return true
+}
+
+export function updateFeatureState(
+  featureState$: FeatureObserver,
+  context?: FeatureContext,
+  overrides: Map<string, boolean> = new Map(),
+) {
+  const currentState = featureState$.getValue()
+
+  const updatedFeatures = Object.entries(currentState.features).reduce(
+    (acc, [key, value]) => ({
+      ...acc,
+      [key]: {
+        ...value,
+        activated: isActive(key as FeatureName, value, context, overrides),
+      },
+    }),
+    {} as Features,
+  )
+
+  featureState$.next({ features: updatedFeatures, context })
+  return featureState$
+}
+
+export function updateFeatureContext<K extends keyof FeatureContext>(
+  featureState$: FeatureObserver | undefined,
+  key: K,
+  value: FeatureContext[K],
+  overrides: Map<string, boolean>,
+) {
+  if (!featureState$) {
+    return
+  }
+  const currentState = featureState$.getValue()
+  const newContext = { ...(currentState.context || {}), [key]: value }
+
+  if (newContext[key] !== currentState?.context?.[key]) {
+    updateFeatureState(featureState$, newContext, overrides)
+  }
+}
+
+export function getFeatureSnapshot(featureState$: FeatureObserver | undefined): string {
+  if (!featureState$) {
+    return ''
+  }
+
+  return JSON.stringify(featureState$.getValue())
+}
+
+export function loadFeatureSnapshot(snapshot: string): FeatureObserver {
+  const { features, context } = JSON.parse(snapshot)
+  const featureState$ = new BehaviorSubject<FeatureState>({
+    features,
+    context,
+  })
+
+  featureState$.next({ features, context: featureState$.getValue().context })
+  return featureState$
+}
+
+export function isFeatureActive(
+  featureName: FeatureName,
+  featureState$?: FeatureObserver,
+): boolean {
+  if (!featureState$) {
+    return false
+  }
+
+  const feature = featureState$.getValue().features[featureName]
+  return feature ? feature.activated : false
+}
diff --git a/src/types.ts b/src/types.ts
index d9081918..f2f07c80 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -377,7 +377,6 @@ interface Payload {
     ['runme.dev/id']: string
     content?: string
     initialRows?: number
-    enableShareButton: boolean
     isAutoSaveEnabled: boolean
     isSessionOutputsEnabled: boolean
     isPlatformAuthEnabled: boolean
@@ -438,7 +437,6 @@ export interface ClientMessagePayload {
   [ClientMessages.onProgramClose]: {
     ['runme.dev/id']: string
     code: number | void
-    escalationButton: boolean
   }
   [ClientMessages.activeThemeChanged]: string
   [ClientMessages.openLink]: string
@@ -499,7 +497,6 @@ export interface ClientMessagePayload {
   [ClientMessages.platformApiResponse]: {
     data: any
     id: string
-    escalationButton: boolean
     hasErrors?: boolean
   }
   [ClientMessages.optionsModal]: {
@@ -641,6 +638,16 @@ export interface ClientMessagePayload {
     command: string
     argument?: string
   }
+
+  [ClientMessages.featuresUpdateAction]: {
+    snapshot: string
+  }
+
+  [ClientMessages.featuresRequest]: {}
+
+  [ClientMessages.featuresResponse]: {
+    snapshot: string
+  }
 }
 
 export interface OutputItemsPayload {
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts
index 0684617f..1daeb3f5 100644
--- a/src/utils/configuration.ts
+++ b/src/utils/configuration.ts
@@ -82,7 +82,6 @@ const configurationSchema = {
   app: {
     apiUrl: z.string().default(DEFAULT_RUNME_APP_API_URL),
     baseDomain: z.string().default(DEFAULT_RUNME_BASE_DOMAIN),
-    enableShare: z.boolean().default(true),
     forceNewWindow: z.boolean().default(true),
     notebookAutoSave: z
       .enum([NotebookAutoSaveSetting.Yes, NotebookAutoSaveSetting.No])
@@ -400,10 +399,6 @@ const getRunmeBaseDomain = (): string => {
   return baseDomain
 }
 
-const isRunmeAppButtonsEnabled = (): boolean => {
-  return getCloudConfigurationValue('enableShare', true)
-}
-
 const getRunmePanelIdentifier = (identifer: string): string => {
   const configurationSection = workspace.getConfiguration(`${APP_SECTION_NAME}.panel`)
   const configurationValue = configurationSection.get<string>(identifer) || identifer
@@ -467,7 +462,6 @@ export {
   getTLSEnabled,
   isNotebookTerminalEnabledForCell,
   isNotebookTerminalFeatureEnabled,
-  isRunmeAppButtonsEnabled,
   isPlatformAuthEnabled,
   registerExtensionEnvVarsMutation,
   getSessionOutputs,


Test patch:
diff --git a/tests/e2e/pageobjects/cell.page.ts b/tests/e2e/pageobjects/cell.page.ts
index 0f4f36a9..0c49b590 100644
--- a/tests/e2e/pageobjects/cell.page.ts
+++ b/tests/e2e/pageobjects/cell.page.ts
@@ -106,7 +106,9 @@ export class NotebookCell extends BasePage<typeof notebookCellLocators, typeof l
         expectedTerminal !== OutputType.Display ? await row.getText() : await row.getHTML(false)
 
       if (expectedTerminal === OutputType.TerminalView) {
-        const stopTerm = text.lastIndexOf('Copy\n')
+        // TODO: Why?
+        // const stopTerm = text.lastIndexOf('Copy\n')
+        const stopTerm = text.lastIndexOf('Copy')
         text = text.slice(0, stopTerm).trim()
       }
 
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index 4aa06002..236d57dd 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -200,6 +200,8 @@ suite('runCliCommand', () => {
   beforeEach(() => {
     vi.mocked(getBinaryPath).mockClear()
     vi.mocked(window.showInformationMessage).mockClear()
+    vi.mocked(window.showNotebookDocument).mockClear()
+    vi.mocked(window.showTextDocument).mockClear()
     vi.mocked(getAnnotations).mockClear()
     vi.mocked(terminal.sendText).mockClear()
     vi.mocked(getCLIUseIntegratedRunme).mockClear()
@@ -281,27 +283,29 @@ suite('runCliCommand', () => {
   })
 })
 
-test('open markdown as Runme notebook split', (file: NotebookDocument) => {
+test('open markdown as Runme notebook split', async (file: NotebookDocument) => {
   vi.mocked(getActionsOpenViewInEditor).mockReturnValue('split' as const)
-  openAsRunmeNotebook(file)
+  vi.mocked(workspace.openNotebookDocument).mockResolvedValue(file)
+  await openAsRunmeNotebook(file.uri)
   expect(window.showNotebookDocument).toBeCalledWith(file, { viewColumn: undefined })
 })
 
-test('open Runme notebook in text editor split', (file: TextDocument) => {
+test('open Runme notebook in text editor split', async (file: TextDocument) => {
   vi.mocked(getActionsOpenViewInEditor).mockReturnValue('split' as const)
-  openSplitViewAsMarkdownText(file)
+  vi.mocked(workspace.openTextDocument).mockResolvedValue(file)
+  await openSplitViewAsMarkdownText(file.uri)
   expect(window.showTextDocument).toBeCalledWith(file, { viewColumn: ViewColumn.Beside })
 })
 
-test('open markdown as Runme notebook toggle', (file: NotebookDocument) => {
+test('open markdown as Runme notebook toggle', async (file: NotebookDocument) => {
   vi.mocked(getActionsOpenViewInEditor).mockReturnValue('toggle' as const)
-  openAsRunmeNotebook(file)
+  await openAsRunmeNotebook(file.uri)
   expect(commands.executeCommand).toBeCalledWith('workbench.action.toggleEditorType')
 })
 
-test('open Runme notebook in text editor toggle', (file: TextDocument) => {
+test('open Runme notebook in text editor toggle', async (file: TextDocument) => {
   vi.mocked(getActionsOpenViewInEditor).mockReturnValue('toggle' as const)
-  openSplitViewAsMarkdownText(file)
+  await openSplitViewAsMarkdownText(file.uri)
   expect(commands.executeCommand).toBeCalledWith('workbench.action.toggleEditorType')
 })
 
@@ -345,6 +349,9 @@ suite('askAlternativeOutputsAction', () => {
 })
 
 suite('askNewRunnerSession', () => {
+  beforeEach(() => {
+    vi.mocked(workspace.openNotebookDocument).mockClear()
+  })
   test('asks are you sure first', async () => {
     const newRunnerEnvironment = vi.fn()
     const kernel = { newRunnerEnvironment }
@@ -381,7 +388,7 @@ test('createNewRunmeNotebook', async () => {
   await createNewRunmeNotebook()
   expect(workspace.openNotebookDocument).toBeCalledWith('runme', expect.any(Object))
   expect(NotebookCellData).toBeCalledTimes(3)
-  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', expect.any(String), 'runme')
+  expect(commands.executeCommand).toBeCalledWith('vscode.openWith', undefined, 'runme')
 })
 
 test('welcome command', async () => {
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index 2ce4e3a5..8ba4bc7e 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -68,6 +68,8 @@ vi.mock('../../src/extension/utils', async () => ({
   checkSession: vi.fn(),
   togglePreviewButton: vi.fn(),
   resetNotebookSettings: vi.fn(),
+  getGithubAuthSession: vi.fn().mockResolvedValue(undefined),
+  getPlatformAuthSession: vi.fn().mockResolvedValue(undefined),
 }))
 
 vi.mock('../../src/extension/grpc/runner/v1', () => ({}))
@@ -80,6 +82,7 @@ test('initializes all providers', async () => {
   }
   vi.mocked(workspace.getConfiguration).mockReturnValue({
     get: vi.fn((config: string) => configValues[config]),
+    has: vi.fn(),
   } as any)
   const dummyFilePath = Uri.file('/foo/bar')
   vi.mocked(Uri.joinPath).mockReturnValue(dummyFilePath)
diff --git a/tests/extension/features.test.ts b/tests/extension/features.test.ts
new file mode 100644
index 00000000..a80d7029
--- /dev/null
+++ b/tests/extension/features.test.ts
@@ -0,0 +1,193 @@
+import { describe, it, expect, beforeEach } from 'vitest'
+
+import {
+  updateFeatureState,
+  getFeatureSnapshot,
+  loadFeaturesState,
+  isFeatureActive,
+  loadFeatureSnapshot,
+  FeatureContext,
+  FeatureState,
+  FeatureObserver,
+  FeatureName,
+  ExtensionName,
+} from '../../src/features'
+
+const packageJSON = {
+  runme: {
+    features: {
+      Escalate: {
+        enabled: true,
+        conditions: {
+          os: 'All',
+          vsCodeVersion: '>=1.0.0',
+          runmeVersion: '>=1.0.0',
+          extensionVersion: '>=1.0.0',
+          githubAuthRequired: undefined,
+          statefulAuthRequired: true,
+          enabledForExtensions: {
+            'stateful.platform': true,
+            'stateful.runme': false,
+          },
+        },
+      },
+      Gist: {
+        enabled: true,
+        conditions: {
+          os: 'win32',
+          vsCodeVersion: '>=1.0.0',
+          runmeVersion: '>=1.0.0',
+          extensionVersion: '>=1.0.0',
+          githubAuthRequired: true,
+          statefulAuthRequired: false,
+          enabledForExtensions: {
+            'stateful.platform': true,
+            'stateful.runme': true,
+          },
+        },
+      },
+    },
+  },
+}
+
+describe('Feature Store', () => {
+  let featureState$: FeatureObserver
+
+  beforeEach(() => {
+    featureState$ = loadFeaturesState(packageJSON, {
+      os: 'linux',
+    })
+  })
+
+  it('should evaluate and enable features correctly based on context', () => {
+    const initialContext: FeatureContext = {
+      os: 'win32',
+      vsCodeVersion: '1.59.0',
+      extensionVersion: '1.0.0',
+      runmeVersion: '1.3.0',
+      githubAuth: true,
+      statefulAuth: true,
+      extensionId: ExtensionName.StatefulRunme,
+    }
+
+    updateFeatureState(featureState$, initialContext)
+
+    const currentFeatures = (featureState$.getValue() as FeatureState).features
+
+    expect(currentFeatures.Escalate?.activated).toBe(false)
+    expect(currentFeatures.Gist?.activated).toBe(true)
+  })
+
+  it('should take a snapshot of the current state', () => {
+    const initialContext: FeatureContext = {
+      os: 'win32',
+      vsCodeVersion: '1.59.0',
+      extensionVersion: '1.0.0',
+      runmeVersion: '1.3.0',
+      githubAuth: true,
+      statefulAuth: true,
+      extensionId: 'stateful.runme',
+    }
+
+    updateFeatureState(featureState$, initialContext)
+    const snapshot = getFeatureSnapshot(featureState$)
+
+    expect(snapshot).toBe(JSON.stringify(featureState$.getValue()))
+  })
+
+  it('should load a snapshot and restore feature state', () => {
+    const initialContext: FeatureContext = {
+      os: 'win32',
+      vsCodeVersion: '1.59.0',
+      extensionVersion: '1.0.0',
+      runmeVersion: '1.3.0',
+      githubAuth: true,
+      statefulAuth: true,
+      extensionId: ExtensionName.StatefulRunme,
+    }
+
+    updateFeatureState(featureState$, initialContext)
+    const snapshot = getFeatureSnapshot(featureState$)
+
+    featureState$.next({
+      context: initialContext,
+      features: {
+        Escalate: {
+          enabled: true,
+          activated: false,
+          conditions: {
+            os: 'All',
+            vsCodeVersion: '>=1.58.0',
+            runmeVersion: '>=1.2.0',
+            githubAuthRequired: undefined,
+            statefulAuthRequired: true,
+          },
+        },
+        Gist: {
+          enabled: true,
+          activated: false,
+          conditions: {
+            os: 'win32',
+            vsCodeVersion: '>=1.60.0',
+            githubAuthRequired: true,
+            statefulAuthRequired: false,
+          },
+        },
+      },
+    })
+
+    const featureStateCopy$ = loadFeatureSnapshot(snapshot)
+
+    const restoredFeatures = featureStateCopy$.getValue()
+    expect(restoredFeatures).toEqual(JSON.parse(snapshot))
+  })
+
+  it('should disable a feature if context does not meet conditions', () => {
+    const newContext: FeatureContext = {
+      os: 'win32',
+      vsCodeVersion: '0.0.1',
+      extensionVersion: '0.0.1',
+      runmeVersion: '0.0.1',
+      githubAuth: false,
+      statefulAuth: false,
+      extensionId: ExtensionName.StatefulRunme,
+    }
+    updateFeatureState(featureState$, newContext)
+
+    const currentFeatures = (featureState$.getValue() as FeatureState).features
+    expect(currentFeatures.Escalate?.activated).toBe(false)
+    expect(currentFeatures.Gist?.activated).toBe(false)
+  })
+
+  it('should correctly identify if a feature is enabled by name', () => {
+    const ctx: FeatureContext = {
+      os: 'linux',
+      vsCodeVersion: '1.59.0',
+      extensionVersion: '1.0.0',
+      runmeVersion: '1.3.0',
+      githubAuth: true,
+      statefulAuth: true,
+      extensionId: ExtensionName.StatefulPlatform,
+    }
+    updateFeatureState(featureState$, ctx)
+
+    expect(isFeatureActive(FeatureName.Escalate, featureState$)).toBe(true)
+    expect(isFeatureActive(FeatureName.Gist, featureState$)).toBe(false)
+  })
+
+  it('should correctly identify if a feature is enabled by extensionId', () => {
+    const ctx: FeatureContext = {
+      os: 'win32',
+      vsCodeVersion: '1.59.0',
+      extensionVersion: '1.0.0',
+      runmeVersion: '1.3.0',
+      githubAuth: true,
+      statefulAuth: true,
+      extensionId: ExtensionName.StatefulRunme,
+    }
+    updateFeatureState(featureState$, ctx)
+
+    expect(isFeatureActive(FeatureName.Escalate, featureState$)).toBe(false)
+    expect(isFeatureActive(FeatureName.Gist, featureState$)).toBe(true)
+  })
+})
diff --git a/tests/extension/handler/uri.test.ts b/tests/extension/handler/uri.test.ts
index af7732ac..53c4caf7 100644
--- a/tests/extension/handler/uri.test.ts
+++ b/tests/extension/handler/uri.test.ts
@@ -15,6 +15,7 @@ import {
 
 vi.mock('vscode')
 vi.mock('vscode-telemetry')
+vi.mock('../../../src/utils/logger')
 vi.mock('../../../src/extension/handler/utils', () => ({
   parseParams: vi.fn().mockReturnValue({}),
   getProjectDir: vi.fn(),
diff --git a/tests/extension/kernel.test.ts b/tests/extension/kernel.test.ts
index a35a6c8c..4f44a1bc 100644
--- a/tests/extension/kernel.test.ts
+++ b/tests/extension/kernel.test.ts
@@ -29,7 +29,7 @@ vi.mock('../../src/extension/utils', async () => {
     getNotebookCategories: vi.fn().mockResolvedValue([]),
     isWindows: () => false,
     isShellLanguage: () => false,
-    getAuthSession: vi.fn().mockResolvedValue({
+    getGithubAuthSession: vi.fn().mockResolvedValue({
       accessToken: '123',
     }),
   }
diff --git a/tests/extension/messages/platformApiRequest/saveCellExecution.test.ts b/tests/extension/messages/platformApiRequest/saveCellExecution.test.ts
index 5dff98f7..423e11a5 100644
--- a/tests/extension/messages/platformApiRequest/saveCellExecution.test.ts
+++ b/tests/extension/messages/platformApiRequest/saveCellExecution.test.ts
@@ -14,6 +14,15 @@ import { GrpcSerializer } from '../../../../src/extension/serializer'
 vi.mock('vscode-telemetry')
 vi.mock('../../../src/extension/runner', () => ({}))
 vi.mock('../../../src/extension/grpc/runner/v1', () => ({}))
+vi.mock('node:os', async (importOriginal) => {
+  const original = (await importOriginal()) as any
+  return {
+    default: {
+      ...original.default,
+      platform: vi.fn().mockReturnValue('linux'),
+    },
+  }
+})
 
 vi.mock('vscode', async () => {
   const mocked = (await vi.importActual('../../../../__mocks__/vscode')) as any
@@ -117,6 +126,14 @@ suite('Save cell execution', () => {
               "from": "kernel",
             },
           ],
+          [
+            {
+              "output": {
+                "snapshot": "{"features":{},"context":{"os":"linux","vsCodeVersion":"9.9.9","githubAuth":false,"statefulAuth":false}}",
+              },
+              "type": "features:updateAction",
+            },
+          ],
           [
             {
               "output": {
@@ -126,7 +143,6 @@ suite('Save cell execution', () => {
                     "id": "cell-id",
                   },
                 },
-                "escalationButton": false,
                 "id": "cell-id",
               },
               "type": "common:platformApiResponse",
@@ -142,6 +158,10 @@ suite('Save cell execution', () => {
             "type": "return",
             "value": undefined,
           },
+          {
+            "type": "return",
+            "value": undefined,
+          },
         ],
       }
     `)
@@ -188,6 +208,14 @@ suite('Save cell execution', () => {
               "from": "kernel",
             },
           ],
+          [
+            {
+              "output": {
+                "snapshot": "{"features":{},"context":{"os":"linux","vsCodeVersion":"9.9.9","githubAuth":false,"statefulAuth":false}}",
+              },
+              "type": "features:updateAction",
+            },
+          ],
           [
             {
               "output": {
@@ -197,7 +225,6 @@ suite('Save cell execution', () => {
                     "id": "cell-id",
                   },
                 },
-                "escalationButton": false,
                 "id": "cell-id",
               },
               "type": "common:platformApiResponse",
@@ -209,7 +236,6 @@ suite('Save cell execution', () => {
                 "data": {
                   "displayShare": false,
                 },
-                "escalationButton": false,
                 "id": "cell-id",
               },
               "type": "common:platformApiResponse",
@@ -229,6 +255,10 @@ suite('Save cell execution', () => {
             "type": "return",
             "value": undefined,
           },
+          {
+            "type": "return",
+            "value": undefined,
+          },
         ],
       }
     `)