Merge pull request #49 from andrepimenta/feature/mcp-support

Feature/mcp support
This commit is contained in:
Andre Pimenta
2025-07-14 22:12:13 +01:00
committed by GitHub
10 changed files with 15492 additions and 1074 deletions

View File

@@ -9,4 +9,5 @@ vsc-extension-quickstart.md
**/*.map
**/*.ts
**/.vscode-test.*
backup
backup
.claude

13954
mcp-permissions.js Normal file

File diff suppressed because one or more lines are too long

719
package-lock.json generated
View File

@@ -1,15 +1,14 @@
{
"name": "claude-code-chat",
"version": "0.1.3",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-code-chat",
"version": "0.1.3",
"version": "1.0.0",
"license": "SEE LICENSE IN LICENSE",
"devDependencies": {
"@modelcontextprotocol/sdk": "^1.15.0",
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/vscode": "^1.94.0",
@@ -19,8 +18,7 @@
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.5.0",
"eslint": "^9.25.1",
"typescript": "^5.8.3",
"zod": "^3.25.76"
"typescript": "^5.8.3"
},
"engines": {
"vscode": "^1.94.0"
@@ -558,30 +556,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.15.0.tgz",
"integrity": "sha512-67hnl/ROKdb03Vuu0YOr+baKTvf1/5YBHBm9KnZdjdAh8hjt4FRCPD5ucwxGB237sBpzlqQsLy1PFu7z/ekZ9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.6",
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.0",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.24.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1539,43 +1513,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -1834,27 +1771,6 @@
"node": ">= 6"
}
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1952,16 +1868,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/c8": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz",
@@ -2317,50 +2223,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-disposition/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2368,26 +2230,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -2395,20 +2237,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2565,16 +2393,6 @@
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@@ -2695,13 +2513,6 @@
"url": "https://bevry.me/fund"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true,
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -2709,16 +2520,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
@@ -2832,13 +2633,6 @@
"node": ">=6"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true,
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -3093,39 +2887,6 @@
"node": ">=0.10.0"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/eventsource": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eventsource-parser": "^3.0.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/eventsource-parser": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
"integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -3136,88 +2897,6 @@
"node": ">=6"
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/express/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3317,24 +2996,6 @@
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -3416,26 +3077,6 @@
"node": ">= 6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -3769,33 +3410,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -3936,16 +3550,6 @@
"dev": true,
"optional": true
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -4064,13 +3668,6 @@
"node": ">=8"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"dev": true,
"license": "MIT"
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -4555,29 +4152,6 @@
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4921,16 +4495,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-abi": {
"version": "3.75.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
@@ -5012,16 +4576,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -5034,19 +4588,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5386,16 +4927,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5443,16 +4974,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/path-type": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz",
@@ -5490,16 +5011,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pkce-challenge": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
@@ -5553,20 +5064,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -5643,32 +5140,6 @@
"safe-buffer": "^5.1.0"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -5823,23 +5294,6 @@
"node": ">=0.10.0"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/run-applescript": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz",
@@ -5929,52 +5383,6 @@
"node": ">=10"
}
},
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/send/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/send/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@@ -5985,22 +5393,6 @@
"randombytes": "^2.1.0"
}
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -6008,13 +5400,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"dev": true,
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -6237,16 +5622,6 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
@@ -6688,16 +6063,6 @@
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -6764,44 +6129,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"dev": true,
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/type-is/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/type-is/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typed-rest-client": {
"version": "1.8.11",
"resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz",
@@ -6876,16 +6203,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6943,16 +6260,6 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/version-range": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/version-range/-/version-range-4.14.0.tgz",
@@ -7280,26 +6587,6 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.24.1"
}
}
}
}

View File

@@ -2,7 +2,7 @@
"name": "claude-code-chat",
"displayName": "Claude Code Chat",
"description": "Beautiful Claude Code Chat Interface for VS Code",
"version": "0.1.3",
"version": "1.0.0",
"publisher": "AndrePimenta",
"author": "Andre Pimenta",
"repository": {
@@ -207,8 +207,6 @@
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.5.0",
"eslint": "^9.25.1",
"typescript": "^5.8.3",
"@modelcontextprotocol/sdk": "^1.15.0",
"zod": "^3.25.76"
"typescript": "^5.8.3"
}
}

View File

@@ -86,6 +86,7 @@ class ClaudeChatProvider {
private _webview: vscode.Webview | undefined;
private _webviewView: vscode.WebviewView | undefined;
private _disposables: vscode.Disposable[] = [];
private _messageHandlerDisposable: vscode.Disposable | undefined;
private _totalCost: number = 0;
private _totalTokensInput: number = 0;
private _totalTokensOutput: number = 0;
@@ -286,11 +287,35 @@ class ClaudeChatProvider {
case 'addPermission':
this._addPermission(message.toolName, message.command);
return;
case 'loadMCPServers':
this._loadMCPServers();
return;
case 'saveMCPServer':
this._saveMCPServer(message.name, message.config);
return;
case 'deleteMCPServer':
this._deleteMCPServer(message.name);
return;
case 'getCustomSnippets':
this._sendCustomSnippets();
return;
case 'saveCustomSnippet':
this._saveCustomSnippet(message.snippet);
return;
case 'deleteCustomSnippet':
this._deleteCustomSnippet(message.snippetId);
return;
}
}
private _setupWebviewMessageHandler(webview: vscode.Webview) {
webview.onDidReceiveMessage(
// Dispose of any existing message handler
if (this._messageHandlerDisposable) {
this._messageHandlerDisposable.dispose();
}
// Set up new message handler
this._messageHandlerDisposable = webview.onDidReceiveMessage(
message => this._handleWebviewMessage(message),
null,
this._disposables
@@ -342,6 +367,7 @@ class ClaudeChatProvider {
// Only reinitialize if we have a webview (sidebar)
if (this._webview) {
this._initializeWebview();
// Set up message handler for the webview
this._setupWebviewMessageHandler(this._webview);
}
}
@@ -427,9 +453,9 @@ class ClaudeChatProvider {
// Add MCP configuration for permissions
const mcpConfigPath = this.getMCPConfigPath();
if (mcpConfigPath) {
args.push('--mcp-config', mcpConfigPath);
args.push('--allowedTools', 'mcp__permissions__approval_prompt');
args.push('--permission-prompt-tool', 'mcp__permissions__approval_prompt');
args.push('--mcp-config', this.convertToWSLPath(mcpConfigPath));
args.push('--allowedTools', 'mcp__claude-code-chat-permissions__approval_prompt');
args.push('--permission-prompt-tool', 'mcp__claude-code-chat-permissions__approval_prompt');
}
}
@@ -456,9 +482,13 @@ class ClaudeChatProvider {
let claudeProcess: cp.ChildProcess;
if (wslEnabled) {
// Use WSL
// Use WSL with bash -ic for proper environment loading
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, nodePath, '--no-warnings', '--enable-source-maps', claudePath, ...args], {
const wslCommand = `"${nodePath}" --no-warnings --enable-source-maps "${claudePath}" ${args.join(' ')}`;
console.log('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand].join(" "))
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand], {
cwd: cwd,
stdio: ['pipe', 'pipe', 'pipe'],
env: {
@@ -653,6 +683,12 @@ class ClaudeChatProvider {
for (const content of jsonData.message.content) {
if (content.type === 'tool_result') {
let resultContent = content.content || 'Tool executed successfully';
// Stringify if content is an object or array
if (typeof resultContent === 'object' && resultContent !== null) {
resultContent = JSON.stringify(resultContent, null, 2);
}
const isError = content.is_error || false;
// Find the last tool use to get the tool name
@@ -782,6 +818,9 @@ class ClaudeChatProvider {
}
public newSessionOnConfigChange() {
// Reinitialize MCP config with new WSL paths
this._initializeMCPConfig();
// Start a new session due to configuration change
this._newSession();
@@ -1017,27 +1056,41 @@ class ClaudeChatProvider {
console.log(`Created MCP config directory at: ${mcpConfigDir}`);
}
// Create mcp-servers.json with correct path to compiled MCP permissions server
// Create or update mcp-servers.json with permissions server, preserving existing servers
const mcpConfigPath = path.join(mcpConfigDir, 'mcp-servers.json');
const mcpPermissionsPath = path.join(this._extensionUri.fsPath, 'out', 'permissions', 'mcp-permissions.js');
const permissionRequestsPath = path.join(storagePath, 'permission-requests');
const mcpPermissionsPath = this.convertToWSLPath(path.join(this._extensionUri.fsPath, 'mcp-permissions.js'));
const permissionRequestsPath = this.convertToWSLPath(path.join(storagePath, 'permission-requests'));
const mcpConfig = {
mcpServers: {
permissions: {
command: 'node',
args: [mcpPermissionsPath],
env: {
CLAUDE_PERMISSIONS_PATH: permissionRequestsPath
}
}
// Load existing config or create new one
let mcpConfig: any = { mcpServers: {} };
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
try {
const existingContent = await vscode.workspace.fs.readFile(mcpConfigUri);
mcpConfig = JSON.parse(new TextDecoder().decode(existingContent));
console.log('Loaded existing MCP config, preserving user servers');
} catch {
console.log('No existing MCP config found, creating new one');
}
// Ensure mcpServers exists
if (!mcpConfig.mcpServers) {
mcpConfig.mcpServers = {};
}
// Add or update the permissions server entry
mcpConfig.mcpServers['claude-code-chat-permissions'] = {
command: 'node',
args: [mcpPermissionsPath],
env: {
CLAUDE_PERMISSIONS_PATH: permissionRequestsPath
}
};
const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2));
await vscode.workspace.fs.writeFile(vscode.Uri.file(mcpConfigPath), configContent);
await vscode.workspace.fs.writeFile(mcpConfigUri, configContent);
console.log(`Created MCP config at: ${mcpConfigPath}`);
console.log(`Updated MCP config at: ${mcpConfigPath}`);
} catch (error: any) {
console.error('Failed to initialize MCP config:', error.message);
}
@@ -1049,7 +1102,7 @@ class ClaudeChatProvider {
if (!storagePath) {return;}
// Create permission requests directory
this._permissionRequestsPath = path.join(storagePath, 'permission-requests');
this._permissionRequestsPath = path.join(path.join(storagePath, 'permission-requests'));
try {
await vscode.workspace.fs.stat(vscode.Uri.file(this._permissionRequestsPath));
} catch {
@@ -1057,12 +1110,15 @@ class ClaudeChatProvider {
console.log(`Created permission requests directory at: ${this._permissionRequestsPath}`);
}
console.log("DIRECTORY-----", this._permissionRequestsPath)
// Set up file watcher for *.request files
this._permissionWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this._permissionRequestsPath, '*.request')
);
this._permissionWatcher.onDidCreate(async (uri) => {
console.log("----file", uri)
// Only handle file scheme URIs, ignore vscode-userdata scheme
if (uri.scheme === 'file') {
await this._handlePermissionRequest(uri);
@@ -1451,10 +1507,208 @@ class ClaudeChatProvider {
}
}
private async _loadMCPServers(): Promise<void> {
try {
const mcpConfigPath = this.getMCPConfigPath();
if (!mcpConfigPath) {
this._postMessage({ type: 'mcpServers', data: {} });
return;
}
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
let mcpConfig: any = { mcpServers: {} };
try {
const content = await vscode.workspace.fs.readFile(mcpConfigUri);
mcpConfig = JSON.parse(new TextDecoder().decode(content));
} catch (error) {
console.log('MCP config file not found or error reading:', error);
// File doesn't exist, return empty servers
}
// Filter out internal servers before sending to UI
const filteredServers = Object.fromEntries(
Object.entries(mcpConfig.mcpServers || {}).filter(([name]) => name !== 'claude-code-chat-permissions')
);
this._postMessage({ type: 'mcpServers', data: filteredServers });
} catch (error) {
console.error('Error loading MCP servers:', error);
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } });
}
}
private async _saveMCPServer(name: string, config: any): Promise<void> {
try {
const mcpConfigPath = this.getMCPConfigPath();
if (!mcpConfigPath) {
this._postMessage({ type: 'mcpServerError', data: { error: 'Storage path not available' } });
return;
}
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
let mcpConfig: any = { mcpServers: {} };
// Load existing config
try {
const content = await vscode.workspace.fs.readFile(mcpConfigUri);
mcpConfig = JSON.parse(new TextDecoder().decode(content));
} catch {
// File doesn't exist, use default structure
}
// Ensure mcpServers exists
if (!mcpConfig.mcpServers) {
mcpConfig.mcpServers = {};
}
// Add/update the server
mcpConfig.mcpServers[name] = config;
// Ensure directory exists
const mcpDir = vscode.Uri.file(path.dirname(mcpConfigPath));
try {
await vscode.workspace.fs.stat(mcpDir);
} catch {
await vscode.workspace.fs.createDirectory(mcpDir);
}
// Save the config
const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2));
await vscode.workspace.fs.writeFile(mcpConfigUri, configContent);
this._postMessage({ type: 'mcpServerSaved', data: { name } });
console.log(`Saved MCP server: ${name}`);
} catch (error) {
console.error('Error saving MCP server:', error);
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to save MCP server' } });
}
}
private async _deleteMCPServer(name: string): Promise<void> {
try {
const mcpConfigPath = this.getMCPConfigPath();
if (!mcpConfigPath) {
this._postMessage({ type: 'mcpServerError', data: { error: 'Storage path not available' } });
return;
}
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
let mcpConfig: any = { mcpServers: {} };
// Load existing config
try {
const content = await vscode.workspace.fs.readFile(mcpConfigUri);
mcpConfig = JSON.parse(new TextDecoder().decode(content));
} catch {
// File doesn't exist, nothing to delete
this._postMessage({ type: 'mcpServerError', data: { error: 'MCP config file not found' } });
return;
}
// Delete the server
if (mcpConfig.mcpServers && mcpConfig.mcpServers[name]) {
delete mcpConfig.mcpServers[name];
// Save the updated config
const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2));
await vscode.workspace.fs.writeFile(mcpConfigUri, configContent);
this._postMessage({ type: 'mcpServerDeleted', data: { name } });
console.log(`Deleted MCP server: ${name}`);
} else {
this._postMessage({ type: 'mcpServerError', data: { error: `Server '${name}' not found` } });
}
} catch (error) {
console.error('Error deleting MCP server:', error);
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to delete MCP server' } });
}
}
private async _sendCustomSnippets(): Promise<void> {
try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {});
this._postMessage({
type: 'customSnippetsData',
data: customSnippets
});
} catch (error) {
console.error('Error loading custom snippets:', error);
this._postMessage({
type: 'customSnippetsData',
data: {}
});
}
}
private async _saveCustomSnippet(snippet: any): Promise<void> {
try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {});
customSnippets[snippet.id] = snippet;
await this._context.globalState.update('customPromptSnippets', customSnippets);
this._postMessage({
type: 'customSnippetSaved',
data: { snippet }
});
console.log('Saved custom snippet:', snippet.name);
} catch (error) {
console.error('Error saving custom snippet:', error);
this._postMessage({
type: 'error',
data: 'Failed to save custom snippet'
});
}
}
private async _deleteCustomSnippet(snippetId: string): Promise<void> {
try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {});
if (customSnippets[snippetId]) {
delete customSnippets[snippetId];
await this._context.globalState.update('customPromptSnippets', customSnippets);
this._postMessage({
type: 'customSnippetDeleted',
data: { snippetId }
});
console.log('Deleted custom snippet:', snippetId);
} else {
this._postMessage({
type: 'error',
data: 'Snippet not found'
});
}
} catch (error) {
console.error('Error deleting custom snippet:', error);
this._postMessage({
type: 'error',
data: 'Failed to delete custom snippet'
});
}
}
private convertToWSLPath(windowsPath: string): string {
const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false);
if (wslEnabled && windowsPath.match(/^[a-zA-Z]:/)) {
// Convert C:\Users\... to /mnt/c/Users/...
return windowsPath.replace(/^([a-zA-Z]):/, '/mnt/$1').toLowerCase().replace(/\\/g, '/');
}
return windowsPath;
}
public getMCPConfigPath(): string | undefined {
const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return undefined;}
return path.join(storagePath, 'mcp', 'mcp-servers.json');
const configPath = path.join(storagePath, 'mcp', 'mcp-servers.json');
return path.join(configPath);
}
private _sendAndSaveMessage(message: { type: string, data: any }): void {
@@ -1990,6 +2244,12 @@ class ClaudeChatProvider {
this._panel = undefined;
}
// Dispose message handler if it exists
if (this._messageHandlerDisposable) {
this._messageHandlerDisposable.dispose();
this._messageHandlerDisposable = undefined;
}
while (this._disposables.length) {
const disposable = this._disposables.pop();
if (disposable) {

View File

@@ -1,212 +0,0 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
const server = new McpServer({
name: "Claude Code Permissions MCP Server",
version: "0.0.1",
});
// Get permissions directory from environment
const PERMISSIONS_PATH = process.env.CLAUDE_PERMISSIONS_PATH;
if (!PERMISSIONS_PATH) {
console.error("CLAUDE_PERMISSIONS_PATH environment variable not set");
process.exit(1);
}
interface WorkspacePermissions {
alwaysAllow: {
[toolName: string]: boolean | string[]; // true for all, or array of allowed commands/patterns
};
}
function getWorkspacePermissionsPath(): string | null {
if (!PERMISSIONS_PATH) return null;
return path.join(PERMISSIONS_PATH, 'permissions.json');
}
function loadWorkspacePermissions(): WorkspacePermissions {
const permissionsPath = getWorkspacePermissionsPath();
if (!permissionsPath || !fs.existsSync(permissionsPath)) {
return { alwaysAllow: {} };
}
try {
const content = fs.readFileSync(permissionsPath, 'utf8');
return JSON.parse(content);
} catch (error) {
console.error(`Error loading workspace permissions: ${error}`);
return { alwaysAllow: {} };
}
}
function isAlwaysAllowed(toolName: string, input: any): boolean {
const permissions = loadWorkspacePermissions();
const toolPermission = permissions.alwaysAllow[toolName];
if (!toolPermission) return false;
// If it's true, always allow
if (toolPermission === true) return true;
// If it's an array, check for specific commands (mainly for Bash)
if (Array.isArray(toolPermission)) {
if (toolName === 'Bash' && input.command) {
const command = input.command.trim();
return toolPermission.some(allowedCmd => {
// Support exact match or pattern matching
if (allowedCmd.includes('*')) {
// Handle patterns like "npm i *" to match both "npm i" and "npm i something"
const baseCommand = allowedCmd.replace(' *', '');
if (command === baseCommand) {
return true; // Exact match for base command
}
// Pattern match for command with arguments
const pattern = allowedCmd.replace(/\*/g, '.*');
return new RegExp(`^${pattern}$`).test(command);
}
return command.startsWith(allowedCmd);
});
}
}
return false;
}
function generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}
async function requestPermission(tool_name: string, input: any): Promise<{approved: boolean, reason?: string}> {
if (!PERMISSIONS_PATH) {
console.error("Permissions path not available");
return { approved: false, reason: "Permissions path not configured" };
}
// Check if this tool/command is always allowed for this workspace
if (isAlwaysAllowed(tool_name, input)) {
console.error(`Tool ${tool_name} is always allowed for this workspace`);
return { approved: true };
}
const requestId = generateRequestId();
const requestFile = path.join(PERMISSIONS_PATH, `${requestId}.request`);
const responseFile = path.join(PERMISSIONS_PATH, `${requestId}.response`);
// Write request file
const request = {
id: requestId,
tool: tool_name,
input: input,
timestamp: new Date().toISOString()
};
try {
fs.writeFileSync(requestFile, JSON.stringify(request, null, 2));
// Use fs.watch to wait for response file
return new Promise<{approved: boolean, reason?: string}>((resolve) => {
const timeout = setTimeout(() => {
watcher.close();
// Clean up request file on timeout
if (fs.existsSync(requestFile)) {
fs.unlinkSync(requestFile);
}
console.error(`Permission request ${requestId} timed out`);
resolve({ approved: false, reason: "Permission request timed out" });
}, 3600000); // 1 hour timeout
const watcher = fs.watch(PERMISSIONS_PATH, (eventType, filename) => {
if (eventType === 'rename' && filename === path.basename(responseFile)) {
// Check if file exists (rename event can be for creation or deletion)
if (fs.existsSync(responseFile)) {
try {
const responseContent = fs.readFileSync(responseFile, 'utf8');
const response = JSON.parse(responseContent);
// Clean up response file
fs.unlinkSync(responseFile);
// Clear timeout and close watcher
clearTimeout(timeout);
watcher.close();
resolve({
approved: response.approved,
reason: response.approved ? undefined : "User rejected the request"
});
} catch (error) {
console.error(`Error reading response file: ${error}`);
// Continue watching in case of read error
}
}
}
});
// Handle watcher errors
watcher.on('error', (error) => {
console.error(`File watcher error: ${error}`);
clearTimeout(timeout);
watcher.close();
resolve({ approved: false, reason: "File watcher error" });
});
});
} catch (error) {
console.error(`Error requesting permission: ${error}`);
return { approved: false, reason: `Error processing permission request: ${error}` };
}
}
server.tool(
"approval_prompt",
'Request user permission to execute a tool via VS Code dialog',
{
tool_name: z.string().describe("The name of the tool requesting permission"),
input: z.object({}).passthrough().describe("The input for the tool"),
tool_use_id: z.string().optional().describe("The unique tool use request ID"),
},
async ({ tool_name, input }) => {
console.error(`Requesting permission for tool: ${tool_name}`);
const permissionResult = await requestPermission(tool_name, input);
const behavior = permissionResult.approved ? "allow" : "deny";
console.error(`Permission ${behavior}ed for tool: ${tool_name}`);
return {
content: [
{
type: "text",
text: behavior === "allow" ?
JSON.stringify({
behavior: behavior,
updatedInput: input,
})
:
JSON.stringify({
behavior: behavior,
message: permissionResult.reason || "Permission denied",
})
,
},
],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`Permissions MCP Server running on stdio`);
console.error(`Using permissions directory: ${PERMISSIONS_PATH}`);
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});

View File

@@ -1,10 +0,0 @@
{
"mcpServers": {
"permissions": {
"command": "node",
"args": [
"./out/permissions/mcp-permissions.js"
]
}
}
}

View File

@@ -1351,6 +1351,7 @@ const styles = `
opacity: 1;
}
.slash-btn,
.at-btn {
background-color: transparent;
color: var(--vscode-foreground);
@@ -1363,6 +1364,7 @@ const styles = `
transition: all 0.2s ease;
}
.slash-btn:hover,
.at-btn:hover {
background-color: var(--vscode-list-hoverBackground);
}
@@ -1622,12 +1624,14 @@ const styles = `
.tools-modal-content {
background-color: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
width: 500px;
max-height: 600px;
border-radius: 8px;
width: 700px;
max-width: 90vw;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.tools-modal-header {
@@ -1636,6 +1640,13 @@ const styles = `
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.tools-modal-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
.tools-modal-header span {
@@ -1667,6 +1678,30 @@ const styles = `
overflow-y: auto;
}
/* MCP Modal content area improvements */
#mcpModal * {
box-sizing: border-box;
}
#mcpModal .tools-list {
padding: 24px;
max-height: calc(80vh - 120px);
overflow-y: auto;
width: 100%;
}
#mcpModal .mcp-servers-list {
padding: 0;
}
#mcpModal .mcp-add-server {
padding: 0;
}
#mcpModal .mcp-add-form {
padding: 12px;
}
.tool-item {
display: flex;
align-items: flex-start;
@@ -1880,12 +1915,116 @@ const styles = `
}
/* Slash commands modal */
.slash-commands-search {
padding: 16px 20px;
border-bottom: 1px solid var(--vscode-panel-border);
position: sticky;
top: 0;
background-color: var(--vscode-editor-background);
z-index: 10;
}
.search-input-wrapper {
display: flex;
align-items: center;
border: 1px solid var(--vscode-input-border);
border-radius: 6px;
background-color: var(--vscode-input-background);
transition: all 0.2s ease;
position: relative;
}
.search-input-wrapper:focus-within {
border-color: var(--vscode-focusBorder);
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
}
.search-prefix {
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 32px;
background-color: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
font-size: 13px;
font-weight: 600;
border-radius: 4px 0 0 4px;
border-right: 1px solid var(--vscode-input-border);
}
.slash-commands-search input {
flex: 1;
padding: 8px 12px;
border: none !important;
background: transparent;
color: var(--vscode-input-foreground);
font-size: 13px;
outline: none !important;
box-shadow: none !important;
}
.slash-commands-search input:focus {
border: none !important;
outline: none !important;
box-shadow: none !important;
}
.slash-commands-search input::placeholder {
color: var(--vscode-input-placeholderForeground);
}
.command-input-wrapper {
display: flex;
align-items: center;
border: 1px solid var(--vscode-input-border);
border-radius: 6px;
background-color: var(--vscode-input-background);
transition: all 0.2s ease;
width: 100%;
position: relative;
}
.command-input-wrapper:focus-within {
border-color: var(--vscode-focusBorder);
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
}
.command-prefix {
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 32px;
background-color: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
font-size: 12px;
font-weight: 600;
border-radius: 4px 0 0 4px;
border-right: 1px solid var(--vscode-input-border);
}
.slash-commands-section {
margin-bottom: 32px;
}
.slash-commands-section:last-child {
margin-bottom: 16px;
}
.slash-commands-section h3 {
margin: 16px 20px 12px 20px;
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
}
.slash-commands-info {
padding: 12px 16px;
padding: 12px 20px;
background-color: rgba(255, 149, 0, 0.1);
border: 1px solid rgba(255, 149, 0, 0.2);
border-radius: 4px;
margin-bottom: 16px;
margin: 0 20px 16px 20px;
}
.slash-commands-info p {
@@ -1896,33 +2035,161 @@ const styles = `
opacity: 0.9;
}
.prompt-snippet-item {
border-left: 2px solid var(--vscode-charts-blue);
background-color: rgba(0, 122, 204, 0.03);
}
.prompt-snippet-item:hover {
background-color: rgba(0, 122, 204, 0.08);
}
.add-snippet-item {
border-left: 2px solid var(--vscode-charts-green);
background-color: rgba(0, 200, 83, 0.03);
border-style: dashed;
}
.add-snippet-item:hover {
background-color: rgba(0, 200, 83, 0.08);
border-style: solid;
}
.add-snippet-form {
background-color: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 16px;
margin: 8px 0;
animation: slideDown 0.2s ease;
}
.add-snippet-form .form-group {
margin-bottom: 12px;
}
.add-snippet-form label {
display: block;
margin-bottom: 4px;
font-weight: 500;
font-size: 12px;
color: var(--vscode-foreground);
}
.add-snippet-form textarea {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--vscode-input-border);
border-radius: 3px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
font-size: 12px;
font-family: var(--vscode-font-family);
box-sizing: border-box;
}
.add-snippet-form .command-input-wrapper input {
flex: 1;
padding: 6px 8px;
border: none !important;
background: transparent;
color: var(--vscode-input-foreground);
font-size: 12px;
font-family: var(--vscode-font-family);
outline: none !important;
box-shadow: none !important;
}
.add-snippet-form .command-input-wrapper input:focus {
border: none !important;
outline: none !important;
box-shadow: none !important;
}
.add-snippet-form textarea:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.add-snippet-form input::placeholder,
.add-snippet-form textarea::placeholder {
color: var(--vscode-input-placeholderForeground);
}
.add-snippet-form textarea {
resize: vertical;
min-height: 60px;
}
.add-snippet-form .form-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 12px;
}
.custom-snippet-item {
position: relative;
}
.snippet-actions {
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.2s ease;
margin-left: 8px;
}
.custom-snippet-item:hover .snippet-actions {
opacity: 1;
}
.snippet-delete-btn {
background: none;
border: none;
color: var(--vscode-descriptionForeground);
cursor: pointer;
padding: 4px;
border-radius: 3px;
font-size: 12px;
transition: all 0.2s ease;
opacity: 0.7;
}
.snippet-delete-btn:hover {
background-color: rgba(231, 76, 60, 0.1);
color: var(--vscode-errorForeground);
opacity: 1;
}
.slash-commands-list {
display: grid;
gap: 8px;
max-height: 400px;
overflow-y: auto;
gap: 6px;
padding: 0 20px;
}
.slash-command-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 6px;
padding: 10px 14px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.15s ease;
border: 1px solid transparent;
background-color: transparent;
}
.slash-command-item:hover {
background-color: var(--vscode-list-hoverBackground);
border-color: var(--vscode-focusBorder);
border-color: var(--vscode-list-hoverBackground);
}
.slash-command-icon {
font-size: 18px;
min-width: 24px;
font-size: 16px;
min-width: 20px;
text-align: center;
opacity: 0.8;
}
.slash-command-content {
@@ -1931,7 +2198,7 @@ const styles = `
.slash-command-title {
font-size: 13px;
font-weight: 600;
font-weight: 500;
color: var(--vscode-foreground);
margin-bottom: 2px;
}
@@ -1939,45 +2206,39 @@ const styles = `
.slash-command-description {
font-size: 11px;
color: var(--vscode-descriptionForeground);
opacity: 0.8;
opacity: 0.7;
line-height: 1.3;
}
/* Custom command input */
/* Quick command input */
.custom-command-item {
cursor: default;
}
.custom-command-input-container {
display: flex;
align-items: center;
gap: 2px;
.custom-command-item .command-input-wrapper {
margin-top: 4px;
max-width: 200px;
}
.command-prefix {
font-size: 12px;
color: var(--vscode-foreground);
font-weight: 500;
}
.custom-command-input {
background-color: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
.custom-command-item .command-input-wrapper input {
flex: 1;
padding: 4px 6px;
border: none !important;
background: transparent;
color: var(--vscode-input-foreground);
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
outline: none;
min-width: 120px;
font-family: var(--vscode-editor-font-family);
outline: none !important;
box-shadow: none !important;
}
.custom-command-input:focus {
border-color: var(--vscode-focusBorder);
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
.custom-command-item .command-input-wrapper input:focus {
border: none !important;
outline: none !important;
box-shadow: none !important;
}
.custom-command-input::placeholder {
.custom-command-item .command-input-wrapper input::placeholder {
color: var(--vscode-input-placeholderForeground);
opacity: 0.7;
}
@@ -2307,6 +2568,231 @@ const styles = `
color: var(--vscode-foreground);
opacity: 0.8;
}
/* MCP Servers styles */
.mcp-servers-list {
padding: 4px;
}
.mcp-server-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
margin-bottom: 16px;
background-color: var(--vscode-editor-background);
transition: all 0.2s ease;
}
.mcp-server-item:hover {
border-color: var(--vscode-focusBorder);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.server-info {
flex: 1;
}
.server-name {
font-weight: 600;
font-size: 16px;
color: var(--vscode-foreground);
margin-bottom: 8px;
}
.server-type {
display: inline-block;
background-color: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
margin-bottom: 8px;
}
.server-config {
font-size: 13px;
color: var(--vscode-descriptionForeground);
opacity: 0.9;
line-height: 1.4;
}
.server-delete-btn {
padding: 8px 16px;
font-size: 13px;
color: var(--vscode-errorForeground);
border-color: var(--vscode-errorForeground);
min-width: 80px;
justify-content: center;
}
.server-delete-btn:hover {
background-color: var(--vscode-inputValidation-errorBackground);
border-color: var(--vscode-errorForeground);
}
.server-actions {
display: flex;
gap: 8px;
align-items: center;
flex-shrink: 0;
}
.server-edit-btn {
padding: 8px 16px;
font-size: 13px;
color: var(--vscode-foreground);
border-color: var(--vscode-panel-border);
min-width: 80px;
transition: all 0.2s ease;
justify-content: center;
}
.server-edit-btn:hover {
background-color: var(--vscode-list-hoverBackground);
border-color: var(--vscode-focusBorder);
}
.mcp-add-server {
text-align: center;
margin-bottom: 24px;
padding: 0 4px;
}
.mcp-add-form {
background-color: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
padding: 24px;
margin-top: 20px;
box-sizing: border-box;
width: 100%;
}
.form-group {
margin-bottom: 20px;
box-sizing: border-box;
width: 100%;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
font-size: 13px;
color: var(--vscode-foreground);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
max-width: 100%;
padding: 8px 12px;
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
font-size: 13px;
font-family: var(--vscode-font-family);
box-sizing: border-box;
resize: vertical;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--vscode-focusBorder);
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
}
.form-group textarea {
resize: vertical;
min-height: 60px;
}
.form-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 20px;
}
.no-servers {
text-align: center;
color: var(--vscode-descriptionForeground);
font-style: italic;
padding: 40px 20px;
}
/* Popular MCP Servers */
.mcp-popular-servers {
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid var(--vscode-panel-border);
}
.mcp-popular-servers h4 {
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
opacity: 0.9;
}
.popular-servers-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
.popular-server-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background-color: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.popular-server-item:hover {
border-color: var(--vscode-focusBorder);
background-color: var(--vscode-list-hoverBackground);
transform: translateY(-1px);
}
.popular-server-icon {
font-size: 24px;
flex-shrink: 0;
}
.popular-server-info {
flex: 1;
min-width: 0;
}
.popular-server-name {
font-weight: 600;
font-size: 13px;
color: var(--vscode-foreground);
margin-bottom: 2px;
}
.popular-server-desc {
font-size: 11px;
color: var(--vscode-descriptionForeground);
opacity: 0.8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>`
export default styles

789
src/ui.ts
View File

@@ -75,14 +75,15 @@ const html = `<!DOCTYPE html>
<path d="M1 2.5l3 3 3-3"></path>
</svg>
</button>
<button class="tools-btn" onclick="showToolsModal()" title="Configure tools">
Tools: All
<button class="tools-btn" onclick="showMCPModal()" title="Configure MCP servers">
MCP
<svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
<path d="M1 2.5l3 3 3-3"></path>
</svg>
</button>
</div>
<div class="right-controls">
<button class="slash-btn" onclick="showSlashCommandsModal()" title="Slash commands">/</button>
<button class="at-btn" onclick="showFilePicker()" title="Reference files">@</button>
<button class="image-btn" id="imageBtn" onclick="selectImage()" title="Attach images">
<svg
@@ -148,60 +149,108 @@ const html = `<!DOCTYPE html>
</div>
</div>
<!-- Tools modal -->
<div id="toolsModal" class="tools-modal" style="display: none;">
<!-- MCP Servers modal -->
<div id="mcpModal" class="tools-modal" style="display: none;">
<div class="tools-modal-content">
<div class="tools-modal-header">
<span>Claude Code Tools</span>
<button class="tools-close-btn" onclick="hideToolsModal()">✕</button>
<span>MCP Servers</span>
<button class="tools-close-btn" onclick="hideMCPModal()">✕</button>
</div>
<div class="tools-beta-warning">
In Beta: All tools are enabled by default. Use at your own risk.
</div>
<div id="toolsList" class="tools-list">
<div class="tool-item">
<input type="checkbox" id="tool-bash" checked disabled>
<label for="tool-bash">Bash - Execute shell commands</label>
<div class="tools-list">
<div class="mcp-servers-list" id="mcpServersList">
<!-- MCP servers will be loaded here -->
</div>
<div class="tool-item">
<input type="checkbox" id="tool-read" checked disabled>
<label for="tool-read">Read - Read file contents</label>
<div class="mcp-add-server">
<button class="btn outlined" onclick="showAddServerForm()" id="addServerBtn">+ Add MCP Server</button>
</div>
<div class="tool-item">
<input type="checkbox" id="tool-edit" checked disabled>
<label for="tool-edit">Edit - Modify files</label>
<div class="mcp-popular-servers" id="popularServers">
<h4>Popular MCP Servers</h4>
<div class="popular-servers-grid">
<div class="popular-server-item" onclick="addPopularServer('context7', { type: 'http', url: 'https://context7.liam.sh/mcp' })">
<div class="popular-server-icon">📚</div>
<div class="popular-server-info">
<div class="popular-server-name">Context7</div>
<div class="popular-server-desc">Up-to-date Code Docs For Any Prompt</div>
</div>
</div>
<div class="popular-server-item" onclick="addPopularServer('sequential-thinking', { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-sequential-thinking'] })">
<div class="popular-server-icon">🔗</div>
<div class="popular-server-info">
<div class="popular-server-name">Sequential Thinking</div>
<div class="popular-server-desc">Step-by-step reasoning capabilities</div>
</div>
</div>
<div class="popular-server-item" onclick="addPopularServer('memory', { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-memory'] })">
<div class="popular-server-icon">🧠</div>
<div class="popular-server-info">
<div class="popular-server-name">Memory</div>
<div class="popular-server-desc">Knowledge graph storage</div>
</div>
</div>
<div class="popular-server-item" onclick="addPopularServer('puppeteer', { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-puppeteer'] })">
<div class="popular-server-icon">🎭</div>
<div class="popular-server-info">
<div class="popular-server-name">Puppeteer</div>
<div class="popular-server-desc">Browser automation</div>
</div>
</div>
<div class="popular-server-item" onclick="addPopularServer('fetch', { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-fetch'] })">
<div class="popular-server-icon">🌐</div>
<div class="popular-server-info">
<div class="popular-server-name">Fetch</div>
<div class="popular-server-desc">HTTP requests & web scraping</div>
</div>
</div>
<div class="popular-server-item" onclick="addPopularServer('filesystem', { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem'] })">
<div class="popular-server-icon">📁</div>
<div class="popular-server-info">
<div class="popular-server-name">Filesystem</div>
<div class="popular-server-desc">File operations & management</div>
</div>
</div>
</div>
</div>
<div class="tool-item">
<input type="checkbox" id="tool-write" checked disabled>
<label for="tool-write">Write - Create new files</label>
<div class="mcp-add-form" id="addServerForm" style="display: none;">
<div class="form-group">
<label for="serverName">Server Name:</label>
<input type="text" id="serverName" placeholder="my-server" required>
</div>
<div class="tool-item">
<input type="checkbox" id="tool-glob" checked disabled>
<label for="tool-glob">Glob - Find files by pattern</label>
<div class="form-group">
<label for="serverType">Server Type:</label>
<select id="serverType" onchange="updateServerForm()">
<option value="http">HTTP</option>
<option value="sse">SSE</option>
<option value="stdio">stdio</option>
</select>
</div>
<div class="tool-item">
<input type="checkbox" id="tool-grep" checked disabled>
<label for="tool-grep">Grep - Search file contents</label>
<div class="form-group" id="commandGroup" style="display: none;">
<label for="serverCommand">Command:</label>
<input type="text" id="serverCommand" placeholder="/path/to/server">
</div>
<div class="tool-item">
<input type="checkbox" id="tool-ls" checked disabled>
<label for="tool-ls">LS - List directory contents</label>
<div class="form-group" id="urlGroup">
<label for="serverUrl">URL:</label>
<input type="text" id="serverUrl" placeholder="https://example.com/mcp">
</div>
<div class="tool-item">
<input type="checkbox" id="tool-multiedit" checked disabled>
<label for="tool-multiedit">MultiEdit - Edit multiple files</label>
<div class="form-group" id="argsGroup" style="display: none;">
<label for="serverArgs">Arguments (one per line):</label>
<textarea id="serverArgs" placeholder="--api-key&#10;abc123" rows="3"></textarea>
</div>
<div class="tool-item">
<input type="checkbox" id="tool-websearch" checked disabled>
<label for="tool-websearch">WebSearch - Search the web</label>
<div class="form-group" id="envGroup" style="display: none;">
<label for="serverEnv">Environment Variables (KEY=value, one per line):</label>
<textarea id="serverEnv" placeholder="API_KEY=123&#10;CACHE_DIR=/tmp" rows="3"></textarea>
</div>
<div class="tool-item">
<input type="checkbox" id="tool-webfetch" checked disabled>
<label for="tool-webfetch">WebFetch - Fetch web content</label>
<div class="form-group" id="headersGroup">
<label for="serverHeaders">Headers (KEY=value, one per line):</label>
<textarea id="serverHeaders" placeholder="Authorization=Bearer token&#10;X-API-Key=key" rows="3"></textarea>
</div>
<div class="form-buttons">
<button class="btn" onclick="saveMCPServer()">Add Server</button>
<button class="btn outlined" onclick="hideAddServerForm()">Cancel</button>
</div>
</div>
</div>
</div>
</div>
<!-- Settings modal -->
<div id="settingsModal" class="tools-modal" style="display: none;">
@@ -295,19 +344,6 @@ const html = `<!DOCTYPE html>
</div>
</div>
<h3 style="margin-top: 24px; margin-bottom: 16px; font-size: 14px; font-weight: 600;">MCP Configuration (coming soon)</h3>
<div>
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 0;">
Model Context Protocol (MCP) allows Claude Code to connect to external systems and services for enhanced capabilities like databases, APIs, and tools.
</p>
</div>
<div class="settings-group">
<div class="tool-item">
<input type="checkbox" id="mcp-enabled" disabled>
<label for="mcp-enabled">Enable MCP Integration <span style="font-style: italic; opacity: 0.7;">(Coming Soon)</span></label>
</div>
</div>
<h3 style="margin-top: 24px; margin-bottom: 16px; font-size: 14px; font-weight: 600;">Custom Slash Commands (coming soon)</h3>
<div>
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 0;">
@@ -403,13 +439,121 @@ const html = `<!DOCTYPE html>
<div id="slashCommandsModal" class="tools-modal" style="display: none;">
<div class="tools-modal-content">
<div class="tools-modal-header">
<span>Claude Code Commands</span>
<span>Commands & Prompt Snippets</span>
<button class="tools-close-btn" onclick="hideSlashCommandsModal()">✕</button>
</div>
<div class="slash-commands-info">
<p>These commands require the Claude CLI and will open in VS Code terminal. Return here after completion.</p>
<div class="tools-modal-body">
<!-- Search box -->
<div class="slash-commands-search">
<div class="search-input-wrapper">
<span class="search-prefix">/</span>
<input type="text" id="slashCommandsSearch" placeholder="Search commands and snippets..." oninput="filterSlashCommands()">
</div>
</div>
<div class="slash-commands-list">
<!-- Custom Commands Section -->
<div class="slash-commands-section">
<h3>Custom Commands</h3>
<div class="slash-commands-info">
<p>Custom slash commands for quick prompt access. Click to use directly in chat.</p>
</div>
<div class="slash-commands-list" id="promptSnippetsList">
<!-- Add Custom Snippet Button -->
<div class="slash-command-item add-snippet-item" onclick="showAddSnippetForm()">
<div class="slash-command-icon"></div>
<div class="slash-command-content">
<div class="slash-command-title">Add Custom Command</div>
<div class="slash-command-description">Create your own slash command</div>
</div>
</div>
<!-- Add Custom Command Form (initially hidden) -->
<div class="add-snippet-form" id="addSnippetForm" style="display: none;">
<div class="form-group">
<label for="snippetName">Command name:</label>
<div class="command-input-wrapper">
<span class="command-prefix">/</span>
<input type="text" id="snippetName" placeholder="e.g., fix-bug" maxlength="50">
</div>
</div>
<div class="form-group">
<label for="snippetPrompt">Prompt Text:</label>
<textarea id="snippetPrompt" placeholder="e.g., Help me fix this bug in my code..." rows="3" maxlength="500"></textarea>
</div>
<div class="form-buttons">
<button class="btn" onclick="saveCustomSnippet()">Save Command</button>
<button class="btn outlined" onclick="hideAddSnippetForm()">Cancel</button>
</div>
</div>
<!-- Built-in Snippets -->
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('performance-analysis')">
<div class="slash-command-icon">⚡</div>
<div class="slash-command-content">
<div class="slash-command-title">/performance-analysis</div>
<div class="slash-command-description">Analyze this code for performance issues and suggest optimizations</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('security-review')">
<div class="slash-command-icon">🔒</div>
<div class="slash-command-content">
<div class="slash-command-title">/security-review</div>
<div class="slash-command-description">Review this code for security vulnerabilities</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('implementation-review')">
<div class="slash-command-icon">🔍</div>
<div class="slash-command-content">
<div class="slash-command-title">/implementation-review</div>
<div class="slash-command-description">Review the implementation in this code</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('code-explanation')">
<div class="slash-command-icon">📖</div>
<div class="slash-command-content">
<div class="slash-command-title">/code-explanation</div>
<div class="slash-command-description">Explain how this code works in detail</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('bug-fix')">
<div class="slash-command-icon">🐛</div>
<div class="slash-command-content">
<div class="slash-command-title">/bug-fix</div>
<div class="slash-command-description">Help me fix this bug in my code</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('refactor')">
<div class="slash-command-icon">🔄</div>
<div class="slash-command-content">
<div class="slash-command-title">/refactor</div>
<div class="slash-command-description">Refactor this code to improve readability and maintainability</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('test-generation')">
<div class="slash-command-icon">🧪</div>
<div class="slash-command-content">
<div class="slash-command-title">/test-generation</div>
<div class="slash-command-description">Generate comprehensive tests for this code</div>
</div>
</div>
<div class="slash-command-item prompt-snippet-item" onclick="usePromptSnippet('documentation')">
<div class="slash-command-icon">📝</div>
<div class="slash-command-content">
<div class="slash-command-title">/documentation</div>
<div class="slash-command-description">Generate documentation for this code</div>
</div>
</div>
</div>
</div>
<!-- Built-in Commands Section -->
<div class="slash-commands-section">
<h3>Built-in Commands</h3>
<div class="slash-commands-info">
<p>These commands require the Claude CLI and will open in VS Code terminal. Return here after completion.</p>
</div>
<div class="slash-commands-list" id="nativeCommandsList">
<div class="slash-command-item" onclick="executeSlashCommand('bug')">
<div class="slash-command-icon">🐛</div>
<div class="slash-command-content">
@@ -546,9 +690,9 @@ const html = `<!DOCTYPE html>
<div class="slash-command-item custom-command-item">
<div class="slash-command-icon">⚡</div>
<div class="slash-command-content">
<div class="slash-command-title">Custom Command</div>
<div class="slash-command-title">Quick Command</div>
<div class="slash-command-description">
<div class="custom-command-input-container">
<div class="command-input-wrapper">
<span class="command-prefix">/</span>
<input type="text"
class="custom-command-input"
@@ -561,6 +705,7 @@ const html = `<!DOCTYPE html>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -923,7 +1068,7 @@ const html = `<!DOCTYPE html>
} else if (valueStr.length > 100) {
const truncated = valueStr.substring(0, 97) + '...';
const escapedValue = valueStr.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
result += '<strong>' + key + ':</strong> ' + truncated + ' <span class="expand-btn" data-key="' + key + '" data-value="' + escapedValue + '" onclick="toggleExpand(this)">expand</span>';
result += '<span class="expandable-item"><strong>' + key + ':</strong> ' + truncated + ' <span class="expand-btn" data-key="' + key + '" data-value="' + escapedValue + '" onclick="toggleExpand(this)">expand</span></span>';
} else {
result += '<strong>' + key + ':</strong> ' + valueStr;
}
@@ -1234,6 +1379,46 @@ const html = `<!DOCTYPE html>
}
}
function toggleExpand(button) {
const key = button.getAttribute('data-key');
const value = button.getAttribute('data-value');
// Find the container that holds just this key-value pair
let container = button.parentNode;
while (container && !container.classList.contains('expandable-item')) {
container = container.parentNode;
}
if (!container) {
// Fallback: create a wrapper around the current line
const parent = button.parentNode;
const wrapper = document.createElement('div');
wrapper.className = 'expandable-item';
parent.insertBefore(wrapper, button.previousSibling || button);
// Move the key, value text, and button into the wrapper
let currentNode = wrapper.nextSibling;
const nodesToMove = [];
while (currentNode && currentNode !== button.nextSibling) {
nodesToMove.push(currentNode);
currentNode = currentNode.nextSibling;
}
nodesToMove.forEach(node => wrapper.appendChild(node));
container = wrapper;
}
if (button.textContent === 'expand') {
// Show full content
const decodedValue = value.replace(/&quot;/g, '"').replace(/&#39;/g, "'");
container.innerHTML = '<strong>' + key + ':</strong> ' + decodedValue + ' <span class="expand-btn" data-key="' + key + '" data-value="' + value + '" onclick="toggleExpand(this)">collapse</span>';
} else {
// Show truncated content
const decodedValue = value.replace(/&quot;/g, '"').replace(/&#39;/g, "'");
const truncated = decodedValue.substring(0, 97) + '...';
container.innerHTML = '<strong>' + key + ':</strong> ' + truncated + ' <span class="expand-btn" data-key="' + key + '" data-value="' + value + '" onclick="toggleExpand(this)">expand</span>';
}
}
function sendMessage() {
const text = messageInput.value.trim();
if (text) {
@@ -1523,8 +1708,10 @@ const html = `<!DOCTYPE html>
});
// Tools modal functions
function showToolsModal() {
document.getElementById('toolsModal').style.display = 'flex';
function showMCPModal() {
document.getElementById('mcpModal').style.display = 'flex';
// Load existing MCP servers
loadMCPServers();
}
function updateYoloWarning() {
@@ -1575,17 +1762,310 @@ const html = `<!DOCTYPE html>
}
}
function hideToolsModal() {
document.getElementById('toolsModal').style.display = 'none';
function hideMCPModal() {
document.getElementById('mcpModal').style.display = 'none';
hideAddServerForm();
}
// Close tools modal when clicking outside
document.getElementById('toolsModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('toolsModal')) {
hideToolsModal();
// Close MCP modal when clicking outside
document.getElementById('mcpModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('mcpModal')) {
hideMCPModal();
}
});
// MCP Server management functions
function loadMCPServers() {
vscode.postMessage({ type: 'loadMCPServers' });
}
function showAddServerForm() {
document.getElementById('addServerBtn').style.display = 'none';
document.getElementById('popularServers').style.display = 'none';
document.getElementById('addServerForm').style.display = 'block';
}
function hideAddServerForm() {
document.getElementById('addServerBtn').style.display = 'block';
document.getElementById('popularServers').style.display = 'block';
document.getElementById('addServerForm').style.display = 'none';
// Reset editing state
editingServerName = null;
// Reset form title and button
const formTitle = document.querySelector('#addServerForm h5');
if (formTitle) formTitle.remove();
const saveBtn = document.querySelector('#addServerForm .btn:not(.outlined)');
if (saveBtn) saveBtn.textContent = 'Add Server';
// Clear form
document.getElementById('serverName').value = '';
document.getElementById('serverName').disabled = false;
document.getElementById('serverCommand').value = '';
document.getElementById('serverUrl').value = '';
document.getElementById('serverArgs').value = '';
document.getElementById('serverEnv').value = '';
document.getElementById('serverHeaders').value = '';
document.getElementById('serverType').value = 'http';
updateServerForm();
}
function updateServerForm() {
const serverType = document.getElementById('serverType').value;
const commandGroup = document.getElementById('commandGroup');
const urlGroup = document.getElementById('urlGroup');
const argsGroup = document.getElementById('argsGroup');
const envGroup = document.getElementById('envGroup');
const headersGroup = document.getElementById('headersGroup');
if (serverType === 'stdio') {
commandGroup.style.display = 'block';
urlGroup.style.display = 'none';
argsGroup.style.display = 'block';
envGroup.style.display = 'block';
headersGroup.style.display = 'none';
} else if (serverType === 'http' || serverType === 'sse') {
commandGroup.style.display = 'none';
urlGroup.style.display = 'block';
argsGroup.style.display = 'none';
envGroup.style.display = 'none';
headersGroup.style.display = 'block';
}
}
function saveMCPServer() {
const name = document.getElementById('serverName').value.trim();
const type = document.getElementById('serverType').value;
if (!name) {
// Use a simple notification instead of alert which is blocked
const notification = document.createElement('div');
notification.textContent = 'Server name is required';
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground); padding: 8px 12px; border-radius: 4px; z-index: 9999;';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
return;
}
// If editing, we can use the same name; if adding, check for duplicates
if (!editingServerName) {
const serversList = document.getElementById('mcpServersList');
const existingServers = serversList.querySelectorAll('.server-name');
for (let server of existingServers) {
if (server.textContent === name) {
const notification = document.createElement('div');
notification.textContent = \`Server "\${name}" already exists\`;
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground); padding: 8px 12px; border-radius: 4px; z-index: 9999;';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
return;
}
}
}
const serverConfig = { type };
if (type === 'stdio') {
const command = document.getElementById('serverCommand').value.trim();
if (!command) {
const notification = document.createElement('div');
notification.textContent = 'Command is required for stdio servers';
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground); padding: 8px 12px; border-radius: 4px; z-index: 9999;';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
return;
}
serverConfig.command = command;
const argsText = document.getElementById('serverArgs').value.trim();
if (argsText) {
serverConfig.args = argsText.split('\\n').filter(line => line.trim());
}
const envText = document.getElementById('serverEnv').value.trim();
if (envText) {
serverConfig.env = {};
envText.split('\\n').forEach(line => {
const [key, ...valueParts] = line.split('=');
if (key && valueParts.length > 0) {
serverConfig.env[key.trim()] = valueParts.join('=').trim();
}
});
}
} else if (type === 'http' || type === 'sse') {
const url = document.getElementById('serverUrl').value.trim();
if (!url) {
const notification = document.createElement('div');
notification.textContent = 'URL is required for HTTP/SSE servers';
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground); padding: 8px 12px; border-radius: 4px; z-index: 9999;';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
return;
}
serverConfig.url = url;
const headersText = document.getElementById('serverHeaders').value.trim();
if (headersText) {
serverConfig.headers = {};
headersText.split('\\n').forEach(line => {
const [key, ...valueParts] = line.split('=');
if (key && valueParts.length > 0) {
serverConfig.headers[key.trim()] = valueParts.join('=').trim();
}
});
}
}
vscode.postMessage({
type: 'saveMCPServer',
name: name,
config: serverConfig
});
hideAddServerForm();
}
function deleteMCPServer(serverName) {
// Just delete without confirmation
vscode.postMessage({
type: 'deleteMCPServer',
name: serverName
});
}
let editingServerName = null;
function editMCPServer(name, config) {
editingServerName = name;
// Hide add button and popular servers
document.getElementById('addServerBtn').style.display = 'none';
document.getElementById('popularServers').style.display = 'none';
// Show form
document.getElementById('addServerForm').style.display = 'block';
// Update form title and button
const formTitle = document.querySelector('#addServerForm h5') ||
document.querySelector('#addServerForm').insertAdjacentHTML('afterbegin', '<h5>Edit MCP Server</h5>') ||
document.querySelector('#addServerForm h5');
if (!document.querySelector('#addServerForm h5')) {
document.getElementById('addServerForm').insertAdjacentHTML('afterbegin', '<h5 style="margin: 0 0 20px 0; font-size: 14px; font-weight: 600;">Edit MCP Server</h5>');
} else {
document.querySelector('#addServerForm h5').textContent = 'Edit MCP Server';
}
// Update save button text
const saveBtn = document.querySelector('#addServerForm .btn:not(.outlined)');
if (saveBtn) saveBtn.textContent = 'Update Server';
// Populate form with existing values
document.getElementById('serverName').value = name;
document.getElementById('serverName').disabled = true; // Don't allow name changes when editing
document.getElementById('serverType').value = config.type || 'stdio';
if (config.command) {
document.getElementById('serverCommand').value = config.command;
}
if (config.url) {
document.getElementById('serverUrl').value = config.url;
}
if (config.args && Array.isArray(config.args)) {
document.getElementById('serverArgs').value = config.args.join('\\n');
}
if (config.env) {
const envLines = Object.entries(config.env).map(([key, value]) => \`\${key}=\${value}\`);
document.getElementById('serverEnv').value = envLines.join('\\n');
}
if (config.headers) {
const headerLines = Object.entries(config.headers).map(([key, value]) => \`\${key}=\${value}\`);
document.getElementById('serverHeaders').value = headerLines.join('\\n');
}
// Update form field visibility
updateServerForm();
const toolsList = document.querySelector('.tools-list');
if (toolsList) {
toolsList.scrollTop = toolsList.scrollHeight;
}
}
function addPopularServer(name, config) {
// Check if server already exists
const serversList = document.getElementById('mcpServersList');
const existingServers = serversList.querySelectorAll('.server-name');
for (let server of existingServers) {
if (server.textContent === name) {
const notification = document.createElement('div');
notification.textContent = \`Server "\${name}" already exists\`;
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground); padding: 8px 12px; border-radius: 4px; z-index: 9999;';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
return;
}
}
// Add the server
vscode.postMessage({
type: 'saveMCPServer',
name: name,
config: config
});
}
function displayMCPServers(servers) {
const serversList = document.getElementById('mcpServersList');
serversList.innerHTML = '';
if (Object.keys(servers).length === 0) {
serversList.innerHTML = '<div class="no-servers">No MCP servers configured</div>';
return;
}
for (const [name, config] of Object.entries(servers)) {
const serverItem = document.createElement('div');
serverItem.className = 'mcp-server-item';
// Defensive check for config structure
if (!config || typeof config !== 'object') {
console.error('Invalid config for server:', name, config);
continue;
}
const serverType = config.type || 'stdio';
let configDisplay = '';
if (serverType === 'stdio') {
configDisplay = \`Command: \${config.command || 'Not specified'}\`;
if (config.args && Array.isArray(config.args)) {
configDisplay += \`<br>Args: \${config.args.join(' ')}\`;
}
} else if (serverType === 'http' || serverType === 'sse') {
configDisplay = \`URL: \${config.url || 'Not specified'}\`;
} else {
configDisplay = \`Type: \${serverType}\`;
}
serverItem.innerHTML = \`
<div class="server-info">
<div class="server-name">\${name}</div>
<div class="server-type">\${serverType.toUpperCase()}</div>
<div class="server-config">\${configDisplay}</div>
</div>
<div class="server-actions">
<button class="btn outlined server-edit-btn" onclick="editMCPServer('\${name}', \${JSON.stringify(config).replace(/"/g, '&quot;')})">Edit</button>
<button class="btn outlined server-delete-btn" onclick="deleteMCPServer('\${name}')">Delete</button>
</div>
\`;
serversList.appendChild(serverItem);
}
}
// Model selector functions
let currentModel = 'opus'; // Default model
@@ -1605,6 +2085,10 @@ const html = `<!DOCTYPE html>
// Slash commands modal functions
function showSlashCommandsModal() {
document.getElementById('slashCommandsModal').style.display = 'flex';
// Auto-focus the search input
setTimeout(() => {
document.getElementById('slashCommandsSearch').focus();
}, 100);
}
function hideSlashCommandsModal() {
@@ -1739,6 +2223,136 @@ const html = `<!DOCTYPE html>
}
}
// Store custom snippets data globally
let customSnippetsData = {};
function usePromptSnippet(snippetType) {
const builtInSnippets = {
'performance-analysis': 'Analyze this code for performance issues and suggest optimizations',
'security-review': 'Review this code for security vulnerabilities',
'implementation-review': 'Review the implementation in this code',
'code-explanation': 'Explain how this code works in detail',
'bug-fix': 'Help me fix this bug in my code',
'refactor': 'Refactor this code to improve readability and maintainability',
'test-generation': 'Generate comprehensive tests for this code',
'documentation': 'Generate documentation for this code'
};
// Check built-in snippets first
let promptText = builtInSnippets[snippetType];
// If not found in built-in, check custom snippets
if (!promptText && customSnippetsData[snippetType]) {
promptText = customSnippetsData[snippetType].prompt;
}
if (promptText) {
// Hide the modal
hideSlashCommandsModal();
// Insert the prompt into the message input
messageInput.value = promptText;
messageInput.focus();
// Auto-resize the textarea
autoResizeTextarea();
}
}
function showAddSnippetForm() {
document.getElementById('addSnippetForm').style.display = 'block';
document.getElementById('snippetName').focus();
}
function hideAddSnippetForm() {
document.getElementById('addSnippetForm').style.display = 'none';
// Clear form fields
document.getElementById('snippetName').value = '';
document.getElementById('snippetPrompt').value = '';
}
function saveCustomSnippet() {
const name = document.getElementById('snippetName').value.trim();
const prompt = document.getElementById('snippetPrompt').value.trim();
if (!name || !prompt) {
alert('Please fill in both name and prompt text.');
return;
}
// Generate a unique ID for the snippet
const snippetId = 'custom-' + Date.now();
// Save the snippet using VS Code global storage
const snippetData = {
name: name,
prompt: prompt,
id: snippetId
};
vscode.postMessage({
type: 'saveCustomSnippet',
snippet: snippetData
});
// Hide the form
hideAddSnippetForm();
}
function loadCustomSnippets(snippetsData = {}) {
const snippetsList = document.getElementById('promptSnippetsList');
// Remove existing custom snippets
const existingCustom = snippetsList.querySelectorAll('.custom-snippet-item');
existingCustom.forEach(item => item.remove());
// Add custom snippets after the add button and form
const addForm = document.getElementById('addSnippetForm');
Object.values(snippetsData).forEach(snippet => {
const snippetElement = document.createElement('div');
snippetElement.className = 'slash-command-item prompt-snippet-item custom-snippet-item';
snippetElement.onclick = () => usePromptSnippet(snippet.id);
snippetElement.innerHTML = \`
<div class="slash-command-icon">📝</div>
<div class="slash-command-content">
<div class="slash-command-title">/\${snippet.name}</div>
<div class="slash-command-description">\${snippet.prompt}</div>
</div>
<div class="snippet-actions">
<button class="snippet-delete-btn" onclick="event.stopPropagation(); deleteCustomSnippet('\${snippet.id}')" title="Delete snippet">🗑️</button>
</div>
\`;
// Insert after the form
addForm.parentNode.insertBefore(snippetElement, addForm.nextSibling);
});
}
function deleteCustomSnippet(snippetId) {
vscode.postMessage({
type: 'deleteCustomSnippet',
snippetId: snippetId
});
}
function filterSlashCommands() {
const searchTerm = document.getElementById('slashCommandsSearch').value.toLowerCase();
const allItems = document.querySelectorAll('.slash-command-item');
allItems.forEach(item => {
const title = item.querySelector('.slash-command-title').textContent.toLowerCase();
const description = item.querySelector('.slash-command-description').textContent.toLowerCase();
if (title.includes(searchTerm) || description.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
function openModelTerminal() {
vscode.postMessage({
type: 'openModelTerminal'
@@ -2141,11 +2755,28 @@ const html = `<!DOCTYPE html>
case 'permissionRequest':
addPermissionRequestMessage(message.data);
break;
case 'mcpServers':
displayMCPServers(message.data);
break;
case 'mcpServerSaved':
loadMCPServers(); // Reload the servers list
addMessage('✅ MCP server "' + message.data.name + '" saved successfully', 'system');
break;
case 'mcpServerDeleted':
loadMCPServers(); // Reload the servers list
addMessage('✅ MCP server "' + message.data.name + '" deleted successfully', 'system');
break;
case 'mcpServerError':
addMessage('❌ Error with MCP server: ' + message.data.error, 'error');
break;
}
});
// Permission request functions
function addPermissionRequestMessage(data) {
const messagesDiv = document.getElementById('messages');
const shouldScroll = shouldAutoScroll(messagesDiv);
const messageDiv = document.createElement('div');
messageDiv.className = 'message permission-request';
@@ -2852,6 +3483,11 @@ const html = `<!DOCTYPE html>
}
});
// Request custom snippets from VS Code on page load
vscode.postMessage({
type: 'getCustomSnippets'
});
// Detect slash commands input
messageInput.addEventListener('input', (e) => {
const value = messageInput.value;
@@ -2866,7 +3502,22 @@ const html = `<!DOCTYPE html>
window.addEventListener('message', event => {
const message = event.data;
if (message.type === 'settingsData') {
if (message.type === 'customSnippetsData') {
// Update global custom snippets data
customSnippetsData = message.data || {};
// Refresh the snippets display
loadCustomSnippets(customSnippetsData);
} else if (message.type === 'customSnippetSaved') {
// Refresh snippets after saving
vscode.postMessage({
type: 'getCustomSnippets'
});
} else if (message.type === 'customSnippetDeleted') {
// Refresh snippets after deletion
vscode.postMessage({
type: 'getCustomSnippets'
});
} else if (message.type === 'settingsData') {
// Update UI with current settings
const thinkingIntensity = message.data['thinking.intensity'] || 'think';
const intensityValues = ['think', 'think-hard', 'think-harder', 'ultrathink'];

View File

@@ -13,5 +13,8 @@
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
},
"exclude": [
"mcp-permissions.js"
]
}