Repository: stateful/vscode-runme
Pull request #495 (2023-05-09 18:36:03)

Parse dotenv files
Gives the user configuration to set dotenv file schema, and to toggle it in general.

Part of stateful/runme#234

Note that environment variables set in dotenv files "persist," so even if you delete it from a dotenv file, it will still exist in the runner session. Fixing this will require changes to the behavior and/or API of the grpc server.


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2e08a244..6551e4b1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -77,7 +77,7 @@ jobs:
         if: ${{ github.event.inputs.releaseChannel == 'stable' }}
       
       - name: Run Tests
-        uses: GabrielBB/xvfb-action@v1
+        uses: coactions/setup-xvfb@v1
         with:
           run: npm test
           options: "-screen 0 1600x1200x24"
diff --git a/__mocks__/vscode.ts b/__mocks__/vscode.ts
index cb91f74c..9e7247b7 100644
--- a/__mocks__/vscode.ts
+++ b/__mocks__/vscode.ts
@@ -19,7 +19,6 @@ export const notebooks = {
   }),
 }
 
-
 export class Uri extends URI {
   static file = vi.fn(super.file)
   static parse = vi.fn(super.parse)
@@ -51,13 +50,7 @@ export const workspace = {
     onDidCreate: vi.fn(),
     onDidDelete: vi.fn()
   }),
