fix lint issues (#180)

This commit is contained in:
Ismail Pelaseyed authored and GitHub committed 2026-03-20 21:26:22 +01:00
1 parent 9d93ea4f87
commit bdeccafada
11 files changed
+1444 -13

No files matched your search

+178
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+107
View File
@@ -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",
},
];
+35
View File
@@ -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;
}
+79
View File
@@ -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(() => {})));
},
};
}
+48
View File
@@ -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
View File
@@ -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}
+33
View File
@@ -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: "",
};
}
+456
View File
@@ -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>
);
}
+37
View File
@@ -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 } });
}