diff --git a/bun.lock b/bun.lock index 035a2ae..1930686 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/package.json b/package.json index ca763cb..ec0cf9d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 86feaf1..c998597 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -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___. 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) | 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) | 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; } diff --git a/src/mcp/catalog.ts b/src/mcp/catalog.ts new file mode 100644 index 0000000..d9e50f0 --- /dev/null +++ b/src/mcp/catalog.ts @@ -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", + }, +]; diff --git a/src/mcp/parse-headers.ts b/src/mcp/parse-headers.ts new file mode 100644 index 0000000..1d5577c --- /dev/null +++ b/src/mcp/parse-headers.ts @@ -0,0 +1,35 @@ +export function parseHeaderLines(text: string): Record { + const headers: Record = {}; + + 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 { + const env: Record = {}; + + 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; +} diff --git a/src/mcp/runtime.ts b/src/mcp/runtime.ts new file mode 100644 index 0000000..54bb2bf --- /dev/null +++ b/src/mcp/runtime.ts @@ -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; +} + +export async function buildMcpToolSet(servers: McpServerConfig[]): Promise { + 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(() => {}))); + }, + }; +} diff --git a/src/mcp/validate.ts b/src/mcp/validate.ts new file mode 100644 index 0000000..1879337 --- /dev/null +++ b/src/mcp/validate.ts @@ -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 }; +} diff --git a/src/ui/app.tsx b/src/ui/app.tsx index c8216e8..07fb474 100644 --- a/src/ui/app.tsx +++ b/src/ui/app.tsx @@ -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(() => loadMcpServers()); + const [mcpEditorDraft, setMcpEditorDraft] = useState(createEmptyMcpEditorDraft()); + const [mcpEditorField, setMcpEditorField] = useState("transport"); + const [mcpEditorSyncKey, setMcpEditorSyncKey] = useState(0); + const [mcpEditorError, setMcpEditorError] = useState(null); + const [editingMcpId, setEditingMcpId] = useState(null); + const showMcpModalRef = useRef(false); + const showMcpEditorRef = useRef(false); + const mcpLabelRef = useRef(null); + const mcpUrlRef = useRef(null); + const mcpHeadersRef = useRef(null); + const mcpCommandRef = useRef(null); + const mcpArgsRef = useRef(null); + const mcpCwdRef = useRef(null); + const mcpEnvRef = useRef(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 = ["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 && ( + + )} + {showMcpEditor && ( + + )} {showModelPicker && ( [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(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 ( + + + + + {"MCP Servers"} + + {"esc"} + + + {searchQuery || {"Search servers..."}} + + + {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 ( + + + + {row.server.enabled ? "■ " : "□ "} + {row.server.label} + + {row.server.transport} + + {row.description} + + ); + } + + if (row.kind === "catalog") { + return ( + + + + {"□ "} + {row.entry.name} + + {"Popular"} + + {row.entry.description} + + ); + } + + return ( + + + {"□ Add Custom MCP"} + + + ); + })} + + + + {"enter "} + {"toggle · "} + {"ctrl+e "} + {"edit · "} + {"ctrl+a "} + {"add · "} + {"ctrl+x "} + {"delete"} + + + + + ); +} + +function syncRef(ref: RefObject, 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; + urlRef: RefObject; + headersRef: RefObject; + commandRef: RefObject; + argsRef: RefObject; + cwdRef: RefObject; + envRef: RefObject; + 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 ( + + + + + {title} + + {"esc"} + + + + {"Transport"} + {(["stdio", "http", "sse"] as const).map((option) => { + const active = draft.transport === option; + const focused = focusedField === "transport"; + return ( + + {option} + + ); + })} + + + + + {"Label"} + +