-  workspaceFolders: [
-    {
-      uri: {
-        fsPath: '/runme/workspace',
-      },
-    },
-  ],
+  workspaceFolders: [ { uri: { fsPath: '/runme/workspace' } } ],
   findFiles: vi.fn().mockReturnValue([
     {
       path: 'runme/workspace/README.md',
diff --git a/examples/branchGPT.md b/examples/branchGPT.md
index f5af4fa6..2423a421 100644
--- a/examples/branchGPT.md
+++ b/examples/branchGPT.md
@@ -2,17 +2,27 @@
 
 [![](https://badgen.net/badge/Run%20this%20/Demo/5B3ADF?icon=https://runme.dev/img/logo.svg)](https://runme.dev/api/runme?repository=https://github.com/stateful/vscode-runme.git&fileToOpen=examples/branchGPT.md)
 
-Install or download binary at https://github.com/stateful/runme/releases/tag/v1.1.0
+On Windows, install via scoop (https://scoop.sh):
 
 ```sh
-$ npm install -g runme # install via node.js
-$ brew install stateful/tap/runme # install via homebrew on macOS
+$ scoop bucket add stateful https://github.com/stateful/scoop-bucket.git
+$ scoop install stateful/runme
 ```
 
+On macOS, install via Homebrew (https://brew.sh):
+
+```sh
+$ brew install stateful/tap/runme
+```
+
+Binaries are also available at https://github.com/stateful/runme/releases/latest
+
+## Demo
+
 Just enter some free-flow description and voilà, get some branch suggestions:
 
 ```sh
-$ npx runme branchGPT
+$ runme branchGPT
 ```
 
-_Read the docs on [runme.dev](https://www.runme.dev/docs/intro) to learn how to get most out of Runme notebooks!_
+*Read the docs on [runme.dev](https://www.runme.dev/docs/intro) to learn how to get most out of Runme notebooks!*
diff --git a/examples/k8s/README.md b/examples/k8s/README.md
index 1d74af8a..0bd958d7 100644
--- a/examples/k8s/README.md
+++ b/examples/k8s/README.md
@@ -1,6 +1,7 @@
 # Deploying a Dockerized React App to Kubernetes Cluster
 
 ## Prerequisites
+
 * Tutorial was done on macOS.
 * Tutorial assumes you have Homebrew installed on you computer. If not, you can install here: https://brew.sh
 * Tutorial assumes you have Docker installed on your computer. If not, you can install it here: https://docs.docker.com/docker-for-mac/install/
@@ -8,34 +9,36 @@
 ## Step 1: Dockerize app
 
 ### Build Docker image
-```sh
+
+```sh { name=docker-build }
 $ docker build -t runme/demo .
 ```
 
 ### Run image locally to make sure it works
-```sh { interactive=false }
-$ docker run -d -p 3000:3000 runme/demo:latest
+
+```sh { name=docker-run interactive=false }
+$ export RUNME_CID=$(docker run -d -p 3000:3000 runme/demo:latest)
+$ echo "Container running with ID $RUNME_CID"
 ```
 
 ### Open Application
 
 And see it running:
 
-```sh
+```sh { name=open-app interactive=false }
 open http://localhost:3000
 ```
 
 then stop the container:
 
-```sh { interactive=false }
-$ docker ps | grep runme/demo:latest
+```sh { name=docker-inspect interactive=false mimeType=text/x-json promptEnv=false }
+$ docker inspect $RUNME_CID | jq ".[0].State"
 ```
 
 Stop the container via:
 
-```sh
-$ export RUNME_CID=<container-id>
-$ docker stop $RUNME_CID
+```sh { name=docker-stop interactive=false }
+$ docker stop $RUNME_CID > /dev/null && echo "Stopped container with ID $RUNME_CID"
 ```
 
 ## Step 2: Deploy application to Kubernetes Cluster with Linkerd
diff --git a/package-lock.json b/package-lock.json
index af9e2a4e..138916a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,10 +21,12 @@
         "@vscode/webview-ui-toolkit": "^1.2.1",
         "clipboardy": "^3.0.0",
         "detect-port": "^1.5.1",
+        "dotenv": "^16.0.3",
         "filenamify": "^6.0.0",
         "get-port": "^6.1.2",
         "got": "^12.5.3",
         "lit": "^2.6.1",
+        "octokit": "^2.0.14",
         "preact": "^10.11.3",
         "undici": "^5.16.0",
         "uuid": "^9.0.0",
@@ -37,6 +39,7 @@
         "xterm-addon-unicode11": "^0.5.0",
         "xterm-addon-web-links": "^0.8.0",
         "xterm-headless": "^5.1.0",
+        "yaml": "^2.2.2",
         "yargs": "^17.6.2",
         "zod": "^3.20.2"
       },
@@ -2994,11 +2997,115 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/@octokit/app": {
+      "version": "13.1.3",
+      "resolved": "https://registry.npmjs.org/@octokit/app/-/app-13.1.3.tgz",
+      "integrity": "sha512-qG3a3AddK/XLmtpQuB2eICbOUTwwdvqfdnyOzU6PlHhhhVapLfuutkbTOllHB/5OS/slUamIBD3zhOYpKsLFDQ==",
+      "dependencies": {
+        "@octokit/auth-app": "^4.0.8",
+        "@octokit/auth-unauthenticated": "^3.0.0",
+        "@octokit/core": "^4.0.0",
+        "@octokit/oauth-app": "^4.0.7",
+        "@octokit/plugin-paginate-rest": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "@octokit/webhooks": "^10.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/auth-app": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-4.0.10.tgz",
+      "integrity": "sha512-cLFDd7Z0TkBB0BLt3rWXNS5f2eTeAjzG9iQlFQ6ttNPaXNdYMtnQmr2NBDFq3WnZFSct9dpSdFaVuquRrCY67w==",
+      "dependencies": {
+        "@octokit/auth-oauth-app": "^5.0.0",
+        "@octokit/auth-oauth-user": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/request-error": "^3.0.0",
+        "@octokit/types": "^9.0.0",
+        "@types/lru-cache": "^5.1.0",
+        "deprecation": "^2.3.1",
+        "lru-cache": "^6.0.0",
+        "universal-github-app-jwt": "^1.1.1",
+        "universal-user-agent": "^6.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/auth-app/node_modules/@types/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="
+    },
+    "node_modules/@octokit/auth-app/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@octokit/auth-app/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+    },
+    "node_modules/@octokit/auth-oauth-app": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-5.0.5.tgz",
+      "integrity": "sha512-UPX1su6XpseaeLVCi78s9droxpGtBWIgz9XhXAx9VXabksoF0MyI5vaa1zo1njyYt6VaAjFisC2A2Wchcu2WmQ==",
+      "dependencies": {
+        "@octokit/auth-oauth-device": "^4.0.0",
+        "@octokit/auth-oauth-user": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "@types/btoa-lite": "^1.0.0",
+        "btoa-lite": "^1.0.0",
+        "universal-user-agent": "^6.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/auth-oauth-device": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-4.0.4.tgz",
+      "integrity": "sha512-Xl85BZYfqCMv+Uvz33nVVUjE7I/PVySNaK6dRRqlkvYcArSr9vRcZC9KVjXYObGRTCN6mISeYdakAZvWEN4+Jw==",
+      "dependencies": {
+        "@octokit/oauth-methods": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "universal-user-agent": "^6.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/auth-oauth-user": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-2.1.1.tgz",
+      "integrity": "sha512-JgqnNNPf9CaWLxWm9uh2WgxcaVYhxBR09NVIPTiMU2dVZ3FObOHs3njBiLNw+zq84k+rEdm5Y7AsiASrZ84Apg==",
+      "dependencies": {
+        "@octokit/auth-oauth-device": "^4.0.0",
+        "@octokit/oauth-methods": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "btoa-lite": "^1.0.0",
+        "universal-user-agent": "^6.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/@octokit/auth-token": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.3.tgz",
       "integrity": "sha512-/aFM2M4HVDBT/jjDBa84sJniv1t9Gm/rLkalaz9htOm+L+8JMj1k9w0CkUdcxNyNxZPlTxKPVko+m1VlM58ZVA==",
-      "dev": true,
       "dependencies": {
         "@octokit/types": "^9.0.0"
       },
@@ -3006,11 +3113,22 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@octokit/auth-unauthenticated": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-3.0.4.tgz",
+      "integrity": "sha512-AT74XGBylcLr4lmUp1s6mjSUgphGdlse21Qjtv5DzpX1YOl5FXKwvNcZWESdhyBbpDT8VkVyLFqa/7a7eqpPNw==",
+      "dependencies": {
+        "@octokit/request-error": "^3.0.0",
+        "@octokit/types": "^9.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/@octokit/core": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz",
       "integrity": "sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg==",
-      "dev": true,
       "dependencies": {
         "@octokit/auth-token": "^3.0.0",
         "@octokit/graphql": "^5.0.0",
@@ -3028,7 +3146,6 @@
       "version": "7.0.5",
       "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.5.tgz",
       "integrity": "sha512-LG4o4HMY1Xoaec87IqQ41TQ+glvIeTKqfjkCEmt5AIwDZJwQeVZFIEYXrYY6yLwK+pAScb9Gj4q+Nz2qSw1roA==",
-      "dev": true,
       "dependencies": {
         "@octokit/types": "^9.0.0",
         "is-plain-object": "^5.0.0",
@@ -3042,7 +3159,6 @@
       "version": "5.0.5",
       "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.5.tgz",
       "integrity": "sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ==",
-      "dev": true,
       "dependencies": {
         "@octokit/request": "^6.0.0",
         "@octokit/types": "^9.0.0",
@@ -3052,17 +3168,57 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@octokit/oauth-app": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-4.2.1.tgz",
+      "integrity": "sha512-HtRWLzvAAizuFKJEoNlhWOktNNhfnLSXJXu2htt5+x2exhHibHupz/+1AAHU4xlBFtQLQh79OB/bQf+1GP5LBg==",
+      "dependencies": {
+        "@octokit/auth-oauth-app": "^5.0.0",
+        "@octokit/auth-oauth-user": "^2.0.0",
+        "@octokit/auth-unauthenticated": "^3.0.0",
+        "@octokit/core": "^4.0.0",
+        "@octokit/oauth-authorization-url": "^5.0.0",
+        "@octokit/oauth-methods": "^2.0.0",
+        "@types/aws-lambda": "^8.10.83",
+        "fromentries": "^1.3.1",
+        "universal-user-agent": "^6.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/oauth-authorization-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-5.0.0.tgz",
+      "integrity": "sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/oauth-methods": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-2.0.5.tgz",
+      "integrity": "sha512-yQP6B5gE3axNxuM3U9KqWs/ErAQ+WLPaPgC/7EjsZsQibkf8sjdAfF8/y/EJW+Dd05XQvadX4WhQZPMnO1SE1A==",
+      "dependencies": {
+        "@octokit/oauth-authorization-url": "^5.0.0",
+        "@octokit/request": "^6.2.3",
+        "@octokit/request-error": "^3.0.3",
+        "@octokit/types": "^9.0.0",
+        "btoa-lite": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/@octokit/openapi-types": {
       "version": "17.1.1",
       "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.1.tgz",
-      "integrity": "sha512-/X7Gh/qWiWaooJmUnYD48SYy72fyrk2ceisOSe89JojK7r0j8YrTwYpDi76kI+c6QiqX1KSgdoBTMJvktsDkYw==",
-      "dev": true
+      "integrity": "sha512-/X7Gh/qWiWaooJmUnYD48SYy72fyrk2ceisOSe89JojK7r0j8YrTwYpDi76kI+c6QiqX1KSgdoBTMJvktsDkYw=="
     },
     "node_modules/@octokit/plugin-paginate-rest": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz",
       "integrity": "sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw==",
-      "dev": true,
       "dependencies": {
         "@octokit/types": "^9.0.0"
       },
@@ -3086,7 +3242,6 @@
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.0.1.tgz",
       "integrity": "sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA==",
-      "dev": true,
       "dependencies": {
         "@octokit/types": "^9.0.0",
         "deprecation": "^2.3.1"
@@ -3098,11 +3253,40 @@
         "@octokit/core": ">=3"
       }
     },
+    "node_modules/@octokit/plugin-retry": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-4.1.3.tgz",
+      "integrity": "sha512-3YKBj7d0J/4mpEc4xzMociWsMNl5lZqrpAnYcW6mqiSGF3wFjU+c6GHih6GLClk31JNvKDr0x9jc5cfm7evkZg==",
+      "dependencies": {
+        "@octokit/types": "^9.0.0",
+        "bottleneck": "^2.15.3"
+      },
+      "engines": {
+        "node": ">= 14"
+      },
+      "peerDependencies": {
+        "@octokit/core": ">=3"
+      }
+    },
+    "node_modules/@octokit/plugin-throttling": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-5.2.0.tgz",
+      "integrity": "sha512-qWYvVIS1aRC3hN4a0ICWUp51IolHIn7E4iri2AKjbExJaovMDvRtae4nGoxG5pP6+WATk7JZ5epOJobFbGYV1w==",
+      "dependencies": {
+        "@octokit/types": "^9.0.0",
+        "bottleneck": "^2.15.3"
+      },
+      "engines": {
+        "node": ">= 14"
+      },
+      "peerDependencies": {
+        "@octokit/core": "^4.0.0"
+      }
+    },
     "node_modules/@octokit/request": {
       "version": "6.2.3",
       "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.3.tgz",
       "integrity": "sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA==",
-      "dev": true,
       "dependencies": {
         "@octokit/endpoint": "^7.0.0",
         "@octokit/request-error": "^3.0.0",
@@ -3119,7 +3303,6 @@
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
       "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
-      "dev": true,
       "dependencies": {
         "@octokit/types": "^9.0.0",
         "deprecation": "^2.0.0",
@@ -3148,11 +3331,37 @@
       "version": "9.2.1",
       "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.1.tgz",
       "integrity": "sha512-Vx4keMiD/CAiwVFasLcH0xBSVbKIHebIZke9i7ZbUWGNN4vJFWSYH6Nvga7UY9NIJCGa6x3QG849XTbi5wYmkA==",
-      "dev": true,
       "dependencies": {
         "@octokit/openapi-types": "^17.1.1"
       }
     },
+    "node_modules/@octokit/webhooks": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-10.9.1.tgz",
+      "integrity": "sha512-5NXU4VfsNOo2VSU/SrLrpPH2Z1ZVDOWFcET4EpnEBX1uh/v8Uz65UVuHIRx5TZiXhnWyRE9AO1PXHa+M/iWwZA==",
+      "dependencies": {
+        "@octokit/request-error": "^3.0.0",
+        "@octokit/webhooks-methods": "^3.0.0",
+        "@octokit/webhooks-types": "6.11.0",
+        "aggregate-error": "^3.1.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/webhooks-methods": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-3.0.2.tgz",
+      "integrity": "sha512-Vlnv5WBscf07tyAvfDbp7pTkMZUwk7z7VwEF32x6HqI+55QRwBTcT+D7DDjZXtad/1dU9E32x0HmtDlF9VIRaQ==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@octokit/webhooks-types": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-6.11.0.tgz",
+      "integrity": "sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw=="
+    },
     "node_modules/@pkgjs/parseargs": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -4081,6 +4290,16 @@
       "resolved": "https://registry.npmjs.org/@types/async-retry/-/async-retry-1.2.1.tgz",
       "integrity": "sha512-yMQ6CVgICWtyFNBqJT3zqOc+TnqqEPLo4nKJNPFwcialiylil38Ie6q1ENeFTjvaLOkVim9K5LisHgAKJWidGQ=="
     },
+    "node_modules/@types/aws-lambda": {
+      "version": "8.10.115",
+      "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.115.tgz",
+      "integrity": "sha512-kCZuFXKLV3y8NjSoaD5+qKTpRWvPz3uh3W/u1uwlw3Mg+MtaStg1NWgjAwUXo/VJDb6n6KF1ljykFNlNwEJ53Q=="
+    },
+    "node_modules/@types/btoa-lite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz",
+      "integrity": "sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg=="
+    },
     "node_modules/@types/cacheable-request": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@@ -4210,6 +4429,14 @@
       "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
       "dev": true
     },
+    "node_modules/@types/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/keyv": {
       "version": "3.1.4",
       "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@@ -7206,8 +7433,7 @@
     "node_modules/before-after-hook": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
-      "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
-      "dev": true
+      "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
     },
     "node_modules/big-integer": {
       "version": "1.6.51",
@@ -7334,6 +7560,11 @@
       "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
       "dev": true
     },
+    "node_modules/bottleneck": {
+      "version": "2.19.5",
+      "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
+      "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
+    },
     "node_modules/bplist-parser": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
@@ -7406,6 +7637,11 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/btoa-lite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
+      "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA=="
+    },
     "node_modules/buffer": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -7453,6 +7689,11 @@
         "node": "*"
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "node_modules/buffer-fill": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
@@ -8571,6 +8812,15 @@
         "node": ">=10"
       }
     },
+    "node_modules/cosmiconfig/node_modules/yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/crc-32": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -9268,8 +9518,7 @@
     "node_modules/deprecation": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
-      "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
-      "dev": true
+      "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
     },
     "node_modules/dequal": {
       "version": "2.0.3",
@@ -9866,6 +10115,14 @@
         "wcwidth": "^1.0.1"
       }
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/edge-paths": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz",
@@ -12276,6 +12533,25 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "node_modules/fromentries": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
+      "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
     "node_modules/fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -13812,7 +14088,6 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
       "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -14679,6 +14954,21 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+      "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash": "^4.17.21",
+        "ms": "^2.1.1",
+        "semver": "^7.3.8"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
     "node_modules/jszip": {
       "version": "3.10.1",
       "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -14733,6 +15023,25 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/keytar": {
       "version": "7.9.0",
       "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz",
@@ -17128,6 +17437,24 @@
       "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==",
       "dev": true
     },
+    "node_modules/octokit": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/octokit/-/octokit-2.0.14.tgz",
+      "integrity": "sha512-z6cgZBFxirpFEQ1La8Lg83GCs5hOV2EPpkYYdjsGNbfQMv8qUGjq294MiRBCbZqLufviakGsPUxaNKe3JrPmsA==",
+      "dependencies": {
+        "@octokit/app": "^13.1.1",
+        "@octokit/core": "^4.0.4",
+        "@octokit/oauth-app": "^4.0.6",
+        "@octokit/plugin-paginate-rest": "^6.0.0",
+        "@octokit/plugin-rest-endpoint-methods": "^7.0.0",
+        "@octokit/plugin-retry": "^4.0.3",
+        "@octokit/plugin-throttling": "^5.0.0",
+        "@octokit/types": "^9.0.0"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/on-exit-leak-free": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz",
@@ -17949,14 +18276,6 @@
         }
       }
     },
-    "node_modules/postcss-load-config/node_modules/yaml": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
-      "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
-      "engines": {
-        "node": ">= 14"
-      }
-    },
     "node_modules/postcss-modules": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-6.0.0.tgz",
@@ -21572,11 +21891,19 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/universal-github-app-jwt": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz",
+      "integrity": "sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==",
+      "dependencies": {
+        "@types/jsonwebtoken": "^9.0.0",
+        "jsonwebtoken": "^9.0.0"
+      }
+    },
     "node_modules/universal-user-agent": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
-      "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
-      "dev": true
+      "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
     },
     "node_modules/universalify": {
       "version": "0.1.2",
@@ -23540,12 +23867,11 @@
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
     },
     "node_modules/yaml": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
-      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
-      "dev": true,
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
+      "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
       "engines": {
-        "node": ">= 6"
+        "node": ">= 14"
       }
     },
     "node_modules/yargs": {
@@ -25955,20 +26281,116 @@
         "json-parse-even-better-errors": "^2.3.1"
       }
     },
+    "@octokit/app": {
+      "version": "13.1.3",
+      "resolved": "https://registry.npmjs.org/@octokit/app/-/app-13.1.3.tgz",
+      "integrity": "sha512-qG3a3AddK/XLmtpQuB2eICbOUTwwdvqfdnyOzU6PlHhhhVapLfuutkbTOllHB/5OS/slUamIBD3zhOYpKsLFDQ==",
+      "requires": {
+        "@octokit/auth-app": "^4.0.8",
+        "@octokit/auth-unauthenticated": "^3.0.0",
+        "@octokit/core": "^4.0.0",
+        "@octokit/oauth-app": "^4.0.7",
+        "@octokit/plugin-paginate-rest": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "@octokit/webhooks": "^10.0.0"
+      }
+    },
+    "@octokit/auth-app": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-4.0.10.tgz",
+      "integrity": "sha512-cLFDd7Z0TkBB0BLt3rWXNS5f2eTeAjzG9iQlFQ6ttNPaXNdYMtnQmr2NBDFq3WnZFSct9dpSdFaVuquRrCY67w==",
+      "requires": {
+        "@octokit/auth-oauth-app": "^5.0.0",
+        "@octokit/auth-oauth-user": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/request-error": "^3.0.0",
+        "@octokit/types": "^9.0.0",
+        "@types/lru-cache": "^5.1.0",
+        "deprecation": "^2.3.1",
+        "lru-cache": "^6.0.0",
+        "universal-github-app-jwt": "^1.1.1",
+        "universal-user-agent": "^6.0.0"
+      },
+      "dependencies": {
+        "@types/lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        }
+      }
+    },
+    "@octokit/auth-oauth-app": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-5.0.5.tgz",
+      "integrity": "sha512-UPX1su6XpseaeLVCi78s9droxpGtBWIgz9XhXAx9VXabksoF0MyI5vaa1zo1njyYt6VaAjFisC2A2Wchcu2WmQ==",
+      "requires": {
+        "@octokit/auth-oauth-device": "^4.0.0",
+        "@octokit/auth-oauth-user": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "@types/btoa-lite": "^1.0.0",
+        "btoa-lite": "^1.0.0",
+        "universal-user-agent": "^6.0.0"
+      }
+    },
+    "@octokit/auth-oauth-device": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-4.0.4.tgz",
+      "integrity": "sha512-Xl85BZYfqCMv+Uvz33nVVUjE7I/PVySNaK6dRRqlkvYcArSr9vRcZC9KVjXYObGRTCN6mISeYdakAZvWEN4+Jw==",
+      "requires": {
+        "@octokit/oauth-methods": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "universal-user-agent": "^6.0.0"
+      }
+    },
+    "@octokit/auth-oauth-user": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-2.1.1.tgz",
+      "integrity": "sha512-JgqnNNPf9CaWLxWm9uh2WgxcaVYhxBR09NVIPTiMU2dVZ3FObOHs3njBiLNw+zq84k+rEdm5Y7AsiASrZ84Apg==",
+      "requires": {
+        "@octokit/auth-oauth-device": "^4.0.0",
+        "@octokit/oauth-methods": "^2.0.0",
+        "@octokit/request": "^6.0.0",
+        "@octokit/types": "^9.0.0",
+        "btoa-lite": "^1.0.0",
+        "universal-user-agent": "^6.0.0"
+      }
+    },
     "@octokit/auth-token": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.3.tgz",
       "integrity": "sha512-/aFM2M4HVDBT/jjDBa84sJniv1t9Gm/rLkalaz9htOm+L+8JMj1k9w0CkUdcxNyNxZPlTxKPVko+m1VlM58ZVA==",
-      "dev": true,
       "requires": {
         "@octokit/types": "^9.0.0"
       }
     },
+    "@octokit/auth-unauthenticated": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-3.0.4.tgz",
+      "integrity": "sha512-AT74XGBylcLr4lmUp1s6mjSUgphGdlse21Qjtv5DzpX1YOl5FXKwvNcZWESdhyBbpDT8VkVyLFqa/7a7eqpPNw==",
+      "requires": {
+        "@octokit/request-error": "^3.0.0",
+        "@octokit/types": "^9.0.0"
+      }
+    },
     "@octokit/core": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz",
       "integrity": "sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg==",
-      "dev": true,
       "requires": {
         "@octokit/auth-token": "^3.0.0",
         "@octokit/graphql": "^5.0.0",
@@ -25983,7 +26405,6 @@
       "version": "7.0.5",
       "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.5.tgz",
       "integrity": "sha512-LG4o4HMY1Xoaec87IqQ41TQ+glvIeTKqfjkCEmt5AIwDZJwQeVZFIEYXrYY6yLwK+pAScb9Gj4q+Nz2qSw1roA==",
-      "dev": true,
       "requires": {
         "@octokit/types": "^9.0.0",
         "is-plain-object": "^5.0.0",
@@ -25994,24 +26415,54 @@
       "version": "5.0.5",
       "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.5.tgz",
       "integrity": "sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ==",
-      "dev": true,
       "requires": {
         "@octokit/request": "^6.0.0",
         "@octokit/types": "^9.0.0",
         "universal-user-agent": "^6.0.0"
       }
     },
+    "@octokit/oauth-app": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-4.2.1.tgz",
+      "integrity": "sha512-HtRWLzvAAizuFKJEoNlhWOktNNhfnLSXJXu2htt5+x2exhHibHupz/+1AAHU4xlBFtQLQh79OB/bQf+1GP5LBg==",
+      "requires": {
+        "@octokit/auth-oauth-app": "^5.0.0",
+        "@octokit/auth-oauth-user": "^2.0.0",
+        "@octokit/auth-unauthenticated": "^3.0.0",
+        "@octokit/core": "^4.0.0",
+        "@octokit/oauth-authorization-url": "^5.0.0",
+        "@octokit/oauth-methods": "^2.0.0",
+        "@types/aws-lambda": "^8.10.83",
+        "fromentries": "^1.3.1",
+        "universal-user-agent": "^6.0.0"
+      }
+    },
+    "@octokit/oauth-authorization-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-5.0.0.tgz",
+      "integrity": "sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg=="
+    },
+    "@octokit/oauth-methods": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-2.0.5.tgz",
+      "integrity": "sha512-yQP6B5gE3axNxuM3U9KqWs/ErAQ+WLPaPgC/7EjsZsQibkf8sjdAfF8/y/EJW+Dd05XQvadX4WhQZPMnO1SE1A==",
+      "requires": {
+        "@octokit/oauth-authorization-url": "^5.0.0",
+        "@octokit/request": "^6.2.3",
+        "@octokit/request-error": "^3.0.3",
+        "@octokit/types": "^9.0.0",
+        "btoa-lite": "^1.0.0"
+      }
+    },
     "@octokit/openapi-types": {
       "version": "17.1.1",
       "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.1.tgz",
-      "integrity": "sha512-/X7Gh/qWiWaooJmUnYD48SYy72fyrk2ceisOSe89JojK7r0j8YrTwYpDi76kI+c6QiqX1KSgdoBTMJvktsDkYw==",
-      "dev": true
+      "integrity": "sha512-/X7Gh/qWiWaooJmUnYD48SYy72fyrk2ceisOSe89JojK7r0j8YrTwYpDi76kI+c6QiqX1KSgdoBTMJvktsDkYw=="
     },
     "@octokit/plugin-paginate-rest": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz",
       "integrity": "sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw==",
-      "dev": true,
       "requires": {
         "@octokit/types": "^9.0.0"
       }
@@ -26027,17 +26478,33 @@
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.0.1.tgz",
       "integrity": "sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA==",
-      "dev": true,
       "requires": {
         "@octokit/types": "^9.0.0",
         "deprecation": "^2.3.1"
       }
     },
+    "@octokit/plugin-retry": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-4.1.3.tgz",
+      "integrity": "sha512-3YKBj7d0J/4mpEc4xzMociWsMNl5lZqrpAnYcW6mqiSGF3wFjU+c6GHih6GLClk31JNvKDr0x9jc5cfm7evkZg==",
+      "requires": {
+        "@octokit/types": "^9.0.0",
+        "bottleneck": "^2.15.3"
+      }
+    },
+    "@octokit/plugin-throttling": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-5.2.0.tgz",
+      "integrity": "sha512-qWYvVIS1aRC3hN4a0ICWUp51IolHIn7E4iri2AKjbExJaovMDvRtae4nGoxG5pP6+WATk7JZ5epOJobFbGYV1w==",
+      "requires": {
+        "@octokit/types": "^9.0.0",
+        "bottleneck": "^2.15.3"
+      }
+    },
     "@octokit/request": {
       "version": "6.2.3",
       "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.3.tgz",
       "integrity": "sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA==",
-      "dev": true,
       "requires": {
         "@octokit/endpoint": "^7.0.0",
         "@octokit/request-error": "^3.0.0",
@@ -26051,7 +26518,6 @@
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
       "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
-      "dev": true,
       "requires": {
         "@octokit/types": "^9.0.0",
         "deprecation": "^2.0.0",
@@ -26074,11 +26540,31 @@
       "version": "9.2.1",
       "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.1.tgz",
       "integrity": "sha512-Vx4keMiD/CAiwVFasLcH0xBSVbKIHebIZke9i7ZbUWGNN4vJFWSYH6Nvga7UY9NIJCGa6x3QG849XTbi5wYmkA==",
-      "dev": true,
       "requires": {
         "@octokit/openapi-types": "^17.1.1"
       }
     },
+    "@octokit/webhooks": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-10.9.1.tgz",
+      "integrity": "sha512-5NXU4VfsNOo2VSU/SrLrpPH2Z1ZVDOWFcET4EpnEBX1uh/v8Uz65UVuHIRx5TZiXhnWyRE9AO1PXHa+M/iWwZA==",
+      "requires": {
+        "@octokit/request-error": "^3.0.0",
+        "@octokit/webhooks-methods": "^3.0.0",
+        "@octokit/webhooks-types": "6.11.0",
+        "aggregate-error": "^3.1.0"
+      }
+    },
+    "@octokit/webhooks-methods": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-3.0.2.tgz",
+      "integrity": "sha512-Vlnv5WBscf07tyAvfDbp7pTkMZUwk7z7VwEF32x6HqI+55QRwBTcT+D7DDjZXtad/1dU9E32x0HmtDlF9VIRaQ=="
+    },
+    "@octokit/webhooks-types": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-6.11.0.tgz",
+      "integrity": "sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw=="
+    },
     "@pkgjs/parseargs": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -26780,6 +27266,16 @@
       "resolved": "https://registry.npmjs.org/@types/async-retry/-/async-retry-1.2.1.tgz",
       "integrity": "sha512-yMQ6CVgICWtyFNBqJT3zqOc+TnqqEPLo4nKJNPFwcialiylil38Ie6q1ENeFTjvaLOkVim9K5LisHgAKJWidGQ=="
     },
+    "@types/aws-lambda": {
+      "version": "8.10.115",
+      "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.115.tgz",
+      "integrity": "sha512-kCZuFXKLV3y8NjSoaD5+qKTpRWvPz3uh3W/u1uwlw3Mg+MtaStg1NWgjAwUXo/VJDb6n6KF1ljykFNlNwEJ53Q=="
+    },
+    "@types/btoa-lite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz",
+      "integrity": "sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg=="
+    },
     "@types/cacheable-request": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@@ -26909,6 +27405,14 @@
       "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
       "dev": true
     },
+    "@types/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/keyv": {
       "version": "3.1.4",
       "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@@ -29194,8 +29698,7 @@
     "before-after-hook": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
-      "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
-      "dev": true
+      "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
     },
     "big-integer": {
       "version": "1.6.51",
@@ -29302,6 +29805,11 @@
       "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
       "dev": true
     },
+    "bottleneck": {
+      "version": "2.19.5",
+      "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
+      "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
+    },
     "bplist-parser": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
@@ -29352,6 +29860,11 @@
         "update-browserslist-db": "^1.0.10"
       }
     },
+    "btoa-lite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
+      "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA=="
+    },
     "buffer": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -29382,6 +29895,11 @@
       "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
       "dev": true
     },
+    "buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "buffer-fill": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
@@ -30193,6 +30711,14 @@
         "parse-json": "^5.0.0",
         "path-type": "^4.0.0",
         "yaml": "^1.10.0"
+      },
+      "dependencies": {
+        "yaml": {
+          "version": "1.10.2",
+          "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+          "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+          "dev": true
+        }
       }
     },
     "crc-32": {
@@ -30721,8 +31247,7 @@
     "deprecation": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
-      "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
-      "dev": true
+      "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
     },
     "dequal": {
       "version": "2.0.3",
@@ -31207,6 +31732,14 @@
         "wcwidth": "^1.0.1"
       }
     },
+    "ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "edge-paths": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz",
@@ -32953,6 +33486,11 @@
         }
       }
     },
+    "fromentries": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
+      "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg=="
+    },
     "fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -34041,8 +34579,7 @@
     "is-plain-object": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
-      "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
-      "dev": true
+      "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
     },
     "is-reference": {
       "version": "3.0.1",
@@ -34692,6 +35229,17 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "jsonwebtoken": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+      "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+      "requires": {
+        "jws": "^3.2.2",
+        "lodash": "^4.17.21",
+        "ms": "^2.1.1",
+        "semver": "^7.3.8"
+      }
+    },
     "jszip": {
       "version": "3.10.1",
       "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -34748,6 +35296,25 @@
         }
       }
     },
+    "jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "requires": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "requires": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "keytar": {
       "version": "7.9.0",
       "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz",
@@ -36485,6 +37052,21 @@
       "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==",
       "dev": true
     },
+    "octokit": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/octokit/-/octokit-2.0.14.tgz",
+      "integrity": "sha512-z6cgZBFxirpFEQ1La8Lg83GCs5hOV2EPpkYYdjsGNbfQMv8qUGjq294MiRBCbZqLufviakGsPUxaNKe3JrPmsA==",
+      "requires": {
+        "@octokit/app": "^13.1.1",
+        "@octokit/core": "^4.0.4",
+        "@octokit/oauth-app": "^4.0.6",
+        "@octokit/plugin-paginate-rest": "^6.0.0",
+        "@octokit/plugin-rest-endpoint-methods": "^7.0.0",
+        "@octokit/plugin-retry": "^4.0.3",
+        "@octokit/plugin-throttling": "^5.0.0",
+        "@octokit/types": "^9.0.0"
+      }
+    },
     "on-exit-leak-free": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz",
@@ -37080,13 +37662,6 @@
       "requires": {
         "lilconfig": "^2.0.5",
         "yaml": "^2.1.1"
-      },
-      "dependencies": {
-        "yaml": {
-          "version": "2.2.2",
-          "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
-          "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="
-        }
       }
     },
     "postcss-modules": {
@@ -39759,11 +40334,19 @@
         "unist-util-is": "^5.0.0"
       }
     },
+    "universal-github-app-jwt": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz",
+      "integrity": "sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==",
+      "requires": {
+        "@types/jsonwebtoken": "^9.0.0",
+        "jsonwebtoken": "^9.0.0"
+      }
+    },
     "universal-user-agent": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
-      "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
-      "dev": true
+      "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
     },
     "universalify": {
       "version": "0.1.2",
@@ -41029,10 +41612,9 @@
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
     },
     "yaml": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
-      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
-      "dev": true
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
+      "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="
     },
     "yargs": {
       "version": "17.7.2",
diff --git a/package.json b/package.json
index d840ffd5..d5875f02 100644
--- a/package.json
+++ b/package.json
@@ -143,6 +143,12 @@
       {
         "command": "runme.file.openInRunme",
         "title": "Open in Runme"
+      },
+      {
+        "command": "runme.authenticateWithGitHub",
+        "title": "Authenticate Runme with GitHub",
+        "category": "Runme",
+        "description": "Give access to Runme to interact with GitHub API"
       }
     ],
     "menus": {
@@ -366,6 +372,16 @@
             "type": "boolean",
             "default": true,
             "markdownDescription": "Enable CodeLens support in markdown documents"
+          },
+          "runme.env.workspaceFileOrder": {
+            "type": "array",
+            "default": [ ".env.local", ".env" ],
+            "markdownDescription": "List of workspace environment files to parse in order; variables defined in later files take priority"
+          },
+          "runme.env.loadWorkspaceFiles": {
+            "type": "boolean",
+            "default": true,
+            "markdownDescription": "Load environment files from workspace into all scripts"
           }
         }
       }
@@ -545,10 +561,12 @@
     "@vscode/webview-ui-toolkit": "^1.2.1",
     "clipboardy": "^3.0.0",
     "detect-port": "^1.5.1",
+    "dotenv": "^16.0.3",
     "filenamify": "^6.0.0",
     "get-port": "^6.1.2",
     "got": "^12.5.3",
     "lit": "^2.6.1",
+    "octokit": "^2.0.14",
     "preact": "^10.11.3",
     "undici": "^5.16.0",
     "uuid": "^9.0.0",
@@ -561,6 +579,7 @@
     "xterm-addon-unicode11": "^0.5.0",
     "xterm-addon-web-links": "^0.8.0",
     "xterm-headless": "^5.1.0",
+    "yaml": "^2.2.2",
     "yargs": "^17.6.2",
     "zod": "^3.20.2"
   }
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index affab2be..5abcecdd 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -2,7 +2,7 @@ import path from 'node:path'
 
 import {
   NotebookCell, Uri, window, env, NotebookDocument, TextDocument, ViewColumn,
-  workspace, NotebookData, commands, NotebookCellData, NotebookCellKind, ExtensionContext
+  workspace, NotebookData, commands, NotebookCellData, NotebookCellKind, ExtensionContext, authentication
 } from 'vscode'
 import { v4 as uuidv4 } from 'uuid'
 
@@ -166,3 +166,11 @@ export async function tryIt (context: ExtensionContext) {
 export async function openFileInRunme (uri: Uri, selection?: Uri[]) {
   await Promise.all((selection ?? [uri]).map(openFileAsRunmeNotebook))
 }
+
+export async function authenticateWithGitHub() {
+  try {
+    await authentication.getSession('github', ['repo'], { createIfNone: true })
+  } catch (error) {
+    window.showErrorMessage('Failed to authenticate with GitHub')
+  }
+}
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 43354058..8f96c27d 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -20,7 +20,14 @@ import { ClientMessages } from '../../constants'
 import { ClientMessage } from '../../types'
 import { PLATFORM_OS } from '../constants'
 import { IRunner, IRunnerEnvironment, RunProgramExecution } from '../runner'
-import { getAnnotations, getCellRunmeId, getCmdShellSeq, getTerminalByCell, prepareCmdSeq } from '../utils'
+import {
+  getAnnotations,
+  getCellRunmeId,
+  getCmdShellSeq,
+  getTerminalByCell,
+  getWorkspaceEnvs,
+  prepareCmdSeq
+} from '../utils'
 import { postClientMessage } from '../../utils/messaging'
 import { isNotebookTerminalEnabledForCell } from '../../utils/configuration'
 import { Kernel } from '../kernel'
@@ -73,12 +80,13 @@ export async function executeRunner(
 
   const RUNME_ID = getCellRunmeId(exec.cell)
 
-  let cellText = exec.cell.document.getText()
-
   const envs: Record<string, string> = {
-    RUNME_ID
+    RUNME_ID,
+    ...await getWorkspaceEnvs(runningCell.uri),
   }
 
+  let cellText = exec.cell.document.getText()
+
   const commands = await parseCommandSeq(cellText, promptEnv, prepareCmdSeq)
   if(!commands) { return false }
 
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 6fb1b672..0c86090c 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -23,7 +23,8 @@ import {
   welcome,
   tryIt,
   openFileInRunme,
-  openIntegratedTerminal
+  openIntegratedTerminal,
+  authenticateWithGitHub
 } from './commands'
 import { WasmSerializer, GrpcSerializer } from './serializer'
 import { RunmeLauncherProvider } from './provider/launcher'
@@ -45,7 +46,7 @@ export class RunmeExtension {
       maxNumberOfIntents: 10,
     }, !grpcServer, grpcRunner)
 
-    let runner: IRunner|undefined
+    let runner: IRunner | undefined
     if (grpcRunner) {
       runner = new GrpcRunner(server)
       kernel.useRunner(runner)
@@ -137,7 +138,7 @@ export class RunmeExtension {
       window.registerTreeDataProvider('runme.launcher', treeViewer),
       RunmeExtension.registerCommand('runme.collapseTreeView', treeViewer.collapseAll.bind(treeViewer)),
       RunmeExtension.registerCommand('runme.expandTreeView', treeViewer.expandAll.bind(treeViewer)),
-
+      RunmeExtension.registerCommand('runme.authenticateWithGitHub', authenticateWithGitHub),
       /**
        * Uri handler
        */
diff --git a/src/extension/provider/runmeTask.ts b/src/extension/provider/runmeTask.ts
index 19256f20..1c27d907 100644
--- a/src/extension/provider/runmeTask.ts
+++ b/src/extension/provider/runmeTask.ts
@@ -18,7 +18,7 @@ import {
   NotebookCellData,
 } from 'vscode'
 
-import { getAnnotations, prepareCmdSeq, processEnviron } from '../utils'
+import { getAnnotations, getWorkspaceEnvs, prepareCmdSeq } from '../utils'
 import { Serializer, RunmeTaskDefinition } from '../../types'
 import { SerializerBase } from '../serializer'
 import type { IRunner, IRunnerEnvironment, RunProgramOptions } from '../runner'
@@ -118,6 +118,12 @@ export class RunmeTaskProvider implements TaskProvider {
         const cellContent = 'value' in cell ? cell.value : cell.document.getText()
         const commands = await parseCommandSeq(cellContent, undefined, prepareCmdSeq)
 
+        const envs: Record<string, string> = {
+          ...await getWorkspaceEnvs(Uri.file(filePath))
+        }
+
+        if (!environment) { Object.assign(envs, process.env) }
+
         const runOpts: RunProgramOptions = {
           programName: getShellPath() ?? 'sh',
           exec: {
@@ -128,10 +134,7 @@ export class RunmeTaskProvider implements TaskProvider {
           environment,
           tty: interactive,
           convertEol: true,
-        }
-
-        if (!environment) {
-          runOpts.envs = processEnviron()
+          envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`),
         }
 
         const program = await runner.createProgramSession(runOpts)
diff --git a/src/extension/services/github.ts b/src/extension/services/github.ts
new file mode 100644
index 00000000..dc61dea1
--- /dev/null
+++ b/src/extension/services/github.ts
@@ -0,0 +1,44 @@
+import { Octokit } from 'octokit'
+import { parse } from 'yaml'
+
+import { IWorkflowDispatchOptions, IWorkflowYamlContentRequest } from './types'
+
+export class GitHubService {
+    private octokit: Octokit
+    constructor(accessToken: string) {
+        this.octokit = new Octokit({ auth: accessToken })
+    }
+
+    /**
+     *  Dispatch a workflow
+     */
+    async createWorkflowDispatch(dispatchOptions: IWorkflowDispatchOptions): Promise<void> {
+        const { owner, repo, workflow_id, ref, inputs } = dispatchOptions
+        await this.octokit.rest.actions.createWorkflowDispatch({
+            owner,
+            repo,
+            workflow_id,
+            ref,
+            inputs
+        })
+
+    }
+
+    /**
+     * Download the YAML content from a workflow file
+     * @returns A workflow YAML file in JSON format
+     */
+    async getWorkflowYamlFile(options: IWorkflowYamlContentRequest): Promise<string> {
+        const { owner, repo, name } = options
+        const workflow = await this.octokit.rest.repos.getContent({
+            owner,
+            repo,
+            path: `.github/workflows/${name}.yml`
+        })
+        if (!('content' in workflow.data)) {
+            throw new Error('Failed to get workflow file content')
+        }
+        const decodedContent = Buffer.from((workflow.data as any).content, 'base64').toString()
+        return parse(decodedContent)
+    }
+}
\ No newline at end of file
diff --git a/src/extension/services/index.ts b/src/extension/services/index.ts
new file mode 100644
index 00000000..833a803d
--- /dev/null
+++ b/src/extension/services/index.ts
@@ -0,0 +1,2 @@
+export { GitHubService } from './github'
+export * as types from './types'
\ No newline at end of file
diff --git a/src/extension/services/types.ts b/src/extension/services/types.ts
new file mode 100644
index 00000000..4113d807
--- /dev/null
+++ b/src/extension/services/types.ts
@@ -0,0 +1,13 @@
+export interface IWorkflowDispatchOptions {
+    owner: string
+    repo: string
+    workflow_id: string
+    ref: string
+    inputs: Record<string, string>
+}
+
+export interface IWorkflowYamlContentRequest {
+    owner: string
+    repo: string
+    name: string
+}
\ No newline at end of file
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index fc959ef9..c36bf41d 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -12,14 +12,16 @@ import vscode, {
   NotebookCellExecution,
   NotebookCellOutput,
   commands,
+  WorkspaceFolder,
 } from 'vscode'
 import { v5 as uuidv5 } from 'uuid'
 import getPort from 'get-port'
+import dotenv from 'dotenv'
 
 import { CellAnnotations, CellAnnotationsErrorResult, RunmeTerminal, Serializer } from '../types'
 import { SafeCellAnnotationsSchema, CellAnnotationsSchema } from '../schema'
 import { SERVER_ADDRESS } from '../constants'
-import { getPortNumber } from '../utils/configuration'
+import { getEnvLoadWorkspaceFiles, getEnvWorkspaceFileOrder, getPortNumber } from '../utils/configuration'
 
 import type executor from './executors'
 import { Kernel } from './kernel'
@@ -360,3 +362,54 @@ export function isWindows(): boolean {
 export async function openFileAsRunmeNotebook(uri: Uri): Promise<void> {
   return await commands.executeCommand('vscode.openWith', uri, Kernel.type)
 }
+
+/**
+ * Replacement for `workspace.getWorkspaceFolder`, which has issues
+ *
+ * If `uri` is undefined, this returns the first (default) workspace folder
+ */
+export function getWorkspaceFolder(uri?: Uri): WorkspaceFolder | undefined {
+  if (!uri) {
+    return workspace.workspaceFolders?.[0]
+  }
+
+  let testPath = uri.fsPath
+  do {
+    for (const workspaceFolder of workspace.workspaceFolders ?? [ ]) {
+      if (testPath === workspaceFolder.uri.fsPath) {
+        return workspaceFolder
+      }
+    }
+
+    testPath = path.dirname(testPath)
+  } while (testPath !== path.dirname(testPath))
+}
+
+export async function getWorkspaceEnvs(uri?: Uri): Promise<Record<string, string>> {
+  const res: Record<string, string> = { }
+  const workspaceFolder = getWorkspaceFolder(uri)
+
+  if (!workspaceFolder || !getEnvLoadWorkspaceFiles()) { return res }
+
+  const envFiles = getEnvWorkspaceFileOrder()
+
+  const envs = await Promise.all(
+    envFiles.map(async (fileName) => {
+      const dotEnvFile = Uri.joinPath(workspaceFolder.uri, fileName)
+
+      return await workspace.fs.stat(dotEnvFile)
+        .then(async (f) => {
+          if (f.type !== FileType.File) { return { } }
+
+          const bytes = await workspace.fs.readFile(dotEnvFile)
+          return dotenv.parse(Buffer.from(bytes))
+        }, () => { return { } })
+    })
+  )
+
+  for (const env of envs) {
+    Object.assign(res, env)
+  }
+
+  return res
+}
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts
index 55a440fe..c8a16efa 100644
--- a/src/utils/configuration.ts
+++ b/src/utils/configuration.ts
@@ -9,9 +9,12 @@ import { getAnnotations, isWindows } from '../extension/utils'
 import { SERVER_PORT } from '../constants'
 
 const SERVER_SECTION_NAME = 'runme.server'
-const TERMINAL_SECTION_NAME= 'runme.terminal'
-const CODELENS_SECTION_NAME= 'runme.codelens'
+const TERMINAL_SECTION_NAME = 'runme.terminal'
+const CODELENS_SECTION_NAME = 'runme.codelens'
+const ENV_SECTION_NAME = 'runme.env'
+
 export const DEFAULT_TLS_DIR = path.join(os.tmpdir(), 'runme', uuidv4(), 'tls')
+const DEFAULT_WORKSPACE_FILE_ORDER = ['.env.local', '.env']
 
 type NotebookTerminalValue = keyof typeof configurationSchema.notebookTerminal
 
@@ -45,6 +48,10 @@ const configurationSchema = {
     },
     codelens: {
       enable: z.boolean().default(true)
+    },
+    env: {
+      workspaceFileOrder: z.array(z.string()).default(DEFAULT_WORKSPACE_FILE_ORDER),
+      loadWorkspaceFiles: z.boolean().default(true),
     }
 }
 
@@ -78,6 +85,16 @@ const getCodeLensConfigurationValue = <T>(configName: keyof typeof configuration
   return defaultValue
 }
 
+const getEnvConfigurationValue = <T>(configName: keyof typeof configurationSchema.env, defaultValue: T) => {
+  const configurationSection = workspace.getConfiguration(ENV_SECTION_NAME)
+  const configurationValue = configurationSection.get<T>(configName)!
+  const parseResult = configurationSchema.env[configName].safeParse(configurationValue)
+  if (parseResult.success) {
+      return parseResult.data as T
+  }
+  return defaultValue
+}
+
 const getPortNumber = (): number => {
   return SERVER_PORT
 }
@@ -160,6 +177,14 @@ const registerExtensionEnvironmentVariables = (context: ExtensionContext): void
   )
 }
 
+const getEnvWorkspaceFileOrder = (): string[] => {
+  return getEnvConfigurationValue('workspaceFileOrder', DEFAULT_WORKSPACE_FILE_ORDER)
+}
+
+const getEnvLoadWorkspaceFiles = (): boolean => {
+  return getEnvConfigurationValue('loadWorkspaceFiles', true)
+}
+
 export {
     getPortNumber,
     getBinaryPath,
@@ -175,4 +200,6 @@ export {
     getCodeLensEnabled,
     registerExtensionEnvironmentVariables,
     getCustomServerAddress,
+    getEnvWorkspaceFileOrder,
+    getEnvLoadWorkspaceFiles,
 }


Test patch:
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index 05b7e686..a96edf12 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -63,7 +63,7 @@ test('initializes all providers', async () => {
   await ext.initialize(context)
   expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(6)
   expect(workspace.registerNotebookSerializer).toBeCalledTimes(1)
-  expect(commands.registerCommand).toBeCalledTimes(20)
+  expect(commands.registerCommand).toBeCalledTimes(21)
   expect(window.registerTreeDataProvider).toBeCalledTimes(1)
   expect(window.registerUriHandler).toBeCalledTimes(1)
 
diff --git a/tests/extension/utils.test.ts b/tests/extension/utils.test.ts
index 6f0afb4b..03077467 100644
--- a/tests/extension/utils.test.ts
+++ b/tests/extension/utils.test.ts
@@ -1,4 +1,4 @@
-import vscode, { commands, workspace } from 'vscode'
+import vscode, { FileType, Uri, commands, workspace } from 'vscode'
 import { expect, vi, test, beforeEach, beforeAll, afterAll, suite } from 'vitest'
 import { v4 } from 'uuid'
 
@@ -15,6 +15,8 @@ import {
   getGrpcHost,
   prepareCmdSeq,
   openFileAsRunmeNotebook,
+  getWorkspaceFolder,
+  getWorkspaceEnvs,
 } from '../../src/extension/utils'
 import { ENV_STORE, DEFAULT_ENV } from '../../src/extension/constants'
 import { CellAnnotations } from '../../src/types'
@@ -24,11 +26,13 @@ vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
 
 vi.mock('vscode', async () => {
   const { v4 } = await vi.importActual('uuid') as typeof import('uuid')
+  const mocked = await vi.importActual('../../__mocks__/vscode') as any
 
   const uuid1 = v4()
   const uuid2 = v4()
 
   return ({
+    ...mocked,
     default: {
       window: {
         terminals: [
@@ -37,7 +41,9 @@ vi.mock('vscode', async () => {
         ]
       },
       workspace: {
-        getConfiguration: vi.fn()
+        getConfiguration: vi.fn(),
+        workspaceFolders: [],
+        fs: mocked.workspace.fs,
       },
       env: {
         machineId: 'test-machine-id'
@@ -48,7 +54,9 @@ vi.mock('vscode', async () => {
       }
     },
     workspace: {
-      getConfiguration: vi.fn()
+      getConfiguration: vi.fn(),
+      fs: mocked.workspace.fs,
+      workspaceFolders: [],
     },
     commands: {
       executeCommand: vi.fn()
@@ -334,3 +342,66 @@ suite('openFileAsRunmeNotebook', () => {
     expect(commands.executeCommand).toBeCalledWith('vscode.openWith', uri, 'runme')
   })
 })
+
+suite('getWorkspaceFolder', () => {
+  beforeEach(() => {
+    // @ts-ignore
+    workspace.workspaceFolders = []
+  })
+
+  test('is empty if no workspace folder', async () => {
+    expect(await getWorkspaceEnvs()).toStrictEqual({ })
+  })
+
+  test('identifies correct workspace', () => {
+    const workspaceFolder1 = { uri: Uri.file('/foo/bar') }
+    const workspaceFolder2 = { uri: Uri.file('/bar/foo') }
+    const workspaceFolder3 = { uri: Uri.file('/bar/baz') }
+
+    // @ts-ignore
+    workspace.workspaceFolders = [
+      workspaceFolder1,
+      workspaceFolder2,
+      workspaceFolder3,
+    ]
+
+    expect(getWorkspaceFolder()).toBe(workspaceFolder1)
+    expect(getWorkspaceFolder(Uri.file('/bar/baz/.env'))).toBe(workspaceFolder3)
+  })
+})
+
+suite('getWorkspaceEnvs', () => {
+  beforeEach(() => {
+    // @ts-ignore
+    workspace.workspaceFolders = []
+
+    vi.mocked(workspace.fs.stat).mockReset()
+    vi.mocked(workspace.fs.readFile).mockReset()
+  })
+
+  test('identifies env files', async () => {
+    const workspaceFolder = { uri: Uri.file('/foo/bar') }
+
+    vi.mocked(workspace.fs.stat).mockResolvedValue({ type: FileType.File } as any)
+    vi.mocked(workspace.fs.readFile).mockImplementation(async (uri) => {
+      if (uri.fsPath === '/foo/bar/.env') {
+        return Buffer.from('SECRET_1=secret1_override\nSECRET_2=secret2\n')
+      } else if (uri.fsPath === '/foo/bar/.env.local') {
+        return Buffer.from('SECRET_1=secret1\nSECRET_3=secret3')
+      }
+
+      return Buffer.from([])
+    })
+
+    // @ts-ignore
+    workspace.workspaceFolders = [
+      workspaceFolder,
+    ]
+
+    expect(await getWorkspaceEnvs(workspaceFolder.uri)).toStrictEqual({
+      SECRET_1: 'secret1_override',
+      SECRET_2: 'secret2',
+      SECRET_3: 'secret3',
+    })
+  })
+})