fix lint issues (#180)
This commit is contained in:
1 parent
9d93ea4f87
commit
bdeccafada
11 files changed
+1444
-13
No files matched your search
@@ -5,7 +5,9 @@
|
||||
"": {
|
||||
"name": "@vibe-kit/grok-cli",
|
||||
"dependencies": {
|
||||
"@ai-sdk/mcp": "^1.0.25",
|
||||
"@ai-sdk/xai": "^3.0.67",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@opentui/core": "^0.1.88",
|
||||
"@opentui/react": "^0.1.88",
|
||||
"ai": "^6.0.116",
|
||||
@@ -30,6 +32,8 @@
|
||||
"packages": {
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A=="],
|
||||
|
||||
"@ai-sdk/mcp": ["@ai-sdk/mcp@1.0.25", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-vMlXUPGHGDE2vzLcPR8sw7Dhz2OBjtPU5lB+lIuC1hNQo4REuUC08P0e96/hzBKf4oQYJ8Zo6uP8AG2qThyFbg=="],
|
||||
|
||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-g3wA57IAQFb+3j4YuFndgkUdXyRETZVvbfAWM+UX7bZSxA3xjes0v3XKgIdKdekPtDGsh4ZX2byHD0gJIMPfiA=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
@@ -60,6 +64,8 @@
|
||||
|
||||
"@grammyjs/types": ["@grammyjs/types@3.25.0", "", {}, "sha512-iN9i5p+8ZOu9OMxWNcguojQfz4K/PDyMPOnL7PPCON+SoA/F8OKMH3uR7CVUkYfdNe0GCz8QOzAWrnqusQYFOg=="],
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
|
||||
|
||||
"@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
|
||||
|
||||
"@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
|
||||
@@ -116,6 +122,8 @@
|
||||
|
||||
"@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "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.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.88", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.88", "@opentui/core-darwin-x64": "0.1.88", "@opentui/core-linux-arm64": "0.1.88", "@opentui/core-linux-x64": "0.1.88", "@opentui/core-win32-arm64": "0.1.88", "@opentui/core-win32-x64": "0.1.88", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-eaDVZfAzZraddOIkgWSHMVkyaY0O20foYnPWKPQx1TY4t7G1oatIoan2zkytx67epW+4BZQ9vGib+61/uNM1MA=="],
|
||||
@@ -150,8 +158,14 @@
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"ai": ["ai@6.0.116", "", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="],
|
||||
|
||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
@@ -166,6 +180,8 @@
|
||||
|
||||
"bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
|
||||
|
||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
|
||||
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
@@ -180,6 +196,12 @@
|
||||
|
||||
"bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-zvnUl4EAsQbKsmZVu+lEJcH8axQ7MiCfqg2OmnHd6uw1THABmHaX0GbpKiHshdgadNN2Nf+4zDyTJB5YMcAdrA=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="],
|
||||
@@ -188,50 +210,130 @@
|
||||
|
||||
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||
|
||||
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
||||
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
|
||||
|
||||
"exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
|
||||
|
||||
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.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" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||
|
||||
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
||||
|
||||
"finalhandler": ["finalhandler@2.1.1", "", { "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" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"grammy": ["grammy@1.41.1", "", { "dependencies": { "@grammyjs/types": "3.25.0", "abort-controller": "^3.0.0", "debug": "^4.4.3", "node-fetch": "^2.7.0" } }, "sha512-wcHAQ1e7svL3fJMpDchcQVcWUmywhuepOOjHUHmMmWAwUJEIyK5ea5sbSjZd+Gy1aMpZeP8VYJa+4tP+j1YptQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
||||
|
||||
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
|
||||
|
||||
"jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
|
||||
|
||||
"jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
|
||||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||
|
||||
"lint-staged": ["lint-staged@16.4.0", "", { "dependencies": { "commander": "^14.0.3", "listr2": "^9.0.5", "picomatch": "^4.0.3", "string-argv": "^0.3.2", "tinyexec": "^1.0.4", "yaml": "^2.8.2" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw=="],
|
||||
|
||||
"listr2": ["listr2@9.0.5", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="],
|
||||
@@ -240,16 +342,36 @@
|
||||
|
||||
"marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||
|
||||
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||
@@ -260,18 +382,34 @@
|
||||
|
||||
"parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||
|
||||
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
|
||||
|
||||
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
||||
|
||||
"planck": ["planck@1.4.3", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-B+lHKhRSeg7vZOfEyEzyQVu7nx8JHcX3QgnAcHXrPW0j04XYKX5eXSiUrxH2Z5QR8OoqvjD6zKIaPMdMYAd0uA=="],
|
||||
|
||||
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
|
||||
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-devtools-core": ["react-devtools-core@7.0.1", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw=="],
|
||||
@@ -282,18 +420,42 @@
|
||||
|
||||
"readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||
|
||||
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"simple-xml-to-json": ["simple-xml-to-json@1.2.4", "", {}, "sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg=="],
|
||||
@@ -302,6 +464,8 @@
|
||||
|
||||
"stage-js": ["stage-js@1.0.1", "", {}, "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw=="],
|
||||
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
"string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="],
|
||||
@@ -318,24 +482,36 @@
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||
|
||||
"xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
|
||||
@@ -350,6 +526,8 @@
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||
|
||||
"@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@jimp/plugin-circle/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "grok-dev",
|
||||
"version": "1.0.0-rc1",
|
||||
"version": "1.0.0-rc2",
|
||||
"description": "An open-source AI coding agent powered by Grok, built with Bun and OpenTUI.",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -40,7 +40,9 @@
|
||||
"author": "Vibe Kit",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ai-sdk/mcp": "^1.0.25",
|
||||
"@ai-sdk/xai": "^3.0.67",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@opentui/core": "^0.1.88",
|
||||
"@opentui/react": "^0.1.88",
|
||||
"ai": "^6.0.116",
|
||||
|
||||
+31
-3
@@ -1,6 +1,7 @@
|
||||
import { type ModelMessage, stepCountIs, streamText } from "ai";
|
||||
import { type ModelMessage, stepCountIs, streamText, type ToolSet } from "ai";
|
||||
import { createProvider, generateTitle as genTitle, type XaiProvider } from "../grok/client";
|
||||
import { createTools } from "../grok/tools";
|
||||
import { buildMcpToolSet } from "../mcp/runtime";
|
||||
import {
|
||||
appendMessages,
|
||||
appendSystemMessage,
|
||||
@@ -26,6 +27,7 @@ import type {
|
||||
WorkspaceInfo,
|
||||
} from "../types/index";
|
||||
import { loadCustomInstructions } from "../utils/instructions";
|
||||
import { loadMcpServers } from "../utils/settings";
|
||||
import { discoverSkills, formatSkillsForPrompt } from "../utils/skills";
|
||||
import { DelegationManager } from "./delegations";
|
||||
|
||||
@@ -64,6 +66,7 @@ TOOLS:
|
||||
- delegation_list: List running and completed background delegations. Do not poll it repeatedly.
|
||||
- search_web: Search the web for current information, documentation, APIs, tutorials, etc.
|
||||
- search_x: Search X/Twitter for real-time posts, discussions, opinions, and trends.
|
||||
- MCP tools: Enabled servers appear as tools named like mcp_<server>__<tool>.
|
||||
|
||||
WORKFLOW:
|
||||
1. Understand the request
|
||||
@@ -427,14 +430,26 @@ export class Agent {
|
||||
const signal = abortSignal;
|
||||
const childMode: AgentMode = request.agent === "explore" ? "ask" : "agent";
|
||||
const childBash = new BashTool(this.bash.getCwd());
|
||||
const childTools = createTools(childBash, provider, childMode);
|
||||
const childBaseTools = createTools(childBash, provider, childMode);
|
||||
const initialDetail = request.agent === "explore" ? "Scanning the codebase" : "Planning delegated work";
|
||||
let assistantText = "";
|
||||
let lastActivity = initialDetail;
|
||||
let childTools: ToolSet = childBaseTools;
|
||||
let closeMcp: (() => Promise<void>) | undefined;
|
||||
|
||||
onActivity?.(initialDetail);
|
||||
|
||||
try {
|
||||
if (childMode === "agent") {
|
||||
const mcpBundle = await buildMcpToolSet(loadMcpServers());
|
||||
closeMcp = mcpBundle.close;
|
||||
childTools = { ...childBaseTools, ...mcpBundle.tools };
|
||||
if (mcpBundle.errors.length > 0) {
|
||||
lastActivity = `MCP unavailable: ${mcpBundle.errors.join(" | ")}`;
|
||||
onActivity?.(lastActivity);
|
||||
}
|
||||
}
|
||||
|
||||
const result = streamText({
|
||||
model: provider(request.agent === "explore" ? "grok-4-1-fast" : this.modelId),
|
||||
system: buildSubagentPrompt(request, childBash.getCwd()),
|
||||
@@ -497,6 +512,8 @@ export class Agent {
|
||||
activity: lastActivity,
|
||||
},
|
||||
};
|
||||
} finally {
|
||||
await closeMcp?.().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,15 +611,25 @@ export class Agent {
|
||||
|
||||
let assistantText = "";
|
||||
let streamOk = false;
|
||||
let closeMcp: (() => Promise<void>) | undefined;
|
||||
|
||||
try {
|
||||
const provider = this.requireProvider();
|
||||
const tools = createTools(this.bash, provider, this.mode, {
|
||||
const baseTools = createTools(this.bash, provider, this.mode, {
|
||||
runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
|
||||
runDelegation: (request, abortSignal) => this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
|
||||
readDelegation: (id) => this.readDelegation(id),
|
||||
listDelegations: () => this.listDelegations(),
|
||||
});
|
||||
let tools: ToolSet = baseTools;
|
||||
if (this.mode === "agent") {
|
||||
const mcpBundle = await buildMcpToolSet(loadMcpServers());
|
||||
closeMcp = mcpBundle.close;
|
||||
tools = { ...baseTools, ...mcpBundle.tools };
|
||||
if (mcpBundle.errors.length > 0) {
|
||||
yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
|
||||
}
|
||||
}
|
||||
const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
|
||||
this.planContext = null;
|
||||
|
||||
@@ -721,6 +748,7 @@ export class Agent {
|
||||
}
|
||||
yield { type: "done" };
|
||||
} finally {
|
||||
await closeMcp?.().catch(() => {});
|
||||
if (this.abortController?.signal === signal) {
|
||||
this.abortController = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import type { McpRemoteTransport } from "../utils/settings";
|
||||
|
||||
export interface McpCatalogEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
directoryUrl: string;
|
||||
sourceUrl?: string;
|
||||
starterTransport?: McpRemoteTransport | "stdio";
|
||||
}
|
||||
|
||||
export const POPULAR_MCP_CATALOG: McpCatalogEntry[] = [
|
||||
{
|
||||
id: "github",
|
||||
name: "GitHub",
|
||||
description: "Integration with GitHub issues and related workflows.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-github",
|
||||
sourceUrl: "https://github.com/modelcontextprotocol/servers/tree/main/src/github",
|
||||
starterTransport: "stdio",
|
||||
},
|
||||
{
|
||||
id: "supabase",
|
||||
name: "Supabase",
|
||||
description: "PostgREST-backed operations for Postgres databases.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-supabase",
|
||||
},
|
||||
{
|
||||
id: "vercel",
|
||||
name: "Vercel",
|
||||
description: "Vercel infrastructure and deployment workflows.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-vercel",
|
||||
},
|
||||
{
|
||||
id: "cloudflare",
|
||||
name: "Cloudflare",
|
||||
description: "Workers, KV, R2, and D1 operations on Cloudflare.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-cloudflare",
|
||||
},
|
||||
{
|
||||
id: "stripe",
|
||||
name: "Stripe",
|
||||
description: "Stripe API access for billing and payment workflows.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-stripe",
|
||||
},
|
||||
{
|
||||
id: "notion",
|
||||
name: "Notion",
|
||||
description: "Databases, pages, blocks, and search inside Notion workspaces.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-notion",
|
||||
},
|
||||
{
|
||||
id: "slack",
|
||||
name: "Slack",
|
||||
description: "Slack workspace messaging and channel workflows.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-slack",
|
||||
},
|
||||
{
|
||||
id: "sentry",
|
||||
name: "Sentry",
|
||||
description: "Issue analysis and debugging workflows for Sentry projects.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-sentry",
|
||||
},
|
||||
{
|
||||
id: "figma",
|
||||
name: "Figma",
|
||||
description: "Design data from Figma for implementation workflows.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-figma",
|
||||
},
|
||||
{
|
||||
id: "firebase",
|
||||
name: "Firebase",
|
||||
description: "Auth, Firestore, and Storage operations.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-firebase",
|
||||
},
|
||||
{
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
description: "Manage containers, images, volumes, and networks.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-docker",
|
||||
starterTransport: "stdio",
|
||||
},
|
||||
{
|
||||
id: "prisma",
|
||||
name: "Prisma",
|
||||
description: "Prisma database workflows and Postgres management.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-prisma",
|
||||
},
|
||||
{
|
||||
id: "mongodb",
|
||||
name: "MongoDB",
|
||||
description: "Read-only exploration, queries, and aggregation for MongoDB.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-mongodb",
|
||||
},
|
||||
{
|
||||
id: "puppeteer",
|
||||
name: "Puppeteer",
|
||||
description: "Browser automation via Puppeteer MCP.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-puppeteer",
|
||||
starterTransport: "stdio",
|
||||
},
|
||||
{
|
||||
id: "browserbase",
|
||||
name: "Browserbase",
|
||||
description: "Cloud browser automation workflows.",
|
||||
directoryUrl: "https://cursor.directory/plugins/mcp-browserbase",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,35 @@
|
||||
export function parseHeaderLines(text: string): Record<string, string> {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
for (const rawLine of text.split("\n")) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
|
||||
const idx = line.indexOf(":");
|
||||
if (idx <= 0) continue;
|
||||
|
||||
const name = line.slice(0, idx).trim();
|
||||
const value = line.slice(idx + 1).trim();
|
||||
if (name) headers[name] = value;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function parseEnvLines(text: string): Record<string, string> {
|
||||
const env: Record<string, string> = {};
|
||||
|
||||
for (const rawLine of text.split("\n")) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
|
||||
const idx = line.indexOf("=");
|
||||
if (idx <= 0) continue;
|
||||
|
||||
const name = line.slice(0, idx).trim();
|
||||
const value = line.slice(idx + 1).trim();
|
||||
if (name) env[name] = value;
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { createMCPClient, type MCPClient } from "@ai-sdk/mcp";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import type { ToolSet } from "ai";
|
||||
import type { McpServerConfig } from "../utils/settings";
|
||||
import { validateMcpServerConfig } from "./validate";
|
||||
|
||||
function mcpToolPrefix(server: McpServerConfig): string {
|
||||
return `mcp_${server.id.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
||||
}
|
||||
|
||||
function toTransport(server: McpServerConfig) {
|
||||
if (server.transport === "stdio") {
|
||||
return new StdioClientTransport({
|
||||
command: server.command ?? "",
|
||||
args: server.args,
|
||||
env: server.env,
|
||||
cwd: server.cwd,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: server.transport,
|
||||
url: server.url ?? "",
|
||||
headers: server.headers,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export interface McpToolBundle {
|
||||
tools: ToolSet;
|
||||
errors: string[];
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export async function buildMcpToolSet(servers: McpServerConfig[]): Promise<McpToolBundle> {
|
||||
const tools: ToolSet = {};
|
||||
const errors: string[] = [];
|
||||
const clients: MCPClient[] = [];
|
||||
|
||||
for (const server of servers) {
|
||||
if (!server.enabled) continue;
|
||||
|
||||
const validation = validateMcpServerConfig(server);
|
||||
if (!validation.ok) {
|
||||
errors.push(`${server.label}: ${validation.error}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await createMCPClient({
|
||||
transport: toTransport(server),
|
||||
name: `grok-cli-${server.id}`,
|
||||
version: "1.0.0",
|
||||
});
|
||||
clients.push(client);
|
||||
|
||||
const mcpTools = await client.tools();
|
||||
const prefix = mcpToolPrefix(server);
|
||||
|
||||
for (const [name, tool] of Object.entries(mcpTools)) {
|
||||
const prefixedName = `${prefix}__${name}`;
|
||||
tools[prefixedName] = {
|
||||
...tool,
|
||||
description: `[MCP ${server.label}] ${tool.description ?? name}`,
|
||||
};
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
errors.push(`${server.label}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tools,
|
||||
errors,
|
||||
async close() {
|
||||
await Promise.all(clients.map((client) => client.close().catch(() => {})));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { McpRemoteTransport, McpServerConfig } from "../utils/settings";
|
||||
|
||||
export function isRemoteTransport(value: string): value is McpRemoteTransport {
|
||||
return value === "http" || value === "sse";
|
||||
}
|
||||
|
||||
export function toMcpServerId(label: string): string {
|
||||
const base = label
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
|
||||
return base || "mcp-server";
|
||||
}
|
||||
|
||||
export function validateMcpServerConfig(server: McpServerConfig): { ok: true } | { ok: false; error: string } {
|
||||
if (!server.id.trim()) {
|
||||
return { ok: false, error: "Server id is required." };
|
||||
}
|
||||
|
||||
if (!server.label.trim()) {
|
||||
return { ok: false, error: "Server label is required." };
|
||||
}
|
||||
|
||||
if (isRemoteTransport(server.transport)) {
|
||||
if (!server.url?.trim()) {
|
||||
return { ok: false, error: "URL is required for HTTP/SSE MCP servers." };
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(server.url);
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
||||
return { ok: false, error: "URL must start with http:// or https://." };
|
||||
}
|
||||
} catch {
|
||||
return { ok: false, error: "URL is invalid." };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
if (!server.command?.trim()) {
|
||||
return { ok: false, error: "Command is required for stdio MCP servers." };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
+437
-9
@@ -5,6 +5,9 @@ import os from "os";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Agent } from "../agent/agent";
|
||||
import { getModelInfo, MODELS } from "../grok/models";
|
||||
import { POPULAR_MCP_CATALOG } from "../mcp/catalog";
|
||||
import { parseEnvLines, parseHeaderLines } from "../mcp/parse-headers";
|
||||
import { toMcpServerId, validateMcpServerConfig } from "../mcp/validate";
|
||||
import { createTelegramBridge, type TelegramBridgeHandle } from "../telegram/bridge";
|
||||
import { approvePairingCode } from "../telegram/pairing";
|
||||
import { createTurnCoordinator } from "../telegram/turn-coordinator";
|
||||
@@ -24,12 +27,18 @@ import { copyTextToHostClipboard } from "../utils/host-clipboard";
|
||||
import {
|
||||
getApiKey,
|
||||
getTelegramBotToken,
|
||||
loadMcpServers,
|
||||
loadUserSettings,
|
||||
type McpRemoteTransport,
|
||||
type McpServerConfig,
|
||||
saveMcpServers,
|
||||
saveProjectSettings,
|
||||
saveUserSettings,
|
||||
} from "../utils/settings";
|
||||
import { discoverSkills, formatSkillsForChat } from "../utils/skills";
|
||||
import { Markdown } from "./markdown";
|
||||
import { buildMcpBrowseRows, McpBrowserModal, McpEditorModal } from "./mcp-modal";
|
||||
import { createEmptyMcpEditorDraft, type McpEditorDraft, type McpEditorField } from "./mcp-modal-types";
|
||||
import {
|
||||
formatPlanAnswers,
|
||||
initialPlanQuestionsState,
|
||||
@@ -225,7 +234,7 @@ const SLASH_MENU_ITEMS: SlashMenuItem[] = [
|
||||
{ id: "exit", label: "exit", description: "Quit the CLI" },
|
||||
{ id: "help", label: "help", description: "Show available commands" },
|
||||
{ id: "remote-control", label: "remote-control", description: "Remote control" },
|
||||
{ id: "mcps", label: "mcps", description: "Manage MCP servers" },
|
||||
{ id: "mcp", label: "mcp", description: "Manage MCP servers" },
|
||||
{ id: "models", label: "models", description: "Select a model" },
|
||||
{ id: "new", label: "new session", description: "Start a new session" },
|
||||
{ id: "review", label: "review", description: "Review recent changes" },
|
||||
@@ -236,6 +245,9 @@ const CONNECT_CHANNELS: { id: string; label: string; description: string }[] = [
|
||||
{ id: "telegram", label: "Telegram", description: "Chat with Grok from Telegram" },
|
||||
];
|
||||
|
||||
const MCP_REMOTE_FIELDS: McpEditorField[] = ["transport", "label", "url", "headers", "env"];
|
||||
const MCP_STDIO_FIELDS: McpEditorField[] = ["transport", "label", "command", "args", "cwd", "env"];
|
||||
|
||||
export interface AppStartupConfig {
|
||||
apiKey: string | undefined;
|
||||
baseURL: string;
|
||||
@@ -311,6 +323,25 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
const showConnectModalRef = useRef(false);
|
||||
const showTelegramTokenModalRef = useRef(false);
|
||||
const showTelegramPairModalRef = useRef(false);
|
||||
const [showMcpModal, setShowMcpModal] = useState(false);
|
||||
const [showMcpEditor, setShowMcpEditor] = useState(false);
|
||||
const [mcpSearchQuery, setMcpSearchQuery] = useState("");
|
||||
const [mcpModalIndex, setMcpModalIndex] = useState(0);
|
||||
const [mcpServers, setMcpServers] = useState<McpServerConfig[]>(() => loadMcpServers());
|
||||
const [mcpEditorDraft, setMcpEditorDraft] = useState<McpEditorDraft>(createEmptyMcpEditorDraft());
|
||||
const [mcpEditorField, setMcpEditorField] = useState<McpEditorField>("transport");
|
||||
const [mcpEditorSyncKey, setMcpEditorSyncKey] = useState(0);
|
||||
const [mcpEditorError, setMcpEditorError] = useState<string | null>(null);
|
||||
const [editingMcpId, setEditingMcpId] = useState<string | null>(null);
|
||||
const showMcpModalRef = useRef(false);
|
||||
const showMcpEditorRef = useRef(false);
|
||||
const mcpLabelRef = useRef<TextareaRenderable>(null);
|
||||
const mcpUrlRef = useRef<TextareaRenderable>(null);
|
||||
const mcpHeadersRef = useRef<TextareaRenderable>(null);
|
||||
const mcpCommandRef = useRef<TextareaRenderable>(null);
|
||||
const mcpArgsRef = useRef<TextareaRenderable>(null);
|
||||
const mcpCwdRef = useRef<TextareaRenderable>(null);
|
||||
const mcpEnvRef = useRef<TextareaRenderable>(null);
|
||||
|
||||
const setMode = useCallback(
|
||||
(m: AgentMode) => {
|
||||
@@ -356,6 +387,278 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
item.description.toLowerCase().includes(slashSearchQuery.toLowerCase()),
|
||||
)
|
||||
: SLASH_MENU_ITEMS;
|
||||
const mcpRows = buildMcpBrowseRows(mcpServers, POPULAR_MCP_CATALOG, mcpSearchQuery);
|
||||
const mcpEditorFields = mcpEditorDraft.transport === "stdio" ? MCP_STDIO_FIELDS : MCP_REMOTE_FIELDS;
|
||||
|
||||
const syncStoredMcpServers = useCallback((servers: McpServerConfig[]) => {
|
||||
setMcpServers(servers);
|
||||
saveMcpServers(servers);
|
||||
}, []);
|
||||
|
||||
const snapshotMcpEditorDraft = useCallback((): McpEditorDraft => {
|
||||
return {
|
||||
...mcpEditorDraft,
|
||||
label: mcpLabelRef.current?.plainText ?? mcpEditorDraft.label,
|
||||
url: mcpUrlRef.current?.plainText ?? mcpEditorDraft.url,
|
||||
headersText: mcpHeadersRef.current?.plainText ?? mcpEditorDraft.headersText,
|
||||
command: mcpCommandRef.current?.plainText ?? mcpEditorDraft.command,
|
||||
argsText: mcpArgsRef.current?.plainText ?? mcpEditorDraft.argsText,
|
||||
cwd: mcpCwdRef.current?.plainText ?? mcpEditorDraft.cwd,
|
||||
envText: mcpEnvRef.current?.plainText ?? mcpEditorDraft.envText,
|
||||
};
|
||||
}, [mcpEditorDraft]);
|
||||
|
||||
const openMcpModal = useCallback(() => {
|
||||
const latest = loadMcpServers();
|
||||
setMcpServers(latest);
|
||||
setMcpSearchQuery("");
|
||||
setMcpModalIndex(0);
|
||||
setShowMcpModal(true);
|
||||
setShowMcpEditor(false);
|
||||
setEditingMcpId(null);
|
||||
setMcpEditorError(null);
|
||||
}, []);
|
||||
|
||||
const openMcpEditor = useCallback((draft: McpEditorDraft, editingId: string | null = null) => {
|
||||
setMcpEditorDraft(draft);
|
||||
setEditingMcpId(editingId);
|
||||
setMcpEditorField("transport");
|
||||
setMcpEditorError(null);
|
||||
setMcpEditorSyncKey((n) => n + 1);
|
||||
setShowMcpEditor(true);
|
||||
setShowMcpModal(true);
|
||||
}, []);
|
||||
|
||||
const openCatalogMcp = useCallback(
|
||||
(entry: (typeof POPULAR_MCP_CATALOG)[number]) => {
|
||||
const existing = mcpServers.find((server) => toMcpServerId(server.id) === toMcpServerId(entry.id));
|
||||
if (existing) {
|
||||
openMcpEditor(
|
||||
{
|
||||
label: existing.label,
|
||||
transport: existing.transport,
|
||||
url: existing.url ?? "",
|
||||
headersText: Object.entries(existing.headers ?? {})
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join("\n"),
|
||||
command: existing.command ?? "",
|
||||
argsText: (existing.args ?? []).join("\n"),
|
||||
cwd: existing.cwd ?? "",
|
||||
envText: Object.entries(existing.env ?? {})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("\n"),
|
||||
},
|
||||
existing.id,
|
||||
);
|
||||
return;
|
||||
}
|
||||
openMcpEditor({
|
||||
...createEmptyMcpEditorDraft(),
|
||||
label: entry.name,
|
||||
transport: entry.starterTransport ?? "stdio",
|
||||
});
|
||||
},
|
||||
[mcpServers, openMcpEditor],
|
||||
);
|
||||
|
||||
const editSavedMcp = useCallback(
|
||||
(server: McpServerConfig) => {
|
||||
openMcpEditor(
|
||||
{
|
||||
label: server.label,
|
||||
transport: server.transport,
|
||||
url: server.url ?? "",
|
||||
headersText: Object.entries(server.headers ?? {})
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join("\n"),
|
||||
command: server.command ?? "",
|
||||
argsText: (server.args ?? []).join("\n"),
|
||||
cwd: server.cwd ?? "",
|
||||
envText: Object.entries(server.env ?? {})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("\n"),
|
||||
},
|
||||
server.id,
|
||||
);
|
||||
},
|
||||
[openMcpEditor],
|
||||
);
|
||||
|
||||
const toggleSavedMcp = useCallback(
|
||||
(server: McpServerConfig) => {
|
||||
syncStoredMcpServers(
|
||||
mcpServers.map((item) => (item.id === server.id ? { ...item, enabled: !item.enabled } : item)),
|
||||
);
|
||||
},
|
||||
[mcpServers, syncStoredMcpServers],
|
||||
);
|
||||
|
||||
const deleteSavedMcp = useCallback(
|
||||
(server: McpServerConfig) => {
|
||||
syncStoredMcpServers(mcpServers.filter((item) => item.id !== server.id));
|
||||
setMcpModalIndex((idx) => Math.max(0, Math.min(idx, Math.max(0, mcpRows.length - 2))));
|
||||
},
|
||||
[mcpRows.length, mcpServers, syncStoredMcpServers],
|
||||
);
|
||||
|
||||
const submitMcpEditor = useCallback(() => {
|
||||
const draft: McpEditorDraft = {
|
||||
label: mcpLabelRef.current?.plainText || "",
|
||||
transport: mcpEditorDraft.transport,
|
||||
url: mcpUrlRef.current?.plainText || "",
|
||||
headersText: mcpHeadersRef.current?.plainText || "",
|
||||
command: mcpCommandRef.current?.plainText || "",
|
||||
argsText: mcpArgsRef.current?.plainText || "",
|
||||
cwd: mcpCwdRef.current?.plainText || "",
|
||||
envText: mcpEnvRef.current?.plainText || "",
|
||||
};
|
||||
|
||||
const baseId = toMcpServerId(draft.label);
|
||||
const currentServers = loadMcpServers();
|
||||
|
||||
const conflictingServer = currentServers.find((s) => s.id === baseId && s.id !== editingMcpId);
|
||||
if (conflictingServer) {
|
||||
setMcpEditorError(`Only one protocol is supported per MCP. Edit "${conflictingServer.label}" instead.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = editingMcpId ?? baseId;
|
||||
|
||||
const server: McpServerConfig = {
|
||||
id,
|
||||
label: draft.label.trim(),
|
||||
enabled: true,
|
||||
transport: draft.transport,
|
||||
...(draft.transport === "stdio"
|
||||
? {
|
||||
command: draft.command.trim(),
|
||||
args: draft.argsText
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean),
|
||||
cwd: draft.cwd.trim() || undefined,
|
||||
env: Object.keys(parseEnvLines(draft.envText)).length ? parseEnvLines(draft.envText) : undefined,
|
||||
}
|
||||
: {
|
||||
url: draft.url.trim(),
|
||||
headers: Object.keys(parseHeaderLines(draft.headersText)).length
|
||||
? parseHeaderLines(draft.headersText)
|
||||
: undefined,
|
||||
env: Object.keys(parseEnvLines(draft.envText)).length ? parseEnvLines(draft.envText) : undefined,
|
||||
}),
|
||||
};
|
||||
|
||||
const validation = validateMcpServerConfig(server);
|
||||
if (!validation.ok) {
|
||||
setMcpEditorError(validation.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextServers = editingMcpId
|
||||
? currentServers.map((item) =>
|
||||
item.id === editingMcpId ? { ...server, id: editingMcpId, enabled: item.enabled } : item,
|
||||
)
|
||||
: [...currentServers, server];
|
||||
saveMcpServers(nextServers);
|
||||
setMcpServers(nextServers);
|
||||
setShowMcpEditor(false);
|
||||
setEditingMcpId(null);
|
||||
setMcpEditorError(null);
|
||||
setMcpSearchQuery("");
|
||||
setMcpModalIndex(
|
||||
Math.max(
|
||||
0,
|
||||
nextServers.findIndex((item) => item.id === (editingMcpId ?? server.id)),
|
||||
),
|
||||
);
|
||||
}, [editingMcpId, mcpEditorDraft.transport]);
|
||||
|
||||
const cycleMcpEditorTransport = useCallback(
|
||||
(direction: 1 | -1 = 1) => {
|
||||
const draft = snapshotMcpEditorDraft();
|
||||
const order: Array<McpRemoteTransport | "stdio"> = ["stdio", "http", "sse"];
|
||||
const currentIndex = order.indexOf(draft.transport);
|
||||
const nextTransport = order[(currentIndex + direction + order.length) % order.length];
|
||||
const nextDraft = { ...draft, transport: nextTransport };
|
||||
setMcpEditorDraft(nextDraft);
|
||||
setMcpEditorField("transport");
|
||||
setMcpEditorSyncKey((n) => n + 1);
|
||||
|
||||
if (!editingMcpId) return;
|
||||
|
||||
const existing = mcpServers.find((server) => server.id === editingMcpId);
|
||||
if (!existing) return;
|
||||
|
||||
const optimisticServer: McpServerConfig = {
|
||||
id: existing.id,
|
||||
label: nextDraft.label.trim() || existing.label,
|
||||
enabled: existing.enabled,
|
||||
transport: nextTransport,
|
||||
...(nextTransport === "stdio"
|
||||
? {
|
||||
command: nextDraft.command.trim() || existing.command,
|
||||
args: nextDraft.argsText
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean),
|
||||
cwd: nextDraft.cwd.trim() || undefined,
|
||||
env: Object.keys(parseEnvLines(nextDraft.envText)).length ? parseEnvLines(nextDraft.envText) : undefined,
|
||||
}
|
||||
: {
|
||||
url: nextDraft.url.trim() || existing.url,
|
||||
headers: Object.keys(parseHeaderLines(nextDraft.headersText)).length
|
||||
? parseHeaderLines(nextDraft.headersText)
|
||||
: undefined,
|
||||
env: Object.keys(parseEnvLines(nextDraft.envText)).length ? parseEnvLines(nextDraft.envText) : undefined,
|
||||
}),
|
||||
};
|
||||
|
||||
syncStoredMcpServers(mcpServers.map((server) => (server.id === editingMcpId ? optimisticServer : server)));
|
||||
},
|
||||
[editingMcpId, mcpServers, snapshotMcpEditorDraft, syncStoredMcpServers],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showMcpEditor || !editingMcpId) return;
|
||||
|
||||
const existing = mcpServers.find((server) => server.id === editingMcpId);
|
||||
if (!existing) return;
|
||||
if (existing.transport === mcpEditorDraft.transport) return;
|
||||
|
||||
const syncedServer: McpServerConfig = {
|
||||
id: existing.id,
|
||||
label: mcpEditorDraft.label.trim() || existing.label,
|
||||
enabled: existing.enabled,
|
||||
transport: mcpEditorDraft.transport,
|
||||
...(mcpEditorDraft.transport === "stdio"
|
||||
? {
|
||||
command: mcpEditorDraft.command.trim() || undefined,
|
||||
args: mcpEditorDraft.argsText
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean),
|
||||
cwd: mcpEditorDraft.cwd.trim() || undefined,
|
||||
env: Object.keys(parseEnvLines(mcpEditorDraft.envText)).length
|
||||
? parseEnvLines(mcpEditorDraft.envText)
|
||||
: undefined,
|
||||
}
|
||||
: {
|
||||
url: mcpEditorDraft.url.trim() || undefined,
|
||||
headers: Object.keys(parseHeaderLines(mcpEditorDraft.headersText)).length
|
||||
? parseHeaderLines(mcpEditorDraft.headersText)
|
||||
: undefined,
|
||||
env: Object.keys(parseEnvLines(mcpEditorDraft.envText)).length
|
||||
? parseEnvLines(mcpEditorDraft.envText)
|
||||
: undefined,
|
||||
}),
|
||||
};
|
||||
|
||||
syncStoredMcpServers(mcpServers.map((server) => (server.id === editingMcpId ? syncedServer : server)));
|
||||
}, [editingMcpId, mcpEditorDraft, mcpServers, showMcpEditor, syncStoredMcpServers]);
|
||||
|
||||
useEffect(() => {
|
||||
setMcpModalIndex((idx) => Math.max(0, Math.min(idx, Math.max(0, mcpRows.length - 1))));
|
||||
}, [mcpRows.length]);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
try {
|
||||
@@ -601,6 +904,12 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
useEffect(() => {
|
||||
showTelegramPairModalRef.current = showTelegramPairModal;
|
||||
}, [showTelegramPairModal]);
|
||||
useEffect(() => {
|
||||
showMcpModalRef.current = showMcpModal;
|
||||
}, [showMcpModal]);
|
||||
useEffect(() => {
|
||||
showMcpEditorRef.current = showMcpEditor;
|
||||
}, [showMcpEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -922,13 +1231,17 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
setShowConnectModal(true);
|
||||
return true;
|
||||
}
|
||||
if (c === "/mcp" || c === "/mcps") {
|
||||
openMcpModal();
|
||||
return true;
|
||||
}
|
||||
if (c === "/quit" || c === "/exit" || c === "/q") {
|
||||
handleExit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[handleExit, resetToNewSession],
|
||||
[handleExit, openMcpModal, resetToNewSession],
|
||||
);
|
||||
|
||||
const handleSlashMenuSelect = useCallback(
|
||||
@@ -971,11 +1284,8 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
},
|
||||
]);
|
||||
break;
|
||||
case "mcps":
|
||||
setMessages((p) => [
|
||||
...p,
|
||||
{ type: "assistant", content: "MCP server management coming soon.", timestamp: new Date() },
|
||||
]);
|
||||
case "mcp":
|
||||
openMcpModal();
|
||||
break;
|
||||
case "review":
|
||||
setMessages((p) => [
|
||||
@@ -985,10 +1295,10 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
break;
|
||||
}
|
||||
},
|
||||
[agent, handleExit, resetToNewSession],
|
||||
[agent, handleExit, openMcpModal, resetToNewSession],
|
||||
);
|
||||
|
||||
const blockPrompt = showConnectModal || showTelegramTokenModal || showTelegramPairModal;
|
||||
const blockPrompt = showConnectModal || showTelegramTokenModal || showTelegramPairModal || showMcpModal;
|
||||
|
||||
const showPlanPanel = !!activePlan?.questions?.length;
|
||||
const planQuestions = activePlan?.questions ?? [];
|
||||
@@ -1178,6 +1488,83 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
|
||||
return;
|
||||
}
|
||||
if (showMcpEditorRef.current) {
|
||||
if (key.name === "escape") {
|
||||
setShowMcpEditor(false);
|
||||
setMcpEditorError(null);
|
||||
setMcpSearchQuery("");
|
||||
return;
|
||||
}
|
||||
if (key.name === "return") {
|
||||
submitMcpEditor();
|
||||
return;
|
||||
}
|
||||
if (mcpEditorField === "transport" && (key.name === "left" || key.name === "right")) {
|
||||
cycleMcpEditorTransport(key.name === "left" ? -1 : 1);
|
||||
return;
|
||||
}
|
||||
if (key.name === "tab") {
|
||||
const idx = mcpEditorFields.indexOf(mcpEditorField);
|
||||
const nextIdx = (idx + (key.shift ? -1 : 1) + mcpEditorFields.length) % mcpEditorFields.length;
|
||||
setMcpEditorField(mcpEditorFields[nextIdx]);
|
||||
return;
|
||||
}
|
||||
if (mcpEditorField === "transport") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (showMcpModalRef.current) {
|
||||
const row = mcpRows[mcpModalIndex];
|
||||
if (key.name === "escape") {
|
||||
setShowMcpEditor(false);
|
||||
setShowMcpModal(false);
|
||||
setMcpSearchQuery("");
|
||||
setEditingMcpId(null);
|
||||
setMcpEditorError(null);
|
||||
return;
|
||||
}
|
||||
if (key.name === "up") {
|
||||
setMcpModalIndex((i) => Math.max(0, i - 1));
|
||||
return;
|
||||
}
|
||||
if (key.name === "down") {
|
||||
setMcpModalIndex((i) => Math.min(mcpRows.length - 1, i + 1));
|
||||
return;
|
||||
}
|
||||
if (key.name === "return") {
|
||||
if (row?.kind === "server") {
|
||||
toggleSavedMcp(row.server);
|
||||
} else if (row?.kind === "catalog") {
|
||||
openCatalogMcp(row.entry);
|
||||
} else {
|
||||
openMcpEditor(createEmptyMcpEditorDraft());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key.name === "a" && key.ctrl) {
|
||||
openMcpEditor(createEmptyMcpEditorDraft());
|
||||
return;
|
||||
}
|
||||
if (key.name === "e" && key.ctrl && row?.kind === "server") {
|
||||
editSavedMcp(row.server);
|
||||
return;
|
||||
}
|
||||
if (key.name === "x" && key.ctrl && row?.kind === "server") {
|
||||
deleteSavedMcp(row.server);
|
||||
return;
|
||||
}
|
||||
if (key.name === "backspace") {
|
||||
setMcpSearchQuery((q) => q.slice(0, -1));
|
||||
setMcpModalIndex(0);
|
||||
return;
|
||||
}
|
||||
if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
|
||||
setMcpSearchQuery((q) => q + key.sequence);
|
||||
setMcpModalIndex(0);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (showTelegramTokenModalRef.current) {
|
||||
if (key.name === "escape") {
|
||||
setShowTelegramTokenModal(false);
|
||||
@@ -1371,7 +1758,10 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
closeApiKeyModal,
|
||||
connectModalIndex,
|
||||
cycleMode,
|
||||
cycleMcpEditorTransport,
|
||||
deleteSavedMcp,
|
||||
dismissPlan,
|
||||
editSavedMcp,
|
||||
filteredModelIds,
|
||||
filteredSlashItems,
|
||||
handleExit,
|
||||
@@ -1381,10 +1771,17 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
isPlanConfirmTab,
|
||||
isProcessing,
|
||||
isSinglePlan,
|
||||
mcpEditorField,
|
||||
mcpEditorFields,
|
||||
mcpModalIndex,
|
||||
mcpRows,
|
||||
modelPickerIndex,
|
||||
openApiKeyModal,
|
||||
openCatalogMcp,
|
||||
openMcpEditor,
|
||||
submitTelegramPair,
|
||||
submitTelegramToken,
|
||||
submitMcpEditor,
|
||||
planQuestions,
|
||||
planTabCount,
|
||||
pqs,
|
||||
@@ -1395,6 +1792,7 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
submitApiKey,
|
||||
submitPlanAnswers,
|
||||
copyTuiSelectionToHost,
|
||||
toggleSavedMcp,
|
||||
],
|
||||
);
|
||||
useKeyboard(handleKey);
|
||||
@@ -1606,6 +2004,36 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
filteredItems={filteredSlashItems}
|
||||
/>
|
||||
)}
|
||||
{showMcpModal && !showMcpEditor && (
|
||||
<McpBrowserModal
|
||||
t={t}
|
||||
width={width}
|
||||
height={height}
|
||||
selectedIndex={mcpModalIndex}
|
||||
searchQuery={mcpSearchQuery}
|
||||
rows={mcpRows}
|
||||
/>
|
||||
)}
|
||||
{showMcpEditor && (
|
||||
<McpEditorModal
|
||||
t={t}
|
||||
width={width}
|
||||
height={height}
|
||||
draft={mcpEditorDraft}
|
||||
focusedField={mcpEditorField}
|
||||
syncKey={mcpEditorSyncKey}
|
||||
error={mcpEditorError}
|
||||
title={editingMcpId ? "Edit MCP Server" : "Add MCP Server"}
|
||||
labelRef={mcpLabelRef}
|
||||
urlRef={mcpUrlRef}
|
||||
headersRef={mcpHeadersRef}
|
||||
commandRef={mcpCommandRef}
|
||||
argsRef={mcpArgsRef}
|
||||
cwdRef={mcpCwdRef}
|
||||
envRef={mcpEnvRef}
|
||||
onSubmit={submitMcpEditor}
|
||||
/>
|
||||
)}
|
||||
{showModelPicker && (
|
||||
<ModelPickerModal
|
||||
t={t}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { McpCatalogEntry } from "../mcp/catalog";
|
||||
import type { McpRemoteTransport, McpServerConfig } from "../utils/settings";
|
||||
|
||||
export type McpBrowserRow =
|
||||
| { kind: "server"; server: McpServerConfig; description?: string }
|
||||
| { kind: "catalog"; entry: McpCatalogEntry }
|
||||
| { kind: "add" };
|
||||
|
||||
export type McpEditorField = "transport" | "label" | "url" | "headers" | "command" | "args" | "cwd" | "env";
|
||||
|
||||
export interface McpEditorDraft {
|
||||
label: string;
|
||||
transport: McpRemoteTransport | "stdio";
|
||||
url: string;
|
||||
headersText: string;
|
||||
command: string;
|
||||
argsText: string;
|
||||
cwd: string;
|
||||
envText: string;
|
||||
}
|
||||
|
||||
export function createEmptyMcpEditorDraft(): McpEditorDraft {
|
||||
return {
|
||||
label: "",
|
||||
transport: "stdio",
|
||||
url: "",
|
||||
headersText: "",
|
||||
command: "",
|
||||
argsText: "",
|
||||
cwd: "",
|
||||
envText: "",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core";
|
||||
import { type RefObject, useEffect, useRef } from "react";
|
||||
import type { McpCatalogEntry } from "../mcp/catalog";
|
||||
import { toMcpServerId } from "../mcp/validate";
|
||||
import type { McpServerConfig } from "../utils/settings";
|
||||
import type { McpBrowserRow, McpEditorDraft, McpEditorField } from "./mcp-modal-types";
|
||||
import type { Theme } from "./theme";
|
||||
|
||||
const EDITOR_KEYBINDINGS = [{ name: "return", action: "submit" as const }];
|
||||
|
||||
function bottomAlignedModalTop(height: number, panelHeight: number): number {
|
||||
return Math.max(2, Math.floor((height - panelHeight) / 2));
|
||||
}
|
||||
|
||||
export function buildMcpBrowseRows(
|
||||
servers: McpServerConfig[],
|
||||
catalog: McpCatalogEntry[],
|
||||
query: string,
|
||||
): McpBrowserRow[] {
|
||||
const q = query.trim().toLowerCase();
|
||||
const catalogById = new Map(catalog.map((entry) => [toMcpServerId(entry.id), entry] as const));
|
||||
const filteredServers = q
|
||||
? servers.filter(
|
||||
(server) =>
|
||||
server.label.toLowerCase().includes(q) ||
|
||||
server.id.toLowerCase().includes(q) ||
|
||||
server.transport.toLowerCase().includes(q),
|
||||
)
|
||||
: servers;
|
||||
const savedIds = new Set(servers.map((server) => toMcpServerId(server.id || server.label)));
|
||||
const filteredCatalog = (
|
||||
q
|
||||
? catalog.filter(
|
||||
(entry) =>
|
||||
entry.name.toLowerCase().includes(q) ||
|
||||
entry.id.toLowerCase().includes(q) ||
|
||||
entry.description.toLowerCase().includes(q),
|
||||
)
|
||||
: catalog
|
||||
).filter((entry) => !savedIds.has(toMcpServerId(entry.id)));
|
||||
|
||||
return [
|
||||
...filteredServers.map((server) => {
|
||||
const catalogEntry = catalogById.get(toMcpServerId(server.id || server.label));
|
||||
return {
|
||||
kind: "server",
|
||||
server,
|
||||
description: catalogEntry?.description ?? "Custom MCP server",
|
||||
} satisfies McpBrowserRow;
|
||||
}),
|
||||
...filteredCatalog.map((entry) => ({ kind: "catalog", entry }) satisfies McpBrowserRow),
|
||||
{ kind: "add" },
|
||||
];
|
||||
}
|
||||
|
||||
export function McpBrowserModal({
|
||||
t,
|
||||
width,
|
||||
height,
|
||||
selectedIndex,
|
||||
searchQuery,
|
||||
rows,
|
||||
}: {
|
||||
t: Theme;
|
||||
width: number;
|
||||
height: number;
|
||||
selectedIndex: number;
|
||||
searchQuery: string;
|
||||
rows: McpBrowserRow[];
|
||||
}) {
|
||||
const listRef = useRef<ScrollBoxRenderable>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const selected = rows[selectedIndex];
|
||||
if (!selected) return;
|
||||
|
||||
if (selected.kind === "server") {
|
||||
listRef.current?.scrollChildIntoView(`mcp-server-${selected.server.id}`);
|
||||
} else if (selected.kind === "catalog") {
|
||||
listRef.current?.scrollChildIntoView(`mcp-catalog-${selected.entry.id}`);
|
||||
} else {
|
||||
listRef.current?.scrollChildIntoView("mcp-add");
|
||||
}
|
||||
}, [rows, selectedIndex]);
|
||||
|
||||
const itemCount = Math.max(rows.length, 1);
|
||||
const contentHeight = itemCount + 7;
|
||||
const maxHeight = Math.floor(height * 0.68);
|
||||
const panelHeight = Math.min(contentHeight, maxHeight);
|
||||
const overlayBg = "#000000cc" as string;
|
||||
|
||||
return (
|
||||
<box
|
||||
position="absolute"
|
||||
left={0}
|
||||
top={0}
|
||||
width={width}
|
||||
height={height}
|
||||
alignItems="center"
|
||||
paddingTop={bottomAlignedModalTop(height, panelHeight)}
|
||||
backgroundColor={overlayBg}
|
||||
>
|
||||
<box
|
||||
width={Math.min(96, width - 4)}
|
||||
height={panelHeight}
|
||||
backgroundColor={t.backgroundPanel}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
flexDirection="column"
|
||||
>
|
||||
<box flexShrink={0} flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<text fg={t.primary}>
|
||||
<b>{"MCP Servers"}</b>
|
||||
</text>
|
||||
<text fg={t.textMuted}>{"esc"}</text>
|
||||
</box>
|
||||
<box flexShrink={0} paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
|
||||
<text fg={t.text}>{searchQuery || <span style={{ fg: t.textMuted }}>{"Search servers..."}</span>}</text>
|
||||
</box>
|
||||
<scrollbox ref={listRef} flexGrow={1} minHeight={0}>
|
||||
{rows.map((row, idx) => {
|
||||
const selected = idx === selectedIndex;
|
||||
|
||||
if (row.kind === "server") {
|
||||
const enabledColor = row.server.enabled ? t.diffAddedFg : selected ? t.selected : t.text;
|
||||
return (
|
||||
<box
|
||||
key={`server-${row.server.id}`}
|
||||
id={`mcp-server-${row.server.id}`}
|
||||
backgroundColor={selected ? t.selectedBg : undefined}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={enabledColor}>
|
||||
{row.server.enabled ? "■ " : "□ "}
|
||||
{row.server.label}
|
||||
</text>
|
||||
<text fg={row.server.enabled ? t.diffAddedFg : t.textMuted}>{row.server.transport}</text>
|
||||
</box>
|
||||
<text fg={t.textMuted}>{row.description}</text>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
|
||||
if (row.kind === "catalog") {
|
||||
return (
|
||||
<box
|
||||
key={`catalog-${row.entry.id}`}
|
||||
id={`mcp-catalog-${row.entry.id}`}
|
||||
backgroundColor={selected ? t.selectedBg : undefined}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={selected ? t.selected : t.text}>
|
||||
{"□ "}
|
||||
{row.entry.name}
|
||||
</text>
|
||||
<text fg={t.textMuted}>{"Popular"}</text>
|
||||
</box>
|
||||
<text fg={t.textMuted}>{row.entry.description}</text>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<box
|
||||
key="mcp-add"
|
||||
id="mcp-add"
|
||||
backgroundColor={selected ? t.selectedBg : undefined}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
>
|
||||
<text fg={selected ? t.selected : t.primary}>
|
||||
<b>{"□ Add Custom MCP"}</b>
|
||||
</text>
|
||||
</box>
|
||||
);
|
||||
})}
|
||||
</scrollbox>
|
||||
<box flexShrink={0} paddingLeft={2} paddingRight={2} paddingTop={2} paddingBottom={1}>
|
||||
<text>
|
||||
<span style={{ fg: t.primary }}>{"enter "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"toggle · "}</span>
|
||||
<span style={{ fg: t.primary }}>{"ctrl+e "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"edit · "}</span>
|
||||
<span style={{ fg: t.primary }}>{"ctrl+a "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"add · "}</span>
|
||||
<span style={{ fg: t.primary }}>{"ctrl+x "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"delete"}</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
|
||||
function syncRef(ref: RefObject<TextareaRenderable | null>, value: string): void {
|
||||
ref.current?.clear();
|
||||
if (value) {
|
||||
ref.current?.insertText(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function McpEditorModal({
|
||||
t,
|
||||
width,
|
||||
height,
|
||||
draft,
|
||||
focusedField,
|
||||
syncKey,
|
||||
error,
|
||||
title,
|
||||
labelRef,
|
||||
urlRef,
|
||||
headersRef,
|
||||
commandRef,
|
||||
argsRef,
|
||||
cwdRef,
|
||||
envRef,
|
||||
onSubmit,
|
||||
}: {
|
||||
t: Theme;
|
||||
width: number;
|
||||
height: number;
|
||||
draft: McpEditorDraft;
|
||||
focusedField: McpEditorField;
|
||||
syncKey: number;
|
||||
error: string | null;
|
||||
title: string;
|
||||
labelRef: RefObject<TextareaRenderable | null>;
|
||||
urlRef: RefObject<TextareaRenderable | null>;
|
||||
headersRef: RefObject<TextareaRenderable | null>;
|
||||
commandRef: RefObject<TextareaRenderable | null>;
|
||||
argsRef: RefObject<TextareaRenderable | null>;
|
||||
cwdRef: RefObject<TextareaRenderable | null>;
|
||||
envRef: RefObject<TextareaRenderable | null>;
|
||||
onSubmit: () => void;
|
||||
}) {
|
||||
const panelHeight = Math.min(30, Math.floor(height * 0.82));
|
||||
const overlayBg = "#000000cc" as string;
|
||||
const isRemote = draft.transport === "http" || draft.transport === "sse";
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: syncKey is an intentional cache-bust prop
|
||||
useEffect(() => {
|
||||
syncRef(labelRef, draft.label);
|
||||
syncRef(urlRef, draft.url);
|
||||
syncRef(headersRef, draft.headersText);
|
||||
syncRef(commandRef, draft.command);
|
||||
syncRef(argsRef, draft.argsText);
|
||||
syncRef(cwdRef, draft.cwd);
|
||||
syncRef(envRef, draft.envText);
|
||||
}, [draft, syncKey, labelRef, urlRef, headersRef, commandRef, argsRef, cwdRef, envRef]);
|
||||
|
||||
return (
|
||||
<box
|
||||
position="absolute"
|
||||
left={0}
|
||||
top={0}
|
||||
width={width}
|
||||
height={height}
|
||||
alignItems="center"
|
||||
paddingTop={bottomAlignedModalTop(height, panelHeight)}
|
||||
backgroundColor={overlayBg}
|
||||
>
|
||||
<box
|
||||
width={Math.min(86, width - 6)}
|
||||
height={panelHeight}
|
||||
backgroundColor={t.backgroundPanel}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
flexDirection="column"
|
||||
>
|
||||
<box flexShrink={0} flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<text fg={t.primary}>
|
||||
<b>{title}</b>
|
||||
</text>
|
||||
<text fg={t.textMuted}>{"esc"}</text>
|
||||
</box>
|
||||
<box flexShrink={0} paddingLeft={2} paddingRight={2} paddingTop={1}>
|
||||
<box flexDirection="row" gap={1} alignItems="center">
|
||||
<text fg={focusedField === "transport" ? t.primary : t.textMuted}>{"Transport"}</text>
|
||||
{(["stdio", "http", "sse"] as const).map((option) => {
|
||||
const active = draft.transport === option;
|
||||
const focused = focusedField === "transport";
|
||||
return (
|
||||
<box
|
||||
key={option}
|
||||
backgroundColor={active ? (focused ? t.selectedBg : t.backgroundElement) : undefined}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={active ? (focused ? t.primary : t.text) : t.textMuted}>{option}</text>
|
||||
</box>
|
||||
);
|
||||
})}
|
||||
</box>
|
||||
</box>
|
||||
<scrollbox flexGrow={1} minHeight={0} paddingLeft={2} paddingRight={2} paddingTop={1}>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "label" ? t.primary : t.textMuted}>{"Label"}</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={labelRef}
|
||||
focused={focusedField === "label"}
|
||||
placeholder="GitHub MCP"
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={1}
|
||||
maxHeight={2}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
{isRemote ? (
|
||||
<>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "url" ? t.primary : t.textMuted}>{"URL"}</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={urlRef}
|
||||
focused={focusedField === "url"}
|
||||
placeholder="https://example.com/mcp"
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={1}
|
||||
maxHeight={3}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "headers" ? t.primary : t.textMuted}>
|
||||
{"Headers (one Header: value per line)"}
|
||||
</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={headersRef}
|
||||
focused={focusedField === "headers"}
|
||||
placeholder="Authorization: Bearer ..."
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={2}
|
||||
maxHeight={6}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "command" ? t.primary : t.textMuted}>{"Command"}</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={commandRef}
|
||||
focused={focusedField === "command"}
|
||||
placeholder="npx"
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={1}
|
||||
maxHeight={2}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "args" ? t.primary : t.textMuted}>{"Arguments (one per line)"}</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={argsRef}
|
||||
focused={focusedField === "args"}
|
||||
placeholder={"-y\n@scope/server"}
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={2}
|
||||
maxHeight={6}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "cwd" ? t.primary : t.textMuted}>{"Working Directory (optional)"}</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={cwdRef}
|
||||
focused={focusedField === "cwd"}
|
||||
placeholder="/path/to/project"
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={1}
|
||||
maxHeight={2}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
</>
|
||||
)}
|
||||
|
||||
<box paddingBottom={1}>
|
||||
<text fg={focusedField === "env" ? t.primary : t.textMuted}>{"Extra Env (one KEY=value per line)"}</text>
|
||||
<box backgroundColor={t.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<textarea
|
||||
ref={envRef}
|
||||
focused={focusedField === "env"}
|
||||
placeholder="API_KEY=..."
|
||||
textColor={t.text}
|
||||
backgroundColor={t.backgroundElement}
|
||||
placeholderColor={t.textMuted}
|
||||
minHeight={2}
|
||||
maxHeight={6}
|
||||
wrapMode="word"
|
||||
keyBindings={EDITOR_KEYBINDINGS}
|
||||
onSubmit={onSubmit as unknown as () => void}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
</scrollbox>
|
||||
<box flexShrink={0} paddingLeft={2} paddingRight={2} paddingTop={2} paddingBottom={1}>
|
||||
{error ? (
|
||||
<text fg={t.diffRemovedFg}>{error}</text>
|
||||
) : (
|
||||
<text>
|
||||
<span style={{ fg: t.primary }}>{"enter "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"save · "}</span>
|
||||
<span style={{ fg: t.primary }}>{"tab "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"next field · "}</span>
|
||||
<span style={{ fg: t.primary }}>{"←→ "}</span>
|
||||
<span style={{ fg: t.textMuted }}>{"transport"}</span>
|
||||
</text>
|
||||
)}
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
@@ -17,10 +17,30 @@ export interface TelegramSettings {
|
||||
nativeDrafts?: boolean;
|
||||
}
|
||||
|
||||
export type McpRemoteTransport = "http" | "sse";
|
||||
|
||||
export interface McpServerConfig {
|
||||
id: string;
|
||||
label: string;
|
||||
enabled: boolean;
|
||||
transport: McpRemoteTransport | "stdio";
|
||||
url?: string;
|
||||
headers?: Record<string, string>;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export interface McpSettings {
|
||||
servers?: McpServerConfig[];
|
||||
}
|
||||
|
||||
export interface UserSettings {
|
||||
apiKey?: string;
|
||||
defaultModel?: string;
|
||||
telegram?: TelegramSettings;
|
||||
mcp?: McpSettings;
|
||||
}
|
||||
|
||||
export interface ProjectSettings {
|
||||
@@ -73,6 +93,15 @@ export function saveUserSettings(partial: Partial<UserSettings>): void {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(partial.mcp !== undefined
|
||||
? {
|
||||
mcp: {
|
||||
...current.mcp,
|
||||
...partial.mcp,
|
||||
servers: partial.mcp.servers ?? current.mcp?.servers ?? [],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
writeJson(USER_SETTINGS_PATH, next);
|
||||
@@ -122,3 +151,11 @@ export function resolveTelegramStreamSettings(t: TelegramSettings | undefined):
|
||||
nativeDrafts: t?.nativeDrafts === true,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadMcpServers(): McpServerConfig[] {
|
||||
return loadUserSettings().mcp?.servers ?? [];
|
||||
}
|
||||
|
||||
export function saveMcpServers(servers: McpServerConfig[]): void {
|
||||
saveUserSettings({ mcp: { servers } });
|
||||
}
|
||||
Reference in new issue
Block a user