diff --git a/mcpjam-inspector/package-lock.json b/mcpjam-inspector/package-lock.json
index 3f91a1964..e4b70167d 100644
--- a/mcpjam-inspector/package-lock.json
+++ b/mcpjam-inspector/package-lock.json
@@ -25,7 +25,7 @@
"@hono/node-server": "^1.13.7",
"@hookform/resolvers": "^3.10.0",
"@mcp-ui/client": "^5.9.0",
- "@mcpjam/sdk": "^0.8.4",
+ "@mcpjam/sdk": "^0.8.5",
"@modelcontextprotocol/ext-apps": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.26.0",
"@ngrok/ngrok": "^1.6.0",
@@ -132,7 +132,7 @@
},
"../sdk": {
"name": "@mcpjam/sdk",
- "version": "0.8.4",
+ "version": "0.8.5",
"license": "MIT",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.17",
diff --git a/mcpjam-inspector/package.json b/mcpjam-inspector/package.json
index 1e7e4b5d2..6c89a2c25 100644
--- a/mcpjam-inspector/package.json
+++ b/mcpjam-inspector/package.json
@@ -96,7 +96,7 @@
"@hono/node-server": "^1.13.7",
"@hookform/resolvers": "^3.10.0",
"@mcp-ui/client": "^5.9.0",
- "@mcpjam/sdk": "^0.8.4",
+ "@mcpjam/sdk": "^0.8.5",
"@modelcontextprotocol/ext-apps": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.26.0",
"@ngrok/ngrok": "^1.6.0",
diff --git a/mcpjam-web/.gitignore b/mcpjam-web/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/mcpjam-web/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/mcpjam-web/MCPJAM_WEBAPP_GOAL.md b/mcpjam-web/MCPJAM_WEBAPP_GOAL.md
new file mode 100644
index 000000000..c69d50e35
--- /dev/null
+++ b/mcpjam-web/MCPJAM_WEBAPP_GOAL.md
@@ -0,0 +1,35 @@
+# Objective
+
+We want to build a lightweight web app version of MCPJam, no Hono server, full client app. Because it's going to be hosted, we can only connect to remote Streamable HTTP MCP servers. That means no STDIO or localhost enabled. This project must be very barebones and minimalist.
+
+# Features
+We will only have the server connections page and the LLM playground page.
+
+1. Server connections page to manage server connections.
+2. Be able to also connect to MCP servers that have Dynamic Client Registration
+3. LLM playground to chat with the MCP server with different models
+4. Same UI theme as the MCPJam inspector.
+
+Eventually the LLM playground should have full parity to handle ChatGPT apps rendering and MCP apps rendering. For now, we can start off with basic MCP server chatting.
+
+# Suggested instructions
+- Create a global hook that manages the MCP connections. Use the `MCPClientManager` class from @mcpjam/sdk
+- Have this hook expose some functions such as connect / disconnect MCP servers.
+- Use Vercel AI-SDK to manage text streaming.
+
+## Instructions for AI: Feel free to write more planning below here.
+
+
+## Personal note on CORS pre-flight
+Web app path (fails): browser uses @mcpjam/sdk/browser directly in McpConnectionsProvider.tsx (line 1) and calls manager.connectToServer(...) at McpConnectionsProvider.tsx (line 246).
+Inspector path (works): browser posts config to local API (/api/mcp/connect) at mcp-api.ts (line 37), then backend calls mcpClientManager.connectToServer(...) at connect.ts (line 71), with manager created on the Node server in app.ts (line 86).
+Why that matters:
+
+Browser direct calls hit CORS/preflight rules.
+For https://excalidraw-mcp-app.vercel.app/mcp, OPTIONS returns 405, so streamable fetch fails in browser (Failed to fetch).
+Then SDK fallback tries SSE GET, and that endpoint returns 405, producing your SSE error.
+Node backend (inspector) is not blocked by browser CORS preflight, so it can do the valid streamable POST and connect.
+Extra proof from inspector design:
+
+It also exposes its own bridge endpoints with explicit OPTIONS 204 for browser clients in http-adapters.ts (line 21).
+So the behavior difference is expected: direct browser transport vs backend-proxied transport.
\ No newline at end of file
diff --git a/mcpjam-web/README.md b/mcpjam-web/README.md
new file mode 100644
index 000000000..d2e77611f
--- /dev/null
+++ b/mcpjam-web/README.md
@@ -0,0 +1,73 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/mcpjam-web/eslint.config.js b/mcpjam-web/eslint.config.js
new file mode 100644
index 000000000..5e6b472f5
--- /dev/null
+++ b/mcpjam-web/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/mcpjam-web/index.html b/mcpjam-web/index.html
new file mode 100644
index 000000000..ea76f4762
--- /dev/null
+++ b/mcpjam-web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ mcpjam-web
+
+
+
+
+
+
diff --git a/mcpjam-web/package-lock.json b/mcpjam-web/package-lock.json
new file mode 100644
index 000000000..70c24b946
--- /dev/null
+++ b/mcpjam-web/package-lock.json
@@ -0,0 +1,9134 @@
+{
+ "name": "mcpjam-web",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "mcpjam-web",
+ "version": "0.0.0",
+ "dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@mcp-ui/client": "^5.9.0",
+ "@mcpjam/sdk": "0.8.5",
+ "@modelcontextprotocol/ext-apps": "^1.0.1",
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "@tanstack/react-virtual": "^3.13.12",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "i": "^0.3.7",
+ "lucide-react": "^0.525.0",
+ "npm": "^11.10.0",
+ "radix-ui": "^1.4.2",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "sonner": "^2.0.6",
+ "tailwind-merge": "^3.3.1",
+ "tw-animate-css": "^1.3.5"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@tailwindcss/vite": "^4.1.13",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "tailwindcss": "^4.1.13",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+ },
+ "../sdk": {
+ "name": "@mcpjam/sdk",
+ "version": "0.8.5",
+ "license": "MIT",
+ "dependencies": {
+ "@ai-sdk/anthropic": "^2.0.17",
+ "@ai-sdk/azure": "^2.0.79",
+ "@ai-sdk/deepseek": "^1.0.5",
+ "@ai-sdk/google": "^2.0.11",
+ "@ai-sdk/mistral": "^2.0.19",
+ "@ai-sdk/openai": "^2.0.32",
+ "@ai-sdk/xai": "^2.0.29",
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "@openrouter/ai-sdk-provider": "^2.2.0",
+ "ai": "^6.0.50",
+ "ollama-ai-provider-v2": "^1.5.2",
+ "posthog-node": "^5.24.10",
+ "zod": "^4.1.12"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.2",
+ "@types/jest": "^29.5.12",
+ "@types/json-schema": "^7.0.15",
+ "@types/node": "^20.11.0",
+ "@typescript-eslint/eslint-plugin": "^8.53.1",
+ "@typescript-eslint/parser": "^8.53.1",
+ "eslint": "^9.39.2",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.5",
+ "jest": "^29.7.0",
+ "prettier": "^3.8.0",
+ "ts-jest": "^29.1.2",
+ "tsup": "^8.0.1",
+ "typescript": "^5.3.3",
+ "typescript-eslint": "^8.53.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
+ "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
+ "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.1",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
+ "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
+ "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
+ "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.4",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz",
+ "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.5"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.9",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
+ "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mcp-ui/client": {
+ "version": "5.17.3",
+ "resolved": "https://registry.npmjs.org/@mcp-ui/client/-/client-5.17.3.tgz",
+ "integrity": "sha512-Xxi8d5NYbCBUdBEqhwnm7PgJE6+F1wOV2sPA0AWnfLoudpiq8iomovL/AMFI+alVZwhSBxzqJKqUdbPD2yz5tQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.22.0",
+ "@quilted/threads": "^3.1.3",
+ "@r2wc/react-to-web-component": "^2.0.4",
+ "@remote-dom/core": "^1.8.0",
+ "@remote-dom/react": "^1.2.2"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
+ "node_modules/@mcp-ui/client/node_modules/@remote-dom/react": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@remote-dom/react/-/react-1.2.2.tgz",
+ "integrity": "sha512-PkvioODONTr1M0StGDYsR4Ssf5M0Rd4+IlWVvVoK3Zrw8nr7+5mJkgNofaj/z7i8Aep78L28PCW8/WduUt4unA==",
+ "license": "MIT",
+ "dependencies": {
+ "@remote-dom/core": "^1.7.0",
+ "@types/react": "^18.0.0",
+ "htm": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mcp-ui/client/node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@mcpjam/sdk": {
+ "resolved": "../sdk",
+ "link": true
+ },
+ "node_modules/@modelcontextprotocol/ext-apps": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.0.1.tgz",
+ "integrity": "sha512-rAPzBbB5GNgYk216paQjGKUgbNXSy/yeR95c0ni6Y4uvhWI2AeF+ztEOqQFLBMQy/MPM+02pbVK1HaQmQjMwYQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "workspaces": [
+ "examples/*"
+ ],
+ "optionalDependencies": {
+ "@oven/bun-darwin-aarch64": "^1.2.21",
+ "@oven/bun-darwin-x64": "^1.2.21",
+ "@oven/bun-darwin-x64-baseline": "^1.2.21",
+ "@oven/bun-linux-aarch64": "^1.2.21",
+ "@oven/bun-linux-aarch64-musl": "^1.2.21",
+ "@oven/bun-linux-x64": "^1.2.21",
+ "@oven/bun-linux-x64-baseline": "^1.2.21",
+ "@oven/bun-linux-x64-musl": "^1.2.21",
+ "@oven/bun-linux-x64-musl-baseline": "^1.2.21",
+ "@oven/bun-windows-x64": "^1.2.21",
+ "@oven/bun-windows-x64-baseline": "^1.2.21",
+ "@rollup/rollup-darwin-arm64": "^4.53.3",
+ "@rollup/rollup-darwin-x64": "^4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "^4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "^4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "^4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "^4.53.3"
+ },
+ "peerDependencies": {
+ "@modelcontextprotocol/sdk": "^1.24.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "zod": "^3.25.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
+ "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/@oven/bun-darwin-aarch64": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.9.tgz",
+ "integrity": "sha512-df7smckMWSUfaT5mzwN9Lfpd3ZGkOqo+vmQ8VV2a32gl14v6uZ/qeeo+1RlANXn8M0uzXPWWCkrKZIWSZUR0qw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oven/bun-darwin-x64": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.9.tgz",
+ "integrity": "sha512-YiLxfsPzQqaVvT2a+nxH9do0YfUjrlxF3tKP0b1DDgvfgCcVKGsrQH3Wa82qHgL4dnT8h2bqi94JxXESEuPmcA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oven/bun-darwin-x64-baseline": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.9.tgz",
+ "integrity": "sha512-XbhsA2XAFzvFr0vPSV6SNqGxab4xHKdPmVTLqoSHAx9tffrSq/012BDptOskulwnD+YNsrJUx2D2Ve1xvfgGcg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oven/bun-linux-aarch64": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.9.tgz",
+ "integrity": "sha512-VaNQTu0Up4gnwZLQ6/Hmho6jAlLxTQ1PwxEth8EsXHf82FOXXPV5OCQ6KC9mmmocjKlmWFaIGebThrOy8DUo4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oven/bun-linux-aarch64-musl": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.9.tgz",
+ "integrity": "sha512-t8uimCVBTw5f9K2QTZE5wN6UOrFETNrh/Xr7qtXT9nAOzaOnIFvYA+HcHbGfi31fRlCVfTxqm/EiCwJ1gEw9YQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oven/bun-linux-x64": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.9.tgz",
+ "integrity": "sha512-oQyAW3+ugulvXTZ+XYeUMmNPR94sJeMokfHQoKwPvVwhVkgRuMhcLGV2ZesHCADVu30Oz2MFXbgdC8x4/o9dRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oven/bun-linux-x64-baseline": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.9.tgz",
+ "integrity": "sha512-nZ12g22cy7pEOBwAxz2tp0wVqekaCn9QRKuGTHqOdLlyAqR4SCdErDvDhUWd51bIyHTQoCmj72TegGTgG0WNPw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oven/bun-linux-x64-musl": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.9.tgz",
+ "integrity": "sha512-4ZjIUgCxEyKwcKXideB5sX0KJpnHTZtu778w73VNq2uNH2fNpMZv98+DBgJyQ9OfFoRhmKn1bmLmSefvnHzI9w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oven/bun-linux-x64-musl-baseline": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.9.tgz",
+ "integrity": "sha512-3FXQgtYFsT0YOmAdMcJn56pLM5kzSl6y942rJJIl5l2KummB9Ea3J/vMJMzQk7NCAGhleZGWU/pJSS/uXKGa7w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oven/bun-windows-x64": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.9.tgz",
+ "integrity": "sha512-/d6vAmgKvkoYlsGPsRPlPmOK1slPis/F40UG02pYwypTH0wmY0smgzdFqR4YmryxFh17XrW1kITv+U99Oajk9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@oven/bun-windows-x64-baseline": {
+ "version": "1.3.9",
+ "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.9.tgz",
+ "integrity": "sha512-a/+hSrrDpMD7THyXvE2KJy1skxzAD0cnW4K1WjuI/91VqsphjNzvf5t/ZgxEVL4wb6f+hKrSJ5J3aH47zPr61g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@preact/signals-core": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.13.0.tgz",
+ "integrity": "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/@quilted/events": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@quilted/events/-/events-2.1.3.tgz",
+ "integrity": "sha512-4fHaSLND8rmZ+tce9/4FNmG5UWTRpFtM54kOekf3tLON4ZLLnYzjjldELD35efd7+lT5+E3cdkacqc56d+kCrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@preact/signals-core": "^1.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@quilted/threads": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@quilted/threads/-/threads-3.3.1.tgz",
+ "integrity": "sha512-0ASnjTH+hOu1Qwzi9NnsVcsbMhWVx8pEE8SXIHknqcc/1rXAU0QlKw9ARq0W43FAdzyVeuXeXtZN27ZC0iALKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@quilted/events": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@preact/signals-core": "^1.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@preact/signals-core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@r2wc/core": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@r2wc/core/-/core-1.3.0.tgz",
+ "integrity": "sha512-aPBnND92Itl+SWWbWyyxdFFF0+RqKB6dptGHEdiPB8ZvnHWHlVzOfEvbEcyUYGtB6HBdsfkVuBiaGYyBFVTzVQ==",
+ "license": "MIT"
+ },
+ "node_modules/@r2wc/react-to-web-component": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@r2wc/react-to-web-component/-/react-to-web-component-2.1.0.tgz",
+ "integrity": "sha512-m/PzgUOEiL1HxmvfP5LgBLqB7sHeRj+d1QAeZklwS4OEI2HUU+xTpT3hhJipH5DQoFInDqDTfe0lNFFKcrqk4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@r2wc/core": "^1.3.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-accessible-icon": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz",
+ "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
+ "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collapsible": "1.1.12",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
+ "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.15",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz",
+ "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
+ "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-is-hydrated": "0.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
+ "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
+ "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz",
+ "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-form": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz",
+ "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-label": "2.1.7",
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-hover-card": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz",
+ "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
+ "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menubar": {
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz",
+ "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-navigation-menu": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz",
+ "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-one-time-password-field": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz",
+ "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-is-hydrated": "0.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-password-toggle-field": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz",
+ "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-is-hydrated": "0.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
+ "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
+ "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz",
+ "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
+ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
+ "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz",
+ "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz",
+ "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
+ "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz",
+ "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-toggle": "1.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toolbar": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz",
+ "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-separator": "1.1.7",
+ "@radix-ui/react-toggle-group": "1.1.11"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-is-hydrated": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
+ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@remote-dom/core": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@remote-dom/core/-/core-1.10.1.tgz",
+ "integrity": "sha512-MlbUOGuHjOrB7uOkaYkIoLUG8lDK8/H1D7MHnGkgqbG6jwjwQSlGPHhbwnD6HYWsTGpAPOP02Byd8wBt9U6TEw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remote-dom/polyfill": "^1.5.1",
+ "htm": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@preact/signals-core": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@preact/signals-core": {
+ "optional": true
+ },
+ "preact": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remote-dom/polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@remote-dom/polyfill/-/polyfill-1.5.1.tgz",
+ "integrity": "sha512-eaWdIVKZpNfbqspKkRQLVxiFv/7vIw8u0FVA5oy52YANFbO/WVT0GU+PQmRt/QUSijaB36HBAqx7stjo8HGpVQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
+ "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
+ "tailwindcss": "4.1.18"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.13.18",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.18.tgz",
+ "integrity": "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.13.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.13.18",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz",
+ "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
+ "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
+ "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/type-utils": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.55.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
+ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
+ "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.55.0",
+ "@typescript-eslint/types": "^8.55.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
+ "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
+ "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
+ "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
+ "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
+ "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.55.0",
+ "@typescript-eslint/tsconfig-utils": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "debug": "^4.4.3",
+ "minimatch": "^9.0.5",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
+ "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
+ "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
+ "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-formats/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001769",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
+ "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.19.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
+ "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
+ "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "10.0.1"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz",
+ "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/htm": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz",
+ "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/i": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz",
+ "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
+ "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/jose": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
+ "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.525.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz",
+ "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/npm": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-11.10.0.tgz",
+ "integrity": "sha512-i8hE43iSIAMFuYVi8TxsEISdELM4fIza600aLjJ0ankGPLqd0oTPKMJqAcO/QWm307MbSlWGzJcNZ0lGMQgHPA==",
+ "bundleDependencies": [
+ "@isaacs/string-locale-compare",
+ "@npmcli/arborist",
+ "@npmcli/config",
+ "@npmcli/fs",
+ "@npmcli/map-workspaces",
+ "@npmcli/metavuln-calculator",
+ "@npmcli/package-json",
+ "@npmcli/promise-spawn",
+ "@npmcli/redact",
+ "@npmcli/run-script",
+ "@sigstore/tuf",
+ "abbrev",
+ "archy",
+ "cacache",
+ "chalk",
+ "ci-info",
+ "cli-columns",
+ "fastest-levenshtein",
+ "fs-minipass",
+ "glob",
+ "graceful-fs",
+ "hosted-git-info",
+ "ini",
+ "init-package-json",
+ "is-cidr",
+ "json-parse-even-better-errors",
+ "libnpmaccess",
+ "libnpmdiff",
+ "libnpmexec",
+ "libnpmfund",
+ "libnpmorg",
+ "libnpmpack",
+ "libnpmpublish",
+ "libnpmsearch",
+ "libnpmteam",
+ "libnpmversion",
+ "make-fetch-happen",
+ "minimatch",
+ "minipass",
+ "minipass-pipeline",
+ "ms",
+ "node-gyp",
+ "nopt",
+ "npm-audit-report",
+ "npm-install-checks",
+ "npm-package-arg",
+ "npm-pick-manifest",
+ "npm-profile",
+ "npm-registry-fetch",
+ "npm-user-validate",
+ "p-map",
+ "pacote",
+ "parse-conflict-json",
+ "proc-log",
+ "qrcode-terminal",
+ "read",
+ "semver",
+ "spdx-expression-parse",
+ "ssri",
+ "supports-color",
+ "tar",
+ "text-table",
+ "tiny-relative-date",
+ "treeverse",
+ "validate-npm-package-name",
+ "which"
+ ],
+ "license": "Artistic-2.0",
+ "workspaces": [
+ "docs",
+ "smoke-tests",
+ "mock-globals",
+ "mock-registry",
+ "workspaces/*"
+ ],
+ "dependencies": {
+ "@isaacs/string-locale-compare": "^1.1.0",
+ "@npmcli/arborist": "^9.3.0",
+ "@npmcli/config": "^10.7.0",
+ "@npmcli/fs": "^5.0.0",
+ "@npmcli/map-workspaces": "^5.0.3",
+ "@npmcli/metavuln-calculator": "^9.0.3",
+ "@npmcli/package-json": "^7.0.4",
+ "@npmcli/promise-spawn": "^9.0.1",
+ "@npmcli/redact": "^4.0.0",
+ "@npmcli/run-script": "^10.0.3",
+ "@sigstore/tuf": "^4.0.1",
+ "abbrev": "^4.0.0",
+ "archy": "~1.0.0",
+ "cacache": "^20.0.3",
+ "chalk": "^5.6.2",
+ "ci-info": "^4.4.0",
+ "cli-columns": "^4.0.0",
+ "fastest-levenshtein": "^1.0.16",
+ "fs-minipass": "^3.0.3",
+ "glob": "^13.0.2",
+ "graceful-fs": "^4.2.11",
+ "hosted-git-info": "^9.0.2",
+ "ini": "^6.0.0",
+ "init-package-json": "^8.2.4",
+ "is-cidr": "^6.0.3",
+ "json-parse-even-better-errors": "^5.0.0",
+ "libnpmaccess": "^10.0.3",
+ "libnpmdiff": "^8.1.1",
+ "libnpmexec": "^10.2.1",
+ "libnpmfund": "^7.0.15",
+ "libnpmorg": "^8.0.1",
+ "libnpmpack": "^9.1.1",
+ "libnpmpublish": "^11.1.3",
+ "libnpmsearch": "^9.0.1",
+ "libnpmteam": "^8.0.2",
+ "libnpmversion": "^8.0.3",
+ "make-fetch-happen": "^15.0.3",
+ "minimatch": "^10.1.1",
+ "minipass": "^7.1.1",
+ "minipass-pipeline": "^1.2.4",
+ "ms": "^2.1.2",
+ "node-gyp": "^12.2.0",
+ "nopt": "^9.0.0",
+ "npm-audit-report": "^7.0.0",
+ "npm-install-checks": "^8.0.0",
+ "npm-package-arg": "^13.0.2",
+ "npm-pick-manifest": "^11.0.3",
+ "npm-profile": "^12.0.1",
+ "npm-registry-fetch": "^19.1.1",
+ "npm-user-validate": "^4.0.0",
+ "p-map": "^7.0.4",
+ "pacote": "^21.3.1",
+ "parse-conflict-json": "^5.0.1",
+ "proc-log": "^6.1.0",
+ "qrcode-terminal": "^0.12.0",
+ "read": "^5.0.1",
+ "semver": "^7.7.4",
+ "spdx-expression-parse": "^4.0.0",
+ "ssri": "^13.0.1",
+ "supports-color": "^10.2.2",
+ "tar": "^7.5.7",
+ "text-table": "~0.2.0",
+ "tiny-relative-date": "^2.0.2",
+ "treeverse": "^3.0.0",
+ "validate-npm-package-name": "^7.0.2",
+ "which": "^6.0.1"
+ },
+ "bin": {
+ "npm": "bin/npm-cli.js",
+ "npx": "bin/npx-cli.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@isaacs/balanced-match": {
+ "version": "4.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/npm/node_modules/@isaacs/brace-expansion": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/balanced-match": "^4.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/npm/node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@isaacs/string-locale-compare": {
+ "version": "1.1.0",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/@npmcli/agent": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.1",
+ "lru-cache": "^11.2.1",
+ "socks-proxy-agent": "^8.0.3"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/arborist": {
+ "version": "9.3.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/string-locale-compare": "^1.1.0",
+ "@npmcli/fs": "^5.0.0",
+ "@npmcli/installed-package-contents": "^4.0.0",
+ "@npmcli/map-workspaces": "^5.0.0",
+ "@npmcli/metavuln-calculator": "^9.0.2",
+ "@npmcli/name-from-folder": "^4.0.0",
+ "@npmcli/node-gyp": "^5.0.0",
+ "@npmcli/package-json": "^7.0.0",
+ "@npmcli/query": "^5.0.0",
+ "@npmcli/redact": "^4.0.0",
+ "@npmcli/run-script": "^10.0.0",
+ "bin-links": "^6.0.0",
+ "cacache": "^20.0.1",
+ "common-ancestor-path": "^2.0.0",
+ "hosted-git-info": "^9.0.0",
+ "json-stringify-nice": "^1.1.4",
+ "lru-cache": "^11.2.1",
+ "minimatch": "^10.0.3",
+ "nopt": "^9.0.0",
+ "npm-install-checks": "^8.0.0",
+ "npm-package-arg": "^13.0.0",
+ "npm-pick-manifest": "^11.0.1",
+ "npm-registry-fetch": "^19.0.0",
+ "pacote": "^21.0.2",
+ "parse-conflict-json": "^5.0.1",
+ "proc-log": "^6.0.0",
+ "proggy": "^4.0.0",
+ "promise-all-reject-late": "^1.0.0",
+ "promise-call-limit": "^3.0.1",
+ "semver": "^7.3.7",
+ "ssri": "^13.0.0",
+ "treeverse": "^3.0.0",
+ "walk-up-path": "^4.0.0"
+ },
+ "bin": {
+ "arborist": "bin/index.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/config": {
+ "version": "10.7.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/map-workspaces": "^5.0.0",
+ "@npmcli/package-json": "^7.0.0",
+ "ci-info": "^4.0.0",
+ "ini": "^6.0.0",
+ "nopt": "^9.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.5",
+ "walk-up-path": "^4.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/fs": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/git": {
+ "version": "7.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/promise-spawn": "^9.0.0",
+ "ini": "^6.0.0",
+ "lru-cache": "^11.2.1",
+ "npm-pick-manifest": "^11.0.1",
+ "proc-log": "^6.0.0",
+ "promise-retry": "^2.0.1",
+ "semver": "^7.3.5",
+ "which": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/installed-package-contents": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-bundled": "^5.0.0",
+ "npm-normalize-package-bin": "^5.0.0"
+ },
+ "bin": {
+ "installed-package-contents": "bin/index.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/map-workspaces": {
+ "version": "5.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/name-from-folder": "^4.0.0",
+ "@npmcli/package-json": "^7.0.0",
+ "glob": "^13.0.0",
+ "minimatch": "^10.0.3"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/metavuln-calculator": {
+ "version": "9.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "cacache": "^20.0.0",
+ "json-parse-even-better-errors": "^5.0.0",
+ "pacote": "^21.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/name-from-folder": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/node-gyp": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/package-json": {
+ "version": "7.0.4",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/git": "^7.0.0",
+ "glob": "^13.0.0",
+ "hosted-git-info": "^9.0.0",
+ "json-parse-even-better-errors": "^5.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.5.3",
+ "validate-npm-package-license": "^3.0.4"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/promise-spawn": {
+ "version": "9.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "which": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/query": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "postcss-selector-parser": "^7.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/redact": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/run-script": {
+ "version": "10.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/node-gyp": "^5.0.0",
+ "@npmcli/package-json": "^7.0.0",
+ "@npmcli/promise-spawn": "^9.0.0",
+ "node-gyp": "^12.1.0",
+ "proc-log": "^6.0.0",
+ "which": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@sigstore/bundle": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@sigstore/protobuf-specs": "^0.5.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@sigstore/core": {
+ "version": "3.1.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@sigstore/protobuf-specs": {
+ "version": "0.5.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/npm/node_modules/@sigstore/sign": {
+ "version": "4.1.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@sigstore/bundle": "^4.0.0",
+ "@sigstore/core": "^3.1.0",
+ "@sigstore/protobuf-specs": "^0.5.0",
+ "make-fetch-happen": "^15.0.3",
+ "proc-log": "^6.1.0",
+ "promise-retry": "^2.0.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@sigstore/tuf": {
+ "version": "4.0.1",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@sigstore/protobuf-specs": "^0.5.0",
+ "tuf-js": "^4.1.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@sigstore/verify": {
+ "version": "3.1.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@sigstore/bundle": "^4.0.0",
+ "@sigstore/core": "^3.1.0",
+ "@sigstore/protobuf-specs": "^0.5.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/@tufjs/canonical-json": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^16.14.0 || >=18.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@tufjs/models": {
+ "version": "4.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tufjs/canonical-json": "2.0.0",
+ "minimatch": "^10.1.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/abbrev": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/agent-base": {
+ "version": "7.1.4",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/npm/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/aproba": {
+ "version": "2.1.0",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/archy": {
+ "version": "1.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/bin-links": {
+ "version": "6.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "cmd-shim": "^8.0.0",
+ "npm-normalize-package-bin": "^5.0.0",
+ "proc-log": "^6.0.0",
+ "read-cmd-shim": "^6.0.0",
+ "write-file-atomic": "^7.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/binary-extensions": {
+ "version": "3.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm/node_modules/cacache": {
+ "version": "20.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/fs": "^5.0.0",
+ "fs-minipass": "^3.0.0",
+ "glob": "^13.0.0",
+ "lru-cache": "^11.1.0",
+ "minipass": "^7.0.3",
+ "minipass-collect": "^2.0.1",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "p-map": "^7.0.2",
+ "ssri": "^13.0.0",
+ "unique-filename": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/chalk": {
+ "version": "5.6.2",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/npm/node_modules/chownr": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm/node_modules/ci-info": {
+ "version": "4.4.0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/cidr-regex": {
+ "version": "5.0.2",
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "ip-regex": "5.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/npm/node_modules/cli-columns": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/npm/node_modules/cmd-shim": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/common-ancestor-path": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/npm/node_modules/cssesc": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npm/node_modules/debug": {
+ "version": "4.4.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/npm/node_modules/diff": {
+ "version": "8.0.3",
+ "inBundle": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/npm/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/encoding": {
+ "version": "0.1.13",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/npm/node_modules/env-paths": {
+ "version": "2.2.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/npm/node_modules/err-code": {
+ "version": "2.0.3",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/exponential-backoff": {
+ "version": "3.1.3",
+ "inBundle": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/npm/node_modules/fastest-levenshtein": {
+ "version": "1.0.16",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.9.1"
+ }
+ },
+ "node_modules/npm/node_modules/fs-minipass": {
+ "version": "3.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/glob": {
+ "version": "13.0.2",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.1.2",
+ "minipass": "^7.1.2",
+ "path-scurry": "^2.0.0"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/hosted-git-info": {
+ "version": "9.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^11.1.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/http-cache-semantics": {
+ "version": "4.2.0",
+ "inBundle": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/npm/node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/npm/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/npm/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm/node_modules/ignore-walk": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minimatch": "^10.0.3"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/npm/node_modules/ini": {
+ "version": "6.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/init-package-json": {
+ "version": "8.2.4",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/package-json": "^7.0.0",
+ "npm-package-arg": "^13.0.0",
+ "promzard": "^3.0.1",
+ "read": "^5.0.1",
+ "semver": "^7.7.2",
+ "validate-npm-package-license": "^3.0.4",
+ "validate-npm-package-name": "^7.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/ip-address": {
+ "version": "10.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/npm/node_modules/ip-regex": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm/node_modules/is-cidr": {
+ "version": "6.0.3",
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "cidr-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/npm/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/isexe": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/npm/node_modules/json-parse-even-better-errors": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/json-stringify-nice": {
+ "version": "1.1.4",
+ "inBundle": true,
+ "license": "ISC",
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/jsonparse": {
+ "version": "1.3.1",
+ "engines": [
+ "node >= 0.2.0"
+ ],
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/just-diff": {
+ "version": "6.0.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/just-diff-apply": {
+ "version": "5.5.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/libnpmaccess": {
+ "version": "10.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-package-arg": "^13.0.0",
+ "npm-registry-fetch": "^19.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmdiff": {
+ "version": "8.1.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/arborist": "^9.3.0",
+ "@npmcli/installed-package-contents": "^4.0.0",
+ "binary-extensions": "^3.0.0",
+ "diff": "^8.0.2",
+ "minimatch": "^10.0.3",
+ "npm-package-arg": "^13.0.0",
+ "pacote": "^21.0.2",
+ "tar": "^7.5.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmexec": {
+ "version": "10.2.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/arborist": "^9.3.0",
+ "@npmcli/package-json": "^7.0.0",
+ "@npmcli/run-script": "^10.0.0",
+ "ci-info": "^4.0.0",
+ "npm-package-arg": "^13.0.0",
+ "pacote": "^21.0.2",
+ "proc-log": "^6.0.0",
+ "promise-retry": "^2.0.1",
+ "read": "^5.0.1",
+ "semver": "^7.3.7",
+ "signal-exit": "^4.1.0",
+ "walk-up-path": "^4.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmfund": {
+ "version": "7.0.15",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/arborist": "^9.3.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmorg": {
+ "version": "8.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^19.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmpack": {
+ "version": "9.1.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/arborist": "^9.3.0",
+ "@npmcli/run-script": "^10.0.0",
+ "npm-package-arg": "^13.0.0",
+ "pacote": "^21.0.2"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmpublish": {
+ "version": "11.1.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/package-json": "^7.0.0",
+ "ci-info": "^4.0.0",
+ "npm-package-arg": "^13.0.0",
+ "npm-registry-fetch": "^19.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.7",
+ "sigstore": "^4.0.0",
+ "ssri": "^13.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmsearch": {
+ "version": "9.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-registry-fetch": "^19.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmteam": {
+ "version": "8.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^19.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmversion": {
+ "version": "8.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/git": "^7.0.0",
+ "@npmcli/run-script": "^10.0.0",
+ "json-parse-even-better-errors": "^5.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/lru-cache": {
+ "version": "11.2.6",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/npm/node_modules/make-fetch-happen": {
+ "version": "15.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/agent": "^4.0.0",
+ "cacache": "^20.0.1",
+ "http-cache-semantics": "^4.1.1",
+ "minipass": "^7.0.2",
+ "minipass-fetch": "^5.0.0",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^1.0.0",
+ "proc-log": "^6.0.0",
+ "promise-retry": "^2.0.1",
+ "ssri": "^13.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/minimatch": {
+ "version": "10.1.2",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/brace-expansion": "^5.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/minipass": {
+ "version": "7.1.2",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-collect": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-fetch": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.0.3",
+ "minipass-sized": "^2.0.0",
+ "minizlib": "^3.0.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.13"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-flush": {
+ "version": "1.0.5",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": {
+ "version": "3.3.6",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": {
+ "version": "3.3.6",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-sized": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minizlib": {
+ "version": "3.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/npm/node_modules/ms": {
+ "version": "2.1.3",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/mute-stream": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/negotiator": {
+ "version": "1.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/npm/node_modules/node-gyp": {
+ "version": "12.2.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "exponential-backoff": "^3.1.1",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^15.0.0",
+ "nopt": "^9.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.5",
+ "tar": "^7.5.4",
+ "tinyglobby": "^0.2.12",
+ "which": "^6.0.0"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/nopt": {
+ "version": "9.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^4.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-audit-report": {
+ "version": "7.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-bundled": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-normalize-package-bin": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-install-checks": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "semver": "^7.1.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-normalize-package-bin": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-package-arg": {
+ "version": "13.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "hosted-git-info": "^9.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-name": "^7.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-packlist": {
+ "version": "10.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "ignore-walk": "^8.0.0",
+ "proc-log": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-pick-manifest": {
+ "version": "11.0.3",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-install-checks": "^8.0.0",
+ "npm-normalize-package-bin": "^5.0.0",
+ "npm-package-arg": "^13.0.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-profile": {
+ "version": "12.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-registry-fetch": "^19.0.0",
+ "proc-log": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-registry-fetch": {
+ "version": "19.1.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/redact": "^4.0.0",
+ "jsonparse": "^1.3.1",
+ "make-fetch-happen": "^15.0.0",
+ "minipass": "^7.0.2",
+ "minipass-fetch": "^5.0.0",
+ "minizlib": "^3.0.1",
+ "npm-package-arg": "^13.0.0",
+ "proc-log": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-user-validate": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/p-map": {
+ "version": "7.0.4",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm/node_modules/pacote": {
+ "version": "21.3.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/git": "^7.0.0",
+ "@npmcli/installed-package-contents": "^4.0.0",
+ "@npmcli/package-json": "^7.0.0",
+ "@npmcli/promise-spawn": "^9.0.0",
+ "@npmcli/run-script": "^10.0.0",
+ "cacache": "^20.0.0",
+ "fs-minipass": "^3.0.0",
+ "minipass": "^7.0.2",
+ "npm-package-arg": "^13.0.0",
+ "npm-packlist": "^10.0.1",
+ "npm-pick-manifest": "^11.0.1",
+ "npm-registry-fetch": "^19.0.0",
+ "proc-log": "^6.0.0",
+ "promise-retry": "^2.0.1",
+ "sigstore": "^4.0.0",
+ "ssri": "^13.0.0",
+ "tar": "^7.4.3"
+ },
+ "bin": {
+ "pacote": "bin/index.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/parse-conflict-json": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "json-parse-even-better-errors": "^5.0.0",
+ "just-diff": "^6.0.0",
+ "just-diff-apply": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/path-scurry": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/postcss-selector-parser": {
+ "version": "7.1.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npm/node_modules/proc-log": {
+ "version": "6.1.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/proggy": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/promise-all-reject-late": {
+ "version": "1.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/promise-call-limit": {
+ "version": "3.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/promise-retry": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/promzard": {
+ "version": "3.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "read": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/qrcode-terminal": {
+ "version": "0.12.0",
+ "inBundle": true,
+ "bin": {
+ "qrcode-terminal": "bin/qrcode-terminal.js"
+ }
+ },
+ "node_modules/npm/node_modules/read": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "mute-stream": "^3.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/read-cmd-shim": {
+ "version": "6.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/retry": {
+ "version": "0.12.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/npm/node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/npm/node_modules/semver": {
+ "version": "7.7.4",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/sigstore": {
+ "version": "4.1.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@sigstore/bundle": "^4.0.0",
+ "@sigstore/core": "^3.1.0",
+ "@sigstore/protobuf-specs": "^0.5.0",
+ "@sigstore/sign": "^4.1.0",
+ "@sigstore/tuf": "^4.0.1",
+ "@sigstore/verify": "^3.1.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/socks": {
+ "version": "2.8.7",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.0.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "inBundle": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/npm/node_modules/spdx-expression-parse": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-license-ids": {
+ "version": "3.0.22",
+ "inBundle": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/npm/node_modules/ssri": {
+ "version": "13.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/string-width": {
+ "version": "4.2.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/supports-color": {
+ "version": "10.2.2",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/npm/node_modules/tar": {
+ "version": "7.5.7",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm/node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm/node_modules/text-table": {
+ "version": "0.2.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/tiny-relative-date": {
+ "version": "2.0.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/npm/node_modules/treeverse": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/tuf-js": {
+ "version": "4.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tufjs/models": "4.1.0",
+ "debug": "^4.4.3",
+ "make-fetch-happen": "^15.0.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/unique-filename": {
+ "version": "5.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "unique-slug": "^6.0.0"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/unique-slug": {
+ "version": "6.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/validate-npm-package-name": {
+ "version": "7.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/walk-up-path": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/npm/node_modules/which": {
+ "version": "6.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^4.0.0"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/write-file-atomic": {
+ "version": "7.0.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/npm/node_modules/yallist": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/radix-ui": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz",
+ "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-accessible-icon": "1.1.7",
+ "@radix-ui/react-accordion": "1.2.12",
+ "@radix-ui/react-alert-dialog": "1.1.15",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-aspect-ratio": "1.1.7",
+ "@radix-ui/react-avatar": "1.1.10",
+ "@radix-ui/react-checkbox": "1.3.3",
+ "@radix-ui/react-collapsible": "1.1.12",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-context-menu": "2.2.16",
+ "@radix-ui/react-dialog": "1.1.15",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-dropdown-menu": "2.1.16",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-form": "0.1.8",
+ "@radix-ui/react-hover-card": "1.1.15",
+ "@radix-ui/react-label": "2.1.7",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-menubar": "1.1.16",
+ "@radix-ui/react-navigation-menu": "1.2.14",
+ "@radix-ui/react-one-time-password-field": "0.1.8",
+ "@radix-ui/react-password-toggle-field": "0.1.3",
+ "@radix-ui/react-popover": "1.1.15",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-progress": "1.1.7",
+ "@radix-ui/react-radio-group": "1.3.8",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-scroll-area": "1.2.10",
+ "@radix-ui/react-select": "2.2.6",
+ "@radix-ui/react-separator": "1.1.7",
+ "@radix-ui/react-slider": "1.3.6",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-switch": "1.2.6",
+ "@radix-ui/react-tabs": "1.1.13",
+ "@radix-ui/react-toast": "1.2.15",
+ "@radix-ui/react-toggle": "1.1.10",
+ "@radix-ui/react-toggle-group": "1.1.11",
+ "@radix-ui/react-toolbar": "1.1.11",
+ "@radix-ui/react-tooltip": "1.2.8",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-escape-keydown": "1.1.1",
+ "@radix-ui/react-use-is-hydrated": "0.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
+ "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.55.0",
+ "@typescript-eslint/parser": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.1",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
+ "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25 || ^4"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/mcpjam-web/package.json b/mcpjam-web/package.json
new file mode 100644
index 000000000..87868af28
--- /dev/null
+++ b/mcpjam-web/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "mcpjam-web",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@mcp-ui/client": "^5.9.0",
+ "@mcpjam/sdk": "0.8.5",
+ "@modelcontextprotocol/ext-apps": "^1.0.1",
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "@tanstack/react-virtual": "^3.13.12",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "i": "^0.3.7",
+ "lucide-react": "^0.525.0",
+ "npm": "^11.10.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "radix-ui": "^1.4.2",
+ "sonner": "^2.0.6",
+ "tailwind-merge": "^3.3.1",
+ "tw-animate-css": "^1.3.5"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.1.13",
+ "@eslint/js": "^9.39.1",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "tailwindcss": "^4.1.13",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/mcpjam-web/public/mcp.svg b/mcpjam-web/public/mcp.svg
new file mode 100644
index 000000000..5cd83a8bf
--- /dev/null
+++ b/mcpjam-web/public/mcp.svg
@@ -0,0 +1 @@
+ModelContextProtocol
\ No newline at end of file
diff --git a/mcpjam-web/public/openai_logo.png b/mcpjam-web/public/openai_logo.png
new file mode 100644
index 000000000..ca0ef35c0
Binary files /dev/null and b/mcpjam-web/public/openai_logo.png differ
diff --git a/mcpjam-web/public/vite.svg b/mcpjam-web/public/vite.svg
new file mode 100644
index 000000000..e7b8dfb1b
--- /dev/null
+++ b/mcpjam-web/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mcpjam-web/src/App.tsx b/mcpjam-web/src/App.tsx
new file mode 100644
index 000000000..80dfc2f68
--- /dev/null
+++ b/mcpjam-web/src/App.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useState } from "react";
+import { AppShell } from "./components/layout/AppShell";
+import { PlaygroundPage } from "./pages/PlaygroundPage";
+import { ServerConnectionsPage } from "./pages/ServerConnectionsPage";
+
+export type AppRoute = "servers" | "playground";
+
+const DEFAULT_ROUTE: AppRoute = "servers";
+
+function getRouteFromHash(hash: string): AppRoute {
+ const normalized = hash.replace(/^#/, "");
+ if (normalized === "playground") {
+ return "playground";
+ }
+ return DEFAULT_ROUTE;
+}
+
+function App() {
+ const [route, setRoute] = useState(() =>
+ getRouteFromHash(window.location.hash),
+ );
+
+ useEffect(() => {
+ if (!window.location.hash) {
+ window.location.hash = DEFAULT_ROUTE;
+ }
+
+ const onHashChange = () => {
+ setRoute(getRouteFromHash(window.location.hash));
+ };
+
+ window.addEventListener("hashchange", onHashChange);
+ return () => window.removeEventListener("hashchange", onHashChange);
+ }, []);
+
+ const handleNavigate = (nextRoute: AppRoute) => {
+ window.location.hash = nextRoute;
+ };
+
+ return (
+
+ {route === "servers" ? : }
+
+ );
+}
+
+export default App;
diff --git a/mcpjam-web/src/assets/react.svg b/mcpjam-web/src/assets/react.svg
new file mode 100644
index 000000000..6c87de9bb
--- /dev/null
+++ b/mcpjam-web/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mcpjam-web/src/components/connection/AddServerModal.tsx b/mcpjam-web/src/components/connection/AddServerModal.tsx
new file mode 100644
index 000000000..e5ecf010a
--- /dev/null
+++ b/mcpjam-web/src/components/connection/AddServerModal.tsx
@@ -0,0 +1,358 @@
+import { useEffect, type FormEvent } from "react";
+import { toast } from "sonner";
+import { Button } from "../ui/button";
+import { Input } from "../ui/input";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "../ui/select";
+import { ChevronDown, ChevronRight } from "lucide-react";
+import type { ServerFormData } from "@/types/server-types";
+import { HOSTED_MODE } from "@/lib/config";
+import { useServerForm } from "./hooks/use-server-form";
+import { AuthenticationSection } from "./shared/AuthenticationSection";
+import { CustomHeadersSection } from "./shared/CustomHeadersSection";
+import { EnvVarsSection } from "./shared/EnvVarsSection";
+
+interface AddServerModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onSubmit: (formData: ServerFormData) => Promise;
+ initialData?: Partial;
+}
+
+export function AddServerModal({
+ isOpen,
+ onClose,
+ onSubmit,
+ initialData,
+}: AddServerModalProps) {
+ const formState = useServerForm();
+
+ // Initialize form with initial data if provided
+ useEffect(() => {
+ if (initialData && isOpen) {
+ if (initialData.name) {
+ formState.setName(initialData.name);
+ }
+ // Only set type if it's allowed (STDIO is disabled in web app)
+ if (initialData.type && !(HOSTED_MODE && initialData.type === "stdio")) {
+ formState.setType(initialData.type);
+ }
+ if (initialData.command) {
+ const fullCommand = initialData.args
+ ? `${initialData.command} ${initialData.args.join(" ")}`
+ : initialData.command;
+ formState.setCommandInput(fullCommand);
+ }
+ if (initialData.url) {
+ formState.setUrl(initialData.url);
+ }
+ if (initialData.env) {
+ const envArray = Object.entries(initialData.env).map(
+ ([key, value]) => ({
+ key,
+ value,
+ }),
+ );
+ formState.setEnvVars(envArray);
+ if (envArray.length > 0) {
+ formState.setShowEnvVars(true);
+ }
+ }
+ // Handle authentication configuration
+ if (initialData.useOAuth) {
+ formState.setAuthType("oauth");
+ formState.setShowAuthSettings(true);
+ } else if (
+ initialData.headers &&
+ initialData.headers["Authorization"] !== undefined
+ ) {
+ // Has Authorization header - set up bearer token
+ formState.setAuthType("bearer");
+ formState.setShowAuthSettings(true);
+ formState.setBearerToken(initialData.headers["Authorization"] || "");
+ }
+ }
+ }, [initialData, isOpen]);
+
+ const handleClose = () => {
+ formState.resetForm();
+ onClose();
+ };
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+
+ // Validate Client ID if using custom configuration
+ if (formState.authType === "oauth" && formState.useCustomClientId) {
+ const clientIdError = formState.validateClientId(formState.clientId);
+ if (clientIdError) {
+ toast.error(clientIdError);
+ return;
+ }
+
+ // Validate Client Secret if provided
+ if (formState.clientSecret) {
+ const clientSecretError = formState.validateClientSecret(
+ formState.clientSecret,
+ );
+ if (clientSecretError) {
+ toast.error(clientSecretError);
+ return;
+ }
+ }
+ }
+
+ // Validate form
+ const validationError = formState.validateForm();
+ if (validationError) {
+ toast.error(validationError);
+ return;
+ }
+
+ const finalFormData = formState.buildFormData();
+ try {
+ await onSubmit(finalFormData);
+ formState.resetForm();
+ onClose();
+ } catch (error) {
+ toast.error(
+ error instanceof Error ? error.message : "Failed to connect server",
+ );
+ }
+ };
+
+ return (
+
+ e.preventDefault()}
+ onInteractOutside={(e) => e.preventDefault()}
+ >
+
+
+ Add MCP Server
+
+
+
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/connection/EditServerModal.tsx b/mcpjam-web/src/components/connection/EditServerModal.tsx
new file mode 100644
index 000000000..cb87dcd7d
--- /dev/null
+++ b/mcpjam-web/src/components/connection/EditServerModal.tsx
@@ -0,0 +1,299 @@
+import type React from "react";
+import { toast } from "sonner";
+import { Button } from "../ui/button";
+import { Input } from "../ui/input";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+} from "../ui/dialog";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "../ui/select";
+import type { ServerFormData } from "@/types/server-types";
+import type { ServerWithName } from "@/types/server-types";
+import { useServerForm } from "./hooks/use-server-form";
+import { AuthenticationSection } from "./shared/AuthenticationSection";
+import { CustomHeadersSection } from "./shared/CustomHeadersSection";
+import { EnvVarsSection } from "./shared/EnvVarsSection";
+
+interface EditServerModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onSubmit: (
+ formData: ServerFormData,
+ originalServerName: string,
+ skipAutoConnect?: boolean,
+ ) => Promise;
+ server: ServerWithName;
+ skipAutoConnect?: boolean;
+ existingServerNames?: string[];
+}
+
+export function EditServerModal({
+ isOpen,
+ onClose,
+ onSubmit,
+ server,
+ skipAutoConnect = false,
+ existingServerNames = [],
+}: EditServerModalProps) {
+ // Use the shared form hook
+ const formState = useServerForm(server);
+ const trimmedName = formState.name.trim();
+ const isDuplicateServerName =
+ trimmedName !== "" &&
+ trimmedName !== server.name &&
+ existingServerNames.includes(trimmedName);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (isDuplicateServerName) {
+ toast.error(
+ `A server named "${trimmedName}" already exists. Choose a different name.`,
+ );
+ return;
+ }
+
+ // Validate Client ID if using custom configuration
+ if (formState.authType === "oauth" && formState.useCustomClientId) {
+ const clientIdError = formState.validateClientId(formState.clientId);
+ if (clientIdError) {
+ toast.error(clientIdError);
+ return;
+ }
+
+ // Validate Client Secret if provided
+ if (formState.clientSecret) {
+ const clientSecretError = formState.validateClientSecret(
+ formState.clientSecret,
+ );
+ if (clientSecretError) {
+ toast.error(clientSecretError);
+ return;
+ }
+ }
+ }
+
+ const finalFormData = formState.buildFormData();
+ try {
+ await onSubmit(finalFormData, server.id, skipAutoConnect);
+ handleClose();
+ } catch (error) {
+ toast.error(
+ error instanceof Error ? error.message : "Failed to update server",
+ );
+ }
+ };
+
+ const handleClose = () => {
+ onClose();
+ };
+
+ return (
+
+
+
+
+ Edit MCP Server
+
+
+ Edit your MCP server configuration and authentication settings
+
+
+
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/connection/JsonImportModal.tsx b/mcpjam-web/src/components/connection/JsonImportModal.tsx
new file mode 100644
index 000000000..fde57690e
--- /dev/null
+++ b/mcpjam-web/src/components/connection/JsonImportModal.tsx
@@ -0,0 +1,204 @@
+import { useState, useRef } from "react";
+import { toast } from "sonner";
+import { Button } from "../ui/button";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
+import { Label } from "../ui/label";
+import { Card } from "../ui/card";
+import { Upload } from "lucide-react";
+import { parseJsonConfig, validateJsonConfig } from "@/lib/json-config-parser";
+import type { ServerFormData } from "@/types/server-types";
+import { JsonEditor } from "../ui/json-editor";
+
+interface JsonImportModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onImport: (servers: ServerFormData[]) => void;
+}
+
+export function JsonImportModal({
+ isOpen,
+ onClose,
+ onImport,
+}: JsonImportModalProps) {
+ const [jsonContent, setJsonContent] = useState("");
+ const [validationResult, setValidationResult] = useState<{
+ success: boolean;
+ error?: string;
+ } | null>(null);
+ const [isImporting, setIsImporting] = useState(false);
+ const fileInputRef = useRef(null);
+ const handleFileUpload = (event: React.ChangeEvent) => {
+ const file = event.target.files?.[0];
+ if (!file) return;
+
+ if (file.type !== "application/json" && !file.name.endsWith(".json")) {
+ toast.error("Please select a valid JSON file");
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const content = e.target?.result as string;
+ setJsonContent(content);
+ // Validate the config format
+ if (content.trim()) {
+ const result = validateJsonConfig(content);
+ setValidationResult(result);
+ }
+ };
+ reader.readAsText(file);
+ };
+
+ const handleJsonChange = (content: string) => {
+ setJsonContent(content);
+ // Validate the JSON config format (not just JSON syntax)
+ if (!content.trim()) {
+ setValidationResult(null);
+ return;
+ }
+ const result = validateJsonConfig(content);
+ setValidationResult(result);
+ };
+
+ const handleValidationError = (error: string | null) => {
+ // If there's a JSON syntax error, show it
+ if (error) {
+ setValidationResult({ success: false, error });
+ }
+ // If JSON is valid, validate the config format
+ else if (jsonContent.trim()) {
+ const result = validateJsonConfig(jsonContent);
+ setValidationResult(result);
+ } else {
+ setValidationResult(null);
+ }
+ };
+
+ const handleImport = async () => {
+ if (!validationResult?.success) {
+ toast.error("Please fix the JSON validation errors before importing");
+ return;
+ }
+
+ setIsImporting(true);
+ try {
+ const servers = parseJsonConfig(jsonContent);
+ if (servers.length === 0) {
+ toast.error("No valid servers found in the JSON config");
+ return;
+ }
+
+ onImport(servers);
+ toast.success(
+ `Successfully imported ${servers.length} server${servers.length === 1 ? "" : "s"}`,
+ );
+ handleClose();
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error";
+ toast.error(`Failed to import servers: ${errorMessage}`);
+ } finally {
+ setIsImporting(false);
+ }
+ };
+
+ const handleClose = () => {
+ setJsonContent("");
+ setValidationResult(null);
+ setIsImporting(false);
+ onClose();
+ };
+
+ return (
+
+
+
+
+ Import Servers
+ from JSON
+
+
+
+
+ {/* File Upload Section */}
+
+
Upload JSON File
+
+
+ fileInputRef.current?.click()}
+ className="flex items-center gap-2"
+ >
+
+ Choose File
+
+
+
+
+ {/* JSON Input Section */}
+
+ JSON Configuration
+
+
+
+ {/* Example Config */}
+
+
+
Example JSON Format:
+
+ {`{
+ "mcpServers": {
+ "weather": {
+ "command": "/path/to/node",
+ "args": ["/path/to/weather-server.js"]
+ },
+ "asana": {
+ "type": "sse",
+ "url": "https://mcp.asana.com/sse"
+ }
+ }
+}`}
+
+
+
+
+
+ {/* Action Buttons */}
+
+
+ Cancel
+
+
+ {isImporting ? "Importing..." : "Import Servers"}
+
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/connection/ServerConnectionCard.tsx b/mcpjam-web/src/components/connection/ServerConnectionCard.tsx
new file mode 100644
index 000000000..68e5f53e3
--- /dev/null
+++ b/mcpjam-web/src/components/connection/ServerConnectionCard.tsx
@@ -0,0 +1,383 @@
+import { useState, useEffect } from "react";
+import { toast } from "sonner";
+import type { MCPClientManager } from "@mcpjam/sdk/browser";
+import { Card } from "../ui/card";
+import { Button } from "../ui/button";
+import { Separator } from "../ui/separator";
+import { Switch } from "../ui/switch";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "../ui/dropdown-menu";
+import {
+ MoreVertical,
+ Link2Off,
+ RefreshCw,
+ Loader2,
+ Copy,
+ Download,
+ Check,
+ Edit,
+ ExternalLink,
+ AlertCircle,
+} from "lucide-react";
+import type { ServerWithName } from "@/types/server-types";
+import { exportServerApi } from "@/lib/apis/mcp-export-api";
+import type { ListToolsResultWithMetadata } from "@/lib/apis/mcp-tools-api";
+import {
+ getConnectionStatusMeta,
+ getServerCommandDisplay,
+} from "./server-card-utils";
+import { ServerInfoModal } from "./ServerInfoModal";
+import { downloadJsonFile } from "@/lib/json-config-parser";
+
+interface ServerConnectionCardProps {
+ server: ServerWithName;
+ manager: MCPClientManager | null;
+ onDisconnect: (serverName: string) => void;
+ onReconnect: (
+ serverName: string,
+ options?: { forceOAuthFlow?: boolean },
+ ) => void;
+ onEdit: (server: ServerWithName) => void;
+ onRemove?: (serverName: string) => void;
+}
+
+export function ServerConnectionCard({
+ server,
+ manager,
+ onDisconnect,
+ onReconnect,
+ onEdit,
+ onRemove,
+}: ServerConnectionCardProps) {
+ const [isReconnecting, setIsReconnecting] = useState(false);
+ const [isExporting, setIsExporting] = useState(false);
+ const [isErrorExpanded, setIsErrorExpanded] = useState(false);
+ const [isInfoModalOpen, setIsInfoModalOpen] = useState(false);
+ const [copiedField, setCopiedField] = useState(null);
+ const [toolsData, setToolsData] =
+ useState(null);
+
+ const { label: connectionStatusLabel, indicatorColor } =
+ getConnectionStatusMeta(server.connectionStatus);
+ const commandDisplay = getServerCommandDisplay(server.config);
+
+ const initializationInfo = server.initializationInfo;
+ const serverIcon = initializationInfo?.serverVersion?.icons?.[0];
+ const version = initializationInfo?.serverVersion?.version;
+ const serverTitle = initializationInfo?.serverVersion?.title;
+ const websiteUrl = initializationInfo?.serverVersion?.websiteUrl;
+ const protocolVersion = initializationInfo?.protocolVersion;
+ const instructions = initializationInfo?.instructions;
+ const serverCapabilities = initializationInfo?.serverCapabilities;
+
+ const capabilities: string[] = [];
+ if (serverCapabilities && typeof serverCapabilities === "object") {
+ const caps = serverCapabilities as Record;
+ if (caps.tools) capabilities.push("Tools");
+ if (caps.prompts) capabilities.push("Prompts");
+ if (caps.resources) capabilities.push("Resources");
+ }
+
+ const hasInitInfo =
+ initializationInfo &&
+ (capabilities.length > 0 ||
+ protocolVersion ||
+ websiteUrl ||
+ instructions ||
+ serverCapabilities ||
+ serverTitle);
+
+ const hasError =
+ server.connectionStatus === "failed" && Boolean(server.lastError);
+
+ useEffect(() => {
+ const loadTools = async () => {
+ if (!manager || server.connectionStatus !== "connected") {
+ setToolsData(null);
+ return;
+ }
+ try {
+ const result = await manager.listTools(server.id);
+ const toolsMetadata = manager.getAllToolsMetadata(server.id);
+ setToolsData({ ...result, toolsMetadata });
+ } catch (err) {
+ console.error("Failed to load tools metadata:", err);
+ setToolsData(null);
+ }
+ };
+
+ void loadTools();
+ }, [manager, server.id, server.connectionStatus]);
+
+ const copyToClipboard = async (text: string, fieldName: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopiedField(fieldName);
+ setTimeout(() => setCopiedField(null), 2000);
+ } catch (error) {
+ console.error("Failed to copy text:", error);
+ }
+ };
+
+ const handleReconnect = async (options?: { forceOAuthFlow?: boolean }) => {
+ setIsReconnecting(true);
+ try {
+ onReconnect(server.id, options);
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error";
+ toast.error(`Failed to reconnect to ${server.name}: ${errorMessage}`);
+ } finally {
+ setIsReconnecting(false);
+ }
+ };
+
+ const handleExport = async () => {
+ if (!manager) {
+ toast.error("MCP manager unavailable");
+ return;
+ }
+
+ setIsExporting(true);
+ try {
+ const toastId = toast.loading(`Exporting ${server.name}...`);
+ const data = await exportServerApi(manager, server.id);
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
+ const filename = `mcp-server-export_${server.name}_${ts}.json`;
+ downloadJsonFile(filename, data);
+ toast.success(`Exported ${server.name} info to ${filename}`, {
+ id: toastId,
+ });
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error";
+ toast.error(`Failed to export ${server.name}: ${errorMessage}`);
+ } finally {
+ setIsExporting(false);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+ {serverIcon?.src && (
+
{
+ e.currentTarget.style.display = "none";
+ }}
+ />
+ )}
+
+ {server.name}
+
+ {version && (
+
v{version}
+ )}
+
+
+
+ {hasError && (
+
setIsErrorExpanded(true)}
+ className="inline-flex items-center gap-1 rounded-full border border-red-300/60 bg-red-500/10 px-2 py-0.5 text-[11px] text-red-700 dark:text-red-300 cursor-pointer"
+ >
+
+ Error
+
+ )}
+
+
+
+
+
e.stopPropagation()}
+ >
+
+
+
+ {server.connectionStatus === "failed"
+ ? `${connectionStatusLabel} (${server.retryCount})`
+ : connectionStatusLabel}
+
+
+
+ {
+ if (!checked) {
+ onDisconnect(server.id);
+ } else {
+ void handleReconnect();
+ }
+ }}
+ className="cursor-pointer scale-75"
+ />
+
+
+
+
+
+
+
+
+ {
+ const shouldForceOAuth =
+ server.useOAuth === true || server.oauthTokens != null;
+ void handleReconnect(
+ shouldForceOAuth ? { forceOAuthFlow: true } : undefined,
+ );
+ }}
+ disabled={
+ isReconnecting ||
+ server.connectionStatus === "connecting" ||
+ server.connectionStatus === "oauth-flow"
+ }
+ className="text-xs cursor-pointer"
+ >
+ {isReconnecting ? (
+
+ ) : (
+
+ )}
+ {isReconnecting ? "Reconnecting..." : "Reconnect"}
+
+ onEdit(server)}
+ className="text-xs cursor-pointer"
+ >
+
+ Edit
+
+ void handleExport()}
+ disabled={isExporting || server.connectionStatus !== "connected"}
+ className="text-xs cursor-pointer"
+ >
+ {isExporting ? (
+
+ ) : (
+
+ )}
+ {isExporting ? "Exporting..." : "Export server info"}
+
+
+ {
+ onDisconnect(server.id);
+ onRemove?.(server.id);
+ }}
+ >
+
+ Remove server
+
+
+
+
+
+
+
+
e.stopPropagation()}
+ >
+
{commandDisplay}
+
{
+ e.stopPropagation();
+ void copyToClipboard(commandDisplay, "command");
+ }}
+ className="absolute right-1 top-1 p-1 text-muted-foreground/60 transition-colors hover:text-foreground cursor-pointer"
+ >
+ {copiedField === "command" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {hasInitInfo && (
+ setIsInfoModalOpen(true)}
+ className="text-xs text-muted-foreground transition-colors hover:text-foreground cursor-pointer"
+ >
+ View server info
+
+ )}
+
+
+
+ {hasError && (
+
+
+ {isErrorExpanded
+ ? server.lastError
+ : server.lastError!.length > 140
+ ? `${server.lastError!.substring(0, 140)}...`
+ : server.lastError}
+
+ {server.lastError!.length > 140 && (
+
setIsErrorExpanded((prev) => !prev)}
+ className="mt-1 underline cursor-pointer"
+ >
+ {isErrorExpanded ? "Show less" : "Show more"}
+
+ )}
+ {server.retryCount > 0 && (
+
+ {server.retryCount} retry attempt
+ {server.retryCount !== 1 ? "s" : ""}
+
+ )}
+
+ )}
+
+ {server.connectionStatus === "failed" && (
+
+ )}
+
+
+ setIsInfoModalOpen(false)}
+ server={server}
+ toolsData={toolsData}
+ />
+ >
+ );
+}
diff --git a/mcpjam-web/src/components/connection/ServerInfoModal.tsx b/mcpjam-web/src/components/connection/ServerInfoModal.tsx
new file mode 100644
index 000000000..42862d552
--- /dev/null
+++ b/mcpjam-web/src/components/connection/ServerInfoModal.tsx
@@ -0,0 +1,569 @@
+import { useEffect, useState } from "react";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
+import {
+ Copy,
+ Check,
+ ExternalLink,
+ ChevronDown,
+ ChevronRight,
+} from "lucide-react";
+import type { ServerWithName } from "@/types/server-types";
+import type { ListToolsResultWithMetadata } from "@/lib/apis/mcp-tools-api";
+import { getStoredTokens } from "@/lib/oauth/mcp-oauth";
+import { decodeJWT } from "@/lib/oauth/jwt-decoder";
+import {
+ isMCPApp,
+ isOpenAIApp,
+ isOpenAIAppAndMCPApp,
+} from "@/lib/mcp-ui/mcp-apps-utils";
+import { JsonEditor } from "@/components/ui/json-editor";
+import type { Tool } from "@modelcontextprotocol/sdk/types.js";
+
+interface ServerInfoModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ server: ServerWithName;
+ toolsData: ListToolsResultWithMetadata | null;
+}
+
+export function ServerInfoModal({
+ isOpen,
+ onClose,
+ server,
+ toolsData,
+}: ServerInfoModalProps) {
+ const [copiedField, setCopiedField] = useState(null);
+ const [expandedTokens, setExpandedTokens] = useState>(new Set());
+
+ const oauthTokens = server.oauthTokens || getStoredTokens(server.id);
+ const isHttpServer = "url" in server.config;
+
+ useEffect(() => {
+ if (!isOpen) {
+ setCopiedField(null);
+ setExpandedTokens(new Set());
+ }
+ }, [isOpen, server.name]);
+
+ const initializationInfo = server.initializationInfo;
+
+ // Extract server info
+ const serverName = initializationInfo?.serverVersion?.name;
+ const serverTitle = initializationInfo?.serverVersion?.title;
+ const version = initializationInfo?.serverVersion?.version;
+ const websiteUrl = initializationInfo?.serverVersion?.websiteUrl;
+ const serverIcon = initializationInfo?.serverVersion?.icons?.[0];
+ const protocolVersion = initializationInfo?.protocolVersion;
+ const transport = initializationInfo?.transport;
+ const instructions = initializationInfo?.instructions;
+ const serverCapabilities = initializationInfo?.serverCapabilities;
+ const clientCapabilities = initializationInfo?.clientCapabilities;
+
+ // Build capabilities list
+ const capabilities: string[] = [];
+ if (serverCapabilities?.tools) capabilities.push("Tools");
+ if (serverCapabilities?.prompts) capabilities.push("Prompts");
+ if (serverCapabilities?.resources) capabilities.push("Resources");
+
+ // Check if this is an MCP App (has tools with ui.resourceUri metadata)
+ const isMCPAppServer = isMCPApp(toolsData);
+
+ // Check if this is an OpenAI app (has tools with openai/outputTemplate metadata)
+ const isOpenAIAppServer = isOpenAIApp(toolsData);
+
+ // Check if this is an OpenAI app and MCP app (has tools with openai/outputTemplate and ui.resourceUri metadata)
+ const isOpenAIAppAndMCPAppServer = isOpenAIAppAndMCPApp(toolsData);
+
+ // Has any widget metadata (either MCP App or OpenAI App)
+ const hasWidgetMetadata =
+ isMCPAppServer || isOpenAIAppServer || isOpenAIAppAndMCPAppServer;
+ const copyToClipboard = async (text: string, fieldName: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopiedField(fieldName);
+ setTimeout(() => setCopiedField(null), 2000);
+ } catch (error) {
+ console.error("Failed to copy text:", error);
+ }
+ };
+
+ const toggleTokenExpansion = (tokenName: string) => {
+ setExpandedTokens((prev) => {
+ const next = new Set(prev);
+ if (next.has(tokenName)) {
+ next.delete(tokenName);
+ } else {
+ next.add(tokenName);
+ }
+ return next;
+ });
+ };
+
+ const renderToken = (
+ label: string,
+ tokenValue: string | undefined,
+ tokenKey: string,
+ ) => {
+ if (!tokenValue) return null;
+ const decoded = decodeJWT(tokenValue);
+
+ return (
+
+
{label}:
+
toggleTokenExpansion(tokenKey)}
+ >
+
+ {expandedTokens.has(tokenKey) || tokenValue.length <= 50
+ ? tokenValue
+ : `${tokenValue.substring(0, 50)}...`}
+
+
{
+ e.stopPropagation();
+ copyToClipboard(tokenValue, tokenKey);
+ }}
+ className="absolute top-1 right-1 p-1 text-muted-foreground/50 hover:text-foreground transition-colors cursor-pointer"
+ >
+ {copiedField === tokenKey ? (
+
+ ) : (
+
+ )}
+
+
+ {decoded && (
+
+
toggleTokenExpansion(`${tokenKey}Decoded`)}
+ className="text-muted-foreground hover:text-foreground cursor-pointer flex items-center gap-1"
+ >
+ {expandedTokens.has(`${tokenKey}Decoded`) ? (
+
+ ) : (
+
+ )}
+ View Decoded JWT
+
+ {expandedTokens.has(`${tokenKey}Decoded`) && (
+
+
+
+ )}
+
+ )}
+
+ );
+ };
+
+ const renderOAuthTokensSection = () => {
+ if (!isHttpServer || !oauthTokens) return null;
+
+ return (
+
+
+ OAuth Tokens
+
+
+ {renderToken("Access Token", oauthTokens.access_token, "accessToken")}
+ {renderToken(
+ "Refresh Token",
+ oauthTokens.refresh_token,
+ "refreshToken",
+ )}
+ {renderToken("ID Token", (oauthTokens as any).id_token, "idToken")}
+
+
+ Type: {oauthTokens.token_type || "Bearer"}
+ {oauthTokens.expires_in && (
+ Expires in: {oauthTokens.expires_in}s
+ )}
+ {oauthTokens.scope && Scope: {oauthTokens.scope} }
+
+
+
+ );
+ };
+
+ const renderIconRow = () => (
+
+
Icon
+ {serverIcon?.src ? (
+
+ ) : (
+
+ No icon provided
+
+ )}
+
+ );
+
+ return (
+
+
+
+
+ {server.name}
+ {version && (
+
+ v{version}
+
+ )}
+ {(isOpenAIAppServer || isOpenAIAppAndMCPAppServer) && (
+
+ )}
+ {(isMCPAppServer || isOpenAIAppAndMCPAppServer) && (
+
+ )}
+
+
+
+ {hasWidgetMetadata ? (
+
+
+ Server Info
+ Tools Metadata
+
+
+
+ {serverName && (
+
+
+ Server Name
+
+
{serverName}
+
+ )}
+
+ {serverTitle && (
+
+
+ Server Title
+
+
{serverTitle}
+
+ )}
+
+ {renderIconRow()}
+
+ {protocolVersion && (
+
+
+ MCP Protocol Version
+
+
{protocolVersion}
+
+ )}
+
+ {transport && (
+
+
+ Transport
+
+
{transport}
+
+ )}
+
+ {capabilities.length > 0 && (
+
+
+ Capabilities
+
+
{capabilities.join(", ")}
+
+ )}
+
+ {instructions && (
+
+
+ Instructions
+
+
+ {instructions}
+
+
+ )}
+
+ {serverCapabilities && (
+
+
+ Server Capabilities
+
+
+
+ )}
+
+ {clientCapabilities && (
+
+
+ Client Capabilities
+
+
+
+ )}
+
+ {websiteUrl && (
+
+ )}
+
+ {renderOAuthTokensSection()}
+
+
+
+ {(toolsData?.tools ?? []).some(
+ (tool) =>
+ (tool as Tool)?._meta ||
+ toolsData?.toolsMetadata?.[tool.name],
+ ) ? (
+
+ {(toolsData?.tools ?? [])
+ .map((tool: Tool) => {
+ const metadata =
+ tool._meta ?? toolsData?.toolsMetadata?.[tool.name];
+ const annotations = tool.annotations;
+
+ if (!metadata) return null;
+ return (
+
+
+
+
+
+ {tool.name}
+
+ {metadata?.write !== undefined && (
+
+ {metadata?.write ? "WRITE" : "READ"}
+
+ )}
+
+
+ {tool.description || "No description available"}
+
+
+
+
+ {/* Metadata Section */}
+ {metadata && (
+
+
+ METADATA
+
+
+ {Object.entries(metadata ?? {}).map(
+ ([key, value]) => {
+ if (key === "write") return null;
+
+ return (
+
+
+ {key.replace(/([A-Z])/g, " $1").trim()}
+
+
+ {typeof value === "object"
+ ? JSON.stringify(value, null, 2)
+ : String(value)}
+
+
+ );
+ },
+ )}
+
+ )}
+
+ {annotations && (
+
+ )}
+
+ );
+ })
+ .filter(Boolean)}
+
+ ) : (
+
+ No widget metadata available
+
+ )}
+
+
+ ) : (
+
+ {serverName && (
+
+
+ Server Name
+
+
{serverName}
+
+ )}
+
+ {serverTitle && (
+
+
+ Server Title
+
+
{serverTitle}
+
+ )}
+
+ {renderIconRow()}
+
+ {protocolVersion && (
+
+
+ MCP Protocol Version
+
+
{protocolVersion}
+
+ )}
+
+ {transport && (
+
+
+ Transport
+
+
{transport}
+
+ )}
+
+ {capabilities.length > 0 && (
+
+
+ Capabilities
+
+
{capabilities.join(", ")}
+
+ )}
+
+ {instructions && (
+
+
+ Instructions
+
+
+ {instructions}
+
+
+ )}
+
+ {serverCapabilities && (
+
+
+ Server Capabilities
+
+
+
+ )}
+
+ {clientCapabilities && (
+
+
+ Client Capabilities
+
+
+
+ )}
+
+ {websiteUrl && (
+
+ )}
+
+ {renderOAuthTokensSection()}
+
+ )}
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/connection/hooks/use-server-form.ts b/mcpjam-web/src/components/connection/hooks/use-server-form.ts
new file mode 100644
index 000000000..ffb89fc9b
--- /dev/null
+++ b/mcpjam-web/src/components/connection/hooks/use-server-form.ts
@@ -0,0 +1,390 @@
+import { useState, useEffect } from "react";
+import type { ServerFormData } from "@/types/server-types";
+import type { ServerWithName } from "@/types/server-types";
+import { hasOAuthConfig, getStoredTokens } from "@/lib/oauth/mcp-oauth";
+import { HOSTED_MODE } from "@/lib/config";
+
+export function useServerForm(server?: ServerWithName) {
+ const [name, setName] = useState("");
+ const [type, setType] = useState<"stdio" | "http">("http");
+ const [commandInput, setCommandInput] = useState("");
+ const [url, setUrl] = useState("");
+
+ const [oauthScopesInput, setOauthScopesInput] = useState("");
+ const [clientId, setClientId] = useState("");
+ const [clientSecret, setClientSecret] = useState("");
+ const [bearerToken, setBearerToken] = useState("");
+ const [authType, setAuthType] = useState<"oauth" | "bearer" | "none">("none");
+ const [useCustomClientId, setUseCustomClientId] = useState(false);
+
+ const [clientIdError, setClientIdError] = useState(null);
+ const [clientSecretError, setClientSecretError] = useState(
+ null,
+ );
+
+ const [envVars, setEnvVars] = useState>(
+ [],
+ );
+ const [customHeaders, setCustomHeaders] = useState<
+ Array<{ key: string; value: string }>
+ >([]);
+ const [requestTimeout, setRequestTimeout] = useState("10000");
+
+ const [showConfiguration, setShowConfiguration] = useState(false);
+ const [showEnvVars, setShowEnvVars] = useState(false);
+ const [showAuthSettings, setShowAuthSettings] = useState(false);
+
+ // Initialize form with server data (for edit mode)
+ useEffect(() => {
+ if (server) {
+ const config = server.config;
+ const isHttpServer = "url" in config;
+
+ // For HTTP servers, check OAuth from multiple sources like the original
+ let hasOAuth = false;
+ let scopes: string[] = [];
+ let clientIdValue = "";
+ let clientSecretValue = "";
+
+ if (isHttpServer) {
+ // Check if OAuth is configured by looking at multiple sources:
+ // 1. Check if server has oauth tokens
+ // 2. Check if there's stored OAuth data
+ const hasOAuthTokens = server.oauthTokens != null;
+ const hasStoredOAuthConfig = hasOAuthConfig(server.id);
+ hasOAuth = hasOAuthTokens || hasStoredOAuthConfig;
+
+ const storedOAuthConfig = localStorage.getItem(
+ `mcpjam-web-oauth-config-${server.id}`,
+ );
+ const storedClientInfo = localStorage.getItem(
+ `mcpjam-web-oauth-client-${server.id}`,
+ );
+ const storedTokens = getStoredTokens(server.id);
+
+ const clientInfo = storedClientInfo ? JSON.parse(storedClientInfo) : {};
+ const oauthConfig = storedOAuthConfig
+ ? JSON.parse(storedOAuthConfig)
+ : {};
+
+ // Retrieve scopes from multiple sources (prioritize stored tokens/storage)
+ scopes =
+ server.oauthTokens?.scope?.split(" ") ||
+ storedTokens?.scope?.split(" ") ||
+ oauthConfig.scopes ||
+ [];
+
+ // Get client ID and secret from multiple sources (prioritize stored)
+ clientIdValue = storedTokens?.client_id || clientInfo?.client_id || "";
+
+ clientSecretValue = clientInfo?.client_secret || "";
+ }
+
+ setName(server.name);
+ setType(server.config.command ? "stdio" : "http");
+ setUrl(isHttpServer && config.url ? config.url.toString() : "");
+
+ // For STDIO servers, combine command and args into commandInput
+ if (server.config.command) {
+ const fullCommand = [
+ server.config.command,
+ ...(server.config.args || []),
+ ]
+ .filter(Boolean)
+ .join(" ");
+ setCommandInput(fullCommand);
+ }
+
+ // Don't set a default scope for existing servers - use what's configured
+ // Only set default for new servers
+ setOauthScopesInput(scopes.join(" "));
+ setRequestTimeout(String(config.timeout || 10000));
+
+ // Set auth type based on multiple OAuth detection sources
+ if (hasOAuth) {
+ setAuthType("oauth");
+ setShowAuthSettings(true);
+ } else if (
+ isHttpServer &&
+ config.requestInit?.headers &&
+ typeof config.requestInit.headers === "object" &&
+ "Authorization" in config.requestInit.headers &&
+ typeof config.requestInit.headers.Authorization === "string" &&
+ config.requestInit.headers.Authorization.startsWith("Bearer ")
+ ) {
+ setAuthType("bearer");
+ setBearerToken(
+ config.requestInit.headers.Authorization.replace("Bearer ", ""),
+ );
+ setShowAuthSettings(true);
+ } else {
+ setAuthType("none");
+ setShowAuthSettings(false);
+ }
+
+ // Set custom OAuth credentials if present (from any source)
+ if (clientIdValue) {
+ setUseCustomClientId(true);
+ setClientId(clientIdValue);
+ setClientSecret(clientSecretValue);
+ }
+
+ // Initialize env vars for STDIO servers
+ if (!isHttpServer && config.env) {
+ const envArray = Object.entries(config.env).map(([key, value]) => ({
+ key,
+ value: String(value),
+ }));
+ setEnvVars(envArray);
+ }
+
+ // Initialize custom headers for HTTP servers (excluding Authorization)
+ if (
+ isHttpServer &&
+ config.requestInit?.headers &&
+ typeof config.requestInit.headers === "object"
+ ) {
+ const headersArray = Object.entries(config.requestInit.headers)
+ .filter(([key]) => key !== "Authorization")
+ .map(([key, value]) => ({ key, value: String(value) }));
+ setCustomHeaders(headersArray);
+ }
+ }
+ }, [server]);
+
+ // Validation functions
+ const validateClientId = (value: string): string | null => {
+ if (!value || value.trim() === "") {
+ return "Client ID is required when using custom credentials";
+ }
+ if (value.length < 3) {
+ return "Client ID must be at least 3 characters";
+ }
+ return null;
+ };
+
+ const validateClientSecret = (value: string): string | null => {
+ if (value && value.length < 8) {
+ return "Client Secret must be at least 8 characters if provided";
+ }
+ return null;
+ };
+
+ const validateForm = (): string | null => {
+ if (!name || name.trim() === "") {
+ return "Server name is required";
+ }
+
+ if (type === "stdio") {
+ if (!commandInput || commandInput.trim() === "") {
+ return "Command is required for STDIO servers";
+ }
+ } else if (type === "http") {
+ if (!url || url.trim() === "") {
+ return "URL is required for HTTP servers";
+ }
+
+ // Enforce HTTPS in hosted mode
+ if (HOSTED_MODE) {
+ try {
+ const urlObj = new URL(url.trim());
+ if (urlObj.protocol !== "https:") {
+ return "HTTPS is required in web app";
+ }
+ } catch {
+ return "Invalid URL format";
+ }
+ }
+ }
+
+ return null;
+ };
+
+ // Helper functions
+ const addEnvVar = () => {
+ setEnvVars([...envVars, { key: "", value: "" }]);
+ setShowEnvVars(true);
+ };
+
+ const removeEnvVar = (index: number) => {
+ setEnvVars(envVars.filter((_, i) => i !== index));
+ };
+
+ const updateEnvVar = (
+ index: number,
+ field: "key" | "value",
+ value: string,
+ ) => {
+ const updated = [...envVars];
+ updated[index][field] = value;
+ setEnvVars(updated);
+ };
+
+ const addCustomHeader = () => {
+ setCustomHeaders([...customHeaders, { key: "", value: "" }]);
+ };
+
+ const removeCustomHeader = (index: number) => {
+ setCustomHeaders(customHeaders.filter((_, i) => i !== index));
+ };
+
+ const updateCustomHeader = (
+ index: number,
+ field: "key" | "value",
+ value: string,
+ ) => {
+ const updated = [...customHeaders];
+ updated[index][field] = value;
+ setCustomHeaders(updated);
+ };
+
+ const buildFormData = (): ServerFormData => {
+ const reqTimeout = parseInt(requestTimeout) || 10000;
+
+ // Handle stdio-specific data
+ if (type === "stdio") {
+ // Parse commandInput to extract command and args
+ const parts = commandInput
+ .trim()
+ .split(/\s+/)
+ .filter((part) => part.length > 0);
+ const command = parts[0] || "";
+ const args = parts.slice(1);
+
+ // Build environment variables
+ const env: Record = {};
+ envVars.forEach(({ key, value }) => {
+ if (key.trim()) {
+ env[key.trim()] = value;
+ }
+ });
+
+ return {
+ name: name.trim(),
+ type: "stdio",
+ command: command.trim(),
+ args,
+ env,
+ requestTimeout: reqTimeout,
+ };
+ }
+
+ // Handle http-specific data
+ const headers: Record = {};
+
+ // Add custom headers
+ customHeaders.forEach(({ key, value }) => {
+ if (key.trim()) {
+ headers[key.trim()] = value;
+ }
+ });
+
+ // Parse OAuth scopes from input
+ const scopes = oauthScopesInput
+ .trim()
+ .split(/\s+/)
+ .filter((s) => s.length > 0);
+
+ // Handle authentication
+ let useOAuth = false;
+ if (authType === "bearer" && bearerToken) {
+ headers["Authorization"] = `Bearer ${bearerToken.trim()}`;
+ } else if (authType === "oauth") {
+ useOAuth = true;
+ }
+
+ return {
+ name: name.trim(),
+ type: "http",
+ url: url.trim(),
+ headers,
+ useOAuth,
+ oauthScopes: scopes.length > 0 ? scopes : undefined,
+ clientId: clientId.trim() || undefined,
+ clientSecret: clientSecret.trim() || undefined,
+ requestTimeout: reqTimeout,
+ };
+ };
+
+ const resetForm = () => {
+ setName("");
+ setType("http");
+ setCommandInput("");
+ setUrl("");
+ setOauthScopesInput("");
+ setClientId("");
+ setClientSecret("");
+ setBearerToken("");
+ setAuthType("none");
+ setUseCustomClientId(false);
+ setClientIdError(null);
+ setClientSecretError(null);
+ setEnvVars([]);
+ setCustomHeaders([]);
+ setRequestTimeout("10000");
+ setShowConfiguration(false);
+ setShowEnvVars(false);
+ setShowAuthSettings(false);
+ };
+
+ return {
+ // Form data
+ name,
+ setName,
+ type,
+ setType,
+ commandInput,
+ setCommandInput,
+ url,
+ setUrl,
+
+ // Auth states
+ oauthScopesInput,
+ setOauthScopesInput,
+ clientId,
+ setClientId,
+ clientSecret,
+ setClientSecret,
+ bearerToken,
+ setBearerToken,
+ authType,
+ setAuthType,
+ useCustomClientId,
+ setUseCustomClientId,
+ requestTimeout,
+ setRequestTimeout,
+
+ // Validation states
+ clientIdError,
+ setClientIdError,
+ clientSecretError,
+ setClientSecretError,
+
+ // Arrays
+ envVars,
+ setEnvVars,
+ customHeaders,
+ setCustomHeaders,
+
+ // Toggle states
+ showConfiguration,
+ setShowConfiguration,
+ showEnvVars,
+ setShowEnvVars,
+ showAuthSettings,
+ setShowAuthSettings,
+
+ // Functions
+ validateClientId,
+ validateClientSecret,
+ validateForm,
+ addEnvVar,
+ removeEnvVar,
+ updateEnvVar,
+ addCustomHeader,
+ removeCustomHeader,
+ updateCustomHeader,
+ buildFormData,
+ resetForm,
+ };
+}
diff --git a/mcpjam-web/src/components/connection/server-card-utils.ts b/mcpjam-web/src/components/connection/server-card-utils.ts
new file mode 100644
index 000000000..3530b9136
--- /dev/null
+++ b/mcpjam-web/src/components/connection/server-card-utils.ts
@@ -0,0 +1,61 @@
+import type { ComponentType } from "react";
+import { Check, Loader2, Wifi, X } from "lucide-react";
+import type { MCPServerConfig } from "@mcpjam/sdk";
+import type { ConnectionStatus } from "@/types/server-types";
+
+interface ConnectionStatusMeta {
+ label: string;
+ indicatorColor: string;
+ Icon: ComponentType<{ className?: string }>;
+ iconClassName: string;
+}
+
+const connectionStatusMeta: Record = {
+ connected: {
+ label: "Connected",
+ indicatorColor: "#10b981",
+ Icon: Check,
+ iconClassName: "h-3 w-3 text-green-500",
+ },
+ connecting: {
+ label: "Connecting...",
+ indicatorColor: "#3b82f6",
+ Icon: Loader2,
+ iconClassName: "h-3 w-3 text-blue-500 animate-spin",
+ },
+ "oauth-flow": {
+ label: "Authorizing...",
+ indicatorColor: "#a855f7",
+ Icon: Loader2,
+ iconClassName: "h-3 w-3 text-purple-500 animate-spin",
+ },
+ failed: {
+ label: "Failed",
+ indicatorColor: "#ef4444",
+ Icon: X,
+ iconClassName: "h-3 w-3 text-red-500",
+ },
+ disconnected: {
+ label: "Disconnected",
+ indicatorColor: "#9ca3af",
+ Icon: Wifi,
+ iconClassName: "h-3 w-3 text-gray-500",
+ },
+};
+
+export const getConnectionStatusMeta = (status: ConnectionStatus) =>
+ connectionStatusMeta[status] || connectionStatusMeta.disconnected;
+
+export const getServerCommandDisplay = (config: MCPServerConfig): string => {
+ if (config.url) {
+ return config.url.toString();
+ }
+
+ const command = config.command ?? "";
+ const args = config.args ?? [];
+ return [command, ...args].filter(Boolean).join(" ").trim();
+};
+
+export const getServerTransportLabel = (config: MCPServerConfig): string => {
+ return config.url ? "HTTP/SSE" : "STDIO";
+};
diff --git a/mcpjam-web/src/components/connection/shared/AuthenticationSection.tsx b/mcpjam-web/src/components/connection/shared/AuthenticationSection.tsx
new file mode 100644
index 000000000..18ab3e7f4
--- /dev/null
+++ b/mcpjam-web/src/components/connection/shared/AuthenticationSection.tsx
@@ -0,0 +1,161 @@
+import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+interface AuthenticationSectionProps {
+ authType: "oauth" | "bearer" | "none";
+ onAuthTypeChange: (value: "oauth" | "bearer" | "none") => void;
+ showAuthSettings: boolean;
+ bearerToken: string;
+ onBearerTokenChange: (value: string) => void;
+ oauthScopesInput: string;
+ onOauthScopesChange: (value: string) => void;
+ useCustomClientId: boolean;
+ onUseCustomClientIdChange: (value: boolean) => void;
+ clientId: string;
+ onClientIdChange: (value: string) => void;
+ clientSecret: string;
+ onClientSecretChange: (value: string) => void;
+ clientIdError: string | null;
+ clientSecretError: string | null;
+}
+
+export function AuthenticationSection({
+ authType,
+ onAuthTypeChange,
+ showAuthSettings,
+ bearerToken,
+ onBearerTokenChange,
+ oauthScopesInput,
+ onOauthScopesChange,
+ useCustomClientId,
+ onUseCustomClientIdChange,
+ clientId,
+ onClientIdChange,
+ clientSecret,
+ onClientSecretChange,
+ clientIdError,
+ clientSecretError,
+}: AuthenticationSectionProps) {
+ return (
+
+
+
+
+ Authentication
+
+
+
+
+
+
+ No Authentication
+ Bearer Token
+ OAuth 2.0
+
+
+
+
+ {/* Bearer Token Settings */}
+ {showAuthSettings && authType === "bearer" && (
+
+
+ Bearer Token
+
+ onBearerTokenChange(e.target.value)}
+ placeholder="Enter your bearer token"
+ className="h-10"
+ />
+
+ )}
+
+ {/* OAuth Settings */}
+ {showAuthSettings && authType === "oauth" && (
+
+
+
+ OAuth Scopes
+
+
onOauthScopesChange(e.target.value)}
+ placeholder="mcp:* or custom scopes separated by spaces"
+ className="h-10"
+ />
+
+ Default: mcp:* (space-separated for multiple scopes)
+
+
+
+
+
+ onUseCustomClientIdChange(e.target.checked)}
+ className="rounded"
+ />
+
+ Use custom OAuth credentials
+
+
+
+ Leave unchecked to use the server's default OAuth flow
+
+
+
+ {useCustomClientId && (
+
+
+
+ Client ID
+
+
onClientIdChange(e.target.value)}
+ placeholder="Your OAuth Client ID"
+ className={`h-10 ${clientIdError ? "border-red-500" : ""}`}
+ />
+ {clientIdError && (
+
{clientIdError}
+ )}
+
+
+
+
+ Client Secret (Optional)
+
+
onClientSecretChange(e.target.value)}
+ placeholder="Your OAuth Client Secret"
+ className={`h-10 ${clientSecretError ? "border-red-500" : ""}`}
+ />
+ {clientSecretError && (
+
{clientSecretError}
+ )}
+
+ Optional for public clients using PKCE
+
+
+
+ )}
+
+ )}
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/connection/shared/CustomHeadersSection.tsx b/mcpjam-web/src/components/connection/shared/CustomHeadersSection.tsx
new file mode 100644
index 000000000..ba12f0bc4
--- /dev/null
+++ b/mcpjam-web/src/components/connection/shared/CustomHeadersSection.tsx
@@ -0,0 +1,57 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+
+interface CustomHeadersSectionProps {
+ customHeaders: Array<{ key: string; value: string }>;
+ onAdd: () => void;
+ onRemove: (index: number) => void;
+ onUpdate: (index: number, field: "key" | "value", value: string) => void;
+}
+
+export function CustomHeadersSection({
+ customHeaders,
+ onAdd,
+ onRemove,
+ onUpdate,
+}: CustomHeadersSectionProps) {
+ return (
+
+
+
+ Custom Headers
+
+
+ Add Header
+
+
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/connection/shared/EnvVarsSection.tsx b/mcpjam-web/src/components/connection/shared/EnvVarsSection.tsx
new file mode 100644
index 000000000..916718fae
--- /dev/null
+++ b/mcpjam-web/src/components/connection/shared/EnvVarsSection.tsx
@@ -0,0 +1,98 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { ChevronDown, ChevronRight } from "lucide-react";
+
+interface EnvVarsSectionProps {
+ envVars: Array<{ key: string; value: string }>;
+ showEnvVars: boolean;
+ onToggle: () => void;
+ onAdd: () => void;
+ onRemove: (index: number) => void;
+ onUpdate: (index: number, field: "key" | "value", value: string) => void;
+}
+
+export function EnvVarsSection({
+ envVars,
+ showEnvVars,
+ onToggle,
+ onAdd,
+ onRemove,
+ onUpdate,
+}: EnvVarsSectionProps) {
+ return (
+
+
+
+ {showEnvVars ? (
+
+ ) : (
+
+ )}
+
+ Environment Variables
+
+ {envVars.length > 0 && (
+
+ ({envVars.length})
+
+ )}
+
+ {
+ e.stopPropagation();
+ onAdd();
+ }}
+ className="text-xs"
+ >
+ Add Variable
+
+
+
+ {showEnvVars && envVars.length > 0 && (
+
+ )}
+
+ {!showEnvVars && (
+
+
+ Environment variables for your MCP server process (e.g. API keys,
+ config values)
+
+
+ )}
+
+ );
+}
diff --git a/mcpjam-web/src/components/evals/ErrorBoundary.tsx b/mcpjam-web/src/components/evals/ErrorBoundary.tsx
new file mode 100644
index 000000000..1a47f8807
--- /dev/null
+++ b/mcpjam-web/src/components/evals/ErrorBoundary.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode;
+ fallback?: React.ReactNode;
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+}
+
+export class ErrorBoundary extends React.Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(): ErrorBoundaryState {
+ return { hasError: true };
+ }
+
+ componentDidCatch(error: Error) {
+ console.error("ErrorBoundary caught an error:", error);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return this.props.fallback ?? null;
+ }
+ return this.props.children;
+ }
+}
diff --git a/mcpjam-web/src/components/layout/AppShell.tsx b/mcpjam-web/src/components/layout/AppShell.tsx
new file mode 100644
index 000000000..f7cf260b8
--- /dev/null
+++ b/mcpjam-web/src/components/layout/AppShell.tsx
@@ -0,0 +1,30 @@
+import type { ReactNode } from "react";
+import type { AppRoute } from "../../App";
+import { SidebarNav } from "./SidebarNav";
+import { TopHeader } from "./TopHeader";
+
+interface AppShellProps {
+ route: AppRoute;
+ onNavigate: (route: AppRoute) => void;
+ children: ReactNode;
+}
+
+export function AppShell({ route, onNavigate, children }: AppShellProps) {
+ return (
+
+ );
+}
diff --git a/mcpjam-web/src/components/layout/SidebarNav.tsx b/mcpjam-web/src/components/layout/SidebarNav.tsx
new file mode 100644
index 000000000..fc9154e92
--- /dev/null
+++ b/mcpjam-web/src/components/layout/SidebarNav.tsx
@@ -0,0 +1,39 @@
+import type { AppRoute } from "../../App";
+
+interface SidebarNavProps {
+ route: AppRoute;
+ onNavigate: (route: AppRoute) => void;
+ compact?: boolean;
+}
+
+const NAV_ITEMS: Array<{ route: AppRoute; label: string }> = [
+ { route: "servers", label: "Server Connections" },
+ { route: "playground", label: "LLM Playground" },
+];
+
+export function SidebarNav({
+ route,
+ onNavigate,
+ compact = false,
+}: SidebarNavProps) {
+ return (
+
+ {NAV_ITEMS.map((item) => {
+ const isActive = item.route === route;
+ return (
+ onNavigate(item.route)}
+ >
+ {item.label}
+
+ );
+ })}
+
+ );
+}
diff --git a/mcpjam-web/src/components/layout/TopHeader.tsx b/mcpjam-web/src/components/layout/TopHeader.tsx
new file mode 100644
index 000000000..78391881d
--- /dev/null
+++ b/mcpjam-web/src/components/layout/TopHeader.tsx
@@ -0,0 +1,19 @@
+import type { AppRoute } from "../../App";
+
+interface TopHeaderProps {
+ route: AppRoute;
+}
+
+const ROUTE_TITLES: Record = {
+ servers: "Server Connections",
+ playground: "LLM Playground",
+};
+
+export function TopHeader({ route }: TopHeaderProps) {
+ return (
+
+ MCPJam Web
+ {ROUTE_TITLES[route]}
+
+ );
+}
diff --git a/mcpjam-web/src/components/shared/ErrorBoundary.tsx b/mcpjam-web/src/components/shared/ErrorBoundary.tsx
new file mode 100644
index 000000000..62ed41795
--- /dev/null
+++ b/mcpjam-web/src/components/shared/ErrorBoundary.tsx
@@ -0,0 +1 @@
+export { ErrorBoundary } from "@/components/evals/ErrorBoundary";
diff --git a/mcpjam-web/src/components/ui/button.tsx b/mcpjam-web/src/components/ui/button.tsx
new file mode 100644
index 000000000..5703fc464
--- /dev/null
+++ b/mcpjam-web/src/components/ui/button.tsx
@@ -0,0 +1,59 @@
+import * as React from "react";
+import { Slot as SlotPrimitive } from "radix-ui";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 text-sm has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 text-xs has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 text-base has-[>svg]:px-4",
+ icon: "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? SlotPrimitive.Slot : "button";
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/mcpjam-web/src/components/ui/card.tsx b/mcpjam-web/src/components/ui/card.tsx
new file mode 100644
index 000000000..113d66c74
--- /dev/null
+++ b/mcpjam-web/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/mcpjam-web/src/components/ui/checkbox.tsx b/mcpjam-web/src/components/ui/checkbox.tsx
new file mode 100644
index 000000000..234344030
--- /dev/null
+++ b/mcpjam-web/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+import * as React from "react";
+import { Checkbox as CheckboxPrimitive } from "radix-ui";
+import { CheckIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Checkbox({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ );
+}
+
+export { Checkbox };
diff --git a/mcpjam-web/src/components/ui/dialog.tsx b/mcpjam-web/src/components/ui/dialog.tsx
new file mode 100644
index 000000000..b240c8c32
--- /dev/null
+++ b/mcpjam-web/src/components/ui/dialog.tsx
@@ -0,0 +1,141 @@
+import * as React from "react";
+import { Dialog as DialogPrimitive } from "radix-ui";
+import { XIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean;
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ );
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+};
diff --git a/mcpjam-web/src/components/ui/dropdown-menu.tsx b/mcpjam-web/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 000000000..1f49c585f
--- /dev/null
+++ b/mcpjam-web/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,255 @@
+import * as React from "react";
+import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+};
diff --git a/mcpjam-web/src/components/ui/hover-card.tsx b/mcpjam-web/src/components/ui/hover-card.tsx
new file mode 100644
index 000000000..c2b28dbd7
--- /dev/null
+++ b/mcpjam-web/src/components/ui/hover-card.tsx
@@ -0,0 +1,42 @@
+import * as React from "react";
+import { HoverCard as HoverCardPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function HoverCard({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function HoverCardTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function HoverCardContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { HoverCard, HoverCardTrigger, HoverCardContent };
diff --git a/mcpjam-web/src/components/ui/input.tsx b/mcpjam-web/src/components/ui/input.tsx
new file mode 100644
index 000000000..b1a060f50
--- /dev/null
+++ b/mcpjam-web/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ );
+}
+
+export { Input };
diff --git a/mcpjam-web/src/components/ui/json-editor/__tests__/json-editor.test.tsx b/mcpjam-web/src/components/ui/json-editor/__tests__/json-editor.test.tsx
new file mode 100644
index 000000000..e88c62c6d
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/__tests__/json-editor.test.tsx
@@ -0,0 +1,126 @@
+import { describe, it, expect, vi } from "vitest";
+import { render, screen, waitFor } from "@testing-library/react";
+import { JsonEditor } from "../json-editor";
+
+describe("JsonEditor", () => {
+ describe("autoFormatOnEdit", () => {
+ it("formats valid raw content when entering edit mode", async () => {
+ const onRawChange = vi.fn();
+
+ const { rerender } = render(
+ ,
+ );
+
+ expect(onRawChange).not.toHaveBeenCalled();
+
+ rerender(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(onRawChange).toHaveBeenCalledWith('{\n "a": 1\n}');
+ });
+ });
+
+ it("does not format invalid raw content when entering edit mode", async () => {
+ const onRawChange = vi.fn();
+
+ const { rerender } = render(
+ ,
+ );
+
+ rerender(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(onRawChange).not.toHaveBeenCalled();
+ });
+ });
+
+ it("can disable auto formatting on edit", async () => {
+ const onRawChange = vi.fn();
+
+ const { rerender } = render(
+ ,
+ );
+
+ rerender(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(onRawChange).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe("wrapLongLinesInEdit", () => {
+ it("enables soft wrapping in edit mode when configured", () => {
+ render(
+ ,
+ );
+
+ const textarea = screen.getByRole("textbox");
+ expect(textarea.getAttribute("wrap")).toBe("soft");
+ });
+
+ it("keeps wrapping disabled by default", () => {
+ render(
+ ,
+ );
+
+ const textarea = screen.getByRole("textbox");
+ expect(textarea.getAttribute("wrap")).toBe("off");
+ });
+ });
+});
diff --git a/mcpjam-web/src/components/ui/json-editor/__tests__/json-syntax-highlighter.test.ts b/mcpjam-web/src/components/ui/json-editor/__tests__/json-syntax-highlighter.test.ts
new file mode 100644
index 000000000..e7fd20de3
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/__tests__/json-syntax-highlighter.test.ts
@@ -0,0 +1,299 @@
+import { describe, it, expect } from "vitest";
+import {
+ tokenizeJson,
+ formatPath,
+ highlightJson,
+} from "../json-syntax-highlighter";
+
+describe("tokenizeJson", () => {
+ describe("basic primitives", () => {
+ it("tokenizes a string value", () => {
+ const tokens = tokenizeJson('"hello"');
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "string",
+ value: '"hello"',
+ start: 0,
+ end: 7,
+ });
+ });
+
+ it("tokenizes a number value", () => {
+ const tokens = tokenizeJson("42");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "number",
+ value: "42",
+ start: 0,
+ end: 2,
+ });
+ });
+
+ it("tokenizes negative numbers", () => {
+ const tokens = tokenizeJson("-123.45");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "number",
+ value: "-123.45",
+ });
+ });
+
+ it("tokenizes scientific notation", () => {
+ const tokens = tokenizeJson("1.5e10");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "number",
+ value: "1.5e10",
+ });
+ });
+
+ it("tokenizes boolean true", () => {
+ const tokens = tokenizeJson("true");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "boolean",
+ value: "true",
+ });
+ });
+
+ it("tokenizes boolean false", () => {
+ const tokens = tokenizeJson("false");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "boolean-false",
+ value: "false",
+ });
+ });
+
+ it("tokenizes null", () => {
+ const tokens = tokenizeJson("null");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0]).toMatchObject({
+ type: "null",
+ value: "null",
+ });
+ });
+ });
+
+ describe("objects", () => {
+ it("tokenizes an empty object", () => {
+ const tokens = tokenizeJson("{}");
+ expect(tokens).toHaveLength(2);
+ expect(tokens[0]).toMatchObject({ type: "punctuation", value: "{" });
+ expect(tokens[1]).toMatchObject({ type: "punctuation", value: "}" });
+ });
+
+ it("tokenizes a simple object with key-value", () => {
+ const tokens = tokenizeJson('{"name": "value"}');
+ expect(tokens).toHaveLength(5);
+ expect(tokens[0]).toMatchObject({ type: "punctuation", value: "{" });
+ expect(tokens[1]).toMatchObject({ type: "key", value: '"name"' });
+ expect(tokens[2]).toMatchObject({ type: "punctuation", value: ":" });
+ expect(tokens[3]).toMatchObject({ type: "string", value: '"value"' });
+ expect(tokens[4]).toMatchObject({ type: "punctuation", value: "}" });
+ });
+
+ it("tokenizes object with multiple keys", () => {
+ const tokens = tokenizeJson('{"a": 1, "b": 2}');
+ const keys = tokens.filter((t) => t.type === "key");
+ const numbers = tokens.filter((t) => t.type === "number");
+ expect(keys).toHaveLength(2);
+ expect(numbers).toHaveLength(2);
+ });
+ });
+
+ describe("arrays", () => {
+ it("tokenizes an empty array", () => {
+ const tokens = tokenizeJson("[]");
+ expect(tokens).toHaveLength(2);
+ expect(tokens[0]).toMatchObject({ type: "punctuation", value: "[" });
+ expect(tokens[1]).toMatchObject({ type: "punctuation", value: "]" });
+ });
+
+ it("tokenizes array with primitives", () => {
+ const tokens = tokenizeJson("[1, 2, 3]");
+ const numbers = tokens.filter((t) => t.type === "number");
+ expect(numbers).toHaveLength(3);
+ expect(numbers.map((t) => t.value)).toEqual(["1", "2", "3"]);
+ });
+
+ it("tokenizes array with mixed types", () => {
+ const tokens = tokenizeJson('[1, "two", true, null]');
+ expect(tokens.filter((t) => t.type === "number")).toHaveLength(1);
+ expect(tokens.filter((t) => t.type === "string")).toHaveLength(1);
+ expect(tokens.filter((t) => t.type === "boolean")).toHaveLength(1);
+ expect(tokens.filter((t) => t.type === "null")).toHaveLength(1);
+ });
+ });
+
+ describe("nested structures", () => {
+ it("tokenizes nested objects", () => {
+ const json = '{"outer": {"inner": "value"}}';
+ const tokens = tokenizeJson(json);
+ const keys = tokens.filter((t) => t.type === "key");
+ expect(keys).toHaveLength(2);
+ expect(keys.map((t) => t.value)).toEqual(['"outer"', '"inner"']);
+ });
+
+ it("tokenizes nested arrays", () => {
+ const json = "[[1, 2], [3, 4]]";
+ const tokens = tokenizeJson(json);
+ const brackets = tokens.filter((t) => t.value === "[" || t.value === "]");
+ expect(brackets).toHaveLength(6);
+ });
+
+ it("tokenizes objects in arrays", () => {
+ const json = '[{"a": 1}, {"b": 2}]';
+ const tokens = tokenizeJson(json);
+ const keys = tokens.filter((t) => t.type === "key");
+ expect(keys).toHaveLength(2);
+ });
+
+ it("tokenizes arrays in objects", () => {
+ const json = '{"items": [1, 2, 3]}';
+ const tokens = tokenizeJson(json);
+ const numbers = tokens.filter((t) => t.type === "number");
+ expect(numbers).toHaveLength(3);
+ });
+ });
+
+ describe("escape sequences in strings", () => {
+ it("handles escaped quotes", () => {
+ const tokens = tokenizeJson('"hello \\"world\\""');
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].value).toBe('"hello \\"world\\""');
+ });
+
+ it("handles escaped backslashes", () => {
+ const tokens = tokenizeJson('"path\\\\to\\\\file"');
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].value).toBe('"path\\\\to\\\\file"');
+ });
+
+ it("handles escaped newlines and tabs", () => {
+ const tokens = tokenizeJson('"line1\\nline2\\ttab"');
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].type).toBe("string");
+ });
+
+ it("handles unicode escapes", () => {
+ const tokens = tokenizeJson('"unicode: \\u0041"');
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].type).toBe("string");
+ });
+ });
+
+ describe("path tracking", () => {
+ it("tracks path for simple object values", () => {
+ const tokens = tokenizeJson('{"name": "John"}');
+ const stringValue = tokens.find(
+ (t) => t.type === "string" && t.value === '"John"',
+ );
+ expect(stringValue?.path).toEqual(["name"]);
+ expect(stringValue?.keyName).toBe("name");
+ });
+
+ it("tracks path for nested values", () => {
+ const tokens = tokenizeJson('{"user": {"name": "John"}}');
+ const stringValue = tokens.find(
+ (t) => t.type === "string" && t.value === '"John"',
+ );
+ expect(stringValue?.path).toEqual(["user", "name"]);
+ });
+
+ it("tracks path for array elements", () => {
+ const tokens = tokenizeJson('{"items": [1, 2]}');
+ const numbers = tokens.filter((t) => t.type === "number");
+ expect(numbers[0].path).toEqual(["items", 0]);
+ expect(numbers[1].path).toEqual(["items", 1]);
+ });
+
+ it("tracks path for deeply nested array in object", () => {
+ const tokens = tokenizeJson('{"data": {"list": [{"id": 1}]}}');
+ const numberToken = tokens.find((t) => t.type === "number");
+ expect(numberToken?.path).toEqual(["data", "list", 0, "id"]);
+ });
+ });
+
+ describe("edge cases", () => {
+ it("handles empty string values", () => {
+ const tokens = tokenizeJson('{"empty": ""}');
+ const stringValue = tokens.find(
+ (t) => t.type === "string" && t.value === '""',
+ );
+ expect(stringValue).toBeDefined();
+ });
+
+ it("handles zero", () => {
+ const tokens = tokenizeJson("0");
+ expect(tokens[0]).toMatchObject({ type: "number", value: "0" });
+ });
+
+ it("handles negative zero", () => {
+ const tokens = tokenizeJson("-0");
+ expect(tokens[0]).toMatchObject({ type: "number", value: "-0" });
+ });
+
+ it("handles whitespace between tokens", () => {
+ const tokens = tokenizeJson('{ "a" : 1 }');
+ expect(tokens.filter((t) => t.type === "key")).toHaveLength(1);
+ expect(tokens.filter((t) => t.type === "number")).toHaveLength(1);
+ });
+
+ it("handles newlines in formatted JSON", () => {
+ const json = `{
+ "name": "test"
+}`;
+ const tokens = tokenizeJson(json);
+ expect(tokens.filter((t) => t.type === "key")).toHaveLength(1);
+ expect(tokens.filter((t) => t.type === "string")).toHaveLength(1);
+ });
+ });
+});
+
+describe("formatPath", () => {
+ it("formats simple path", () => {
+ expect(formatPath(["user"])).toBe("user");
+ });
+
+ it("formats nested path with dots", () => {
+ expect(formatPath(["user", "profile", "name"])).toBe("user.profile.name");
+ });
+
+ it("formats path with array indices", () => {
+ expect(formatPath(["items", 0])).toBe("items[0]");
+ });
+
+ it("formats complex path with mixed keys and indices", () => {
+ expect(formatPath(["users", 0, "addresses", 1, "city"])).toBe(
+ "users[0].addresses[1].city",
+ );
+ });
+
+ it("handles empty path", () => {
+ expect(formatPath([])).toBe("");
+ });
+
+ it("handles path starting with array index", () => {
+ expect(formatPath([0, "name"])).toBe("[0].name");
+ });
+});
+
+describe("highlightJson", () => {
+ it("returns HTML with span elements", () => {
+ const html = highlightJson('{"key": "value"}');
+ expect(html).toContain('{ ');
+ expect(html).toContain('"key" ');
+ expect(html).toContain('"value" ');
+ });
+
+ it("escapes HTML entities", () => {
+ const html = highlightJson('{"html": "&
"}');
+ expect(html).toContain("<div>&</div>");
+ });
+
+ it("preserves whitespace between tokens", () => {
+ const html = highlightJson('{ "a": 1 }');
+ expect(html).toContain(" ");
+ });
+});
diff --git a/mcpjam-web/src/components/ui/json-editor/__tests__/use-json-editor.test.ts b/mcpjam-web/src/components/ui/json-editor/__tests__/use-json-editor.test.ts
new file mode 100644
index 000000000..8b136a402
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/__tests__/use-json-editor.test.ts
@@ -0,0 +1,790 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { renderHook, act } from "@testing-library/react";
+import { useJsonEditor } from "../use-json-editor";
+
+describe("useJsonEditor", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe("initial value handling", () => {
+ it("initializes with provided object value", () => {
+ const initialValue = { name: "John", age: 30 };
+ const { result } = renderHook(() => useJsonEditor({ initialValue }));
+
+ expect(result.current.content).toBe(
+ JSON.stringify(initialValue, null, 2),
+ );
+ expect(result.current.isValid).toBe(true);
+ });
+
+ it("initializes with provided string content", () => {
+ const initialContent = '{"key": "value"}';
+ const { result } = renderHook(() => useJsonEditor({ initialContent }));
+
+ expect(result.current.content).toBe(initialContent);
+ expect(result.current.isValid).toBe(true);
+ });
+
+ it("prefers initialContent over initialValue when both provided", () => {
+ const initialValue = { ignored: true };
+ const initialContent = '{"used": true}';
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, initialContent }),
+ );
+
+ expect(result.current.content).toBe(initialContent);
+ });
+
+ it("handles null initial value", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: null }),
+ );
+
+ expect(result.current.content).toBe("null");
+ expect(result.current.isValid).toBe(true);
+ });
+
+ it("handles array initial value", () => {
+ const initialValue = [1, 2, 3];
+ const { result } = renderHook(() => useJsonEditor({ initialValue }));
+
+ expect(result.current.content).toBe(
+ JSON.stringify(initialValue, null, 2),
+ );
+ expect(result.current.isValid).toBe(true);
+ });
+
+ it("handles primitive initial values", () => {
+ const { result: stringResult } = renderHook(() =>
+ useJsonEditor({ initialValue: "hello" }),
+ );
+ expect(stringResult.current.content).toBe('"hello"');
+
+ const { result: numberResult } = renderHook(() =>
+ useJsonEditor({ initialValue: 42 }),
+ );
+ expect(numberResult.current.content).toBe("42");
+
+ const { result: boolResult } = renderHook(() =>
+ useJsonEditor({ initialValue: true }),
+ );
+ expect(boolResult.current.content).toBe("true");
+ });
+
+ it("falls back to null for undefined initial value", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: undefined }),
+ );
+
+ expect(result.current.content).toBe("null");
+ expect(result.current.isValid).toBe(true);
+ });
+ });
+
+ describe("onChange and onRawChange callbacks", () => {
+ it("calls onChange callback with parsed value for valid JSON", () => {
+ const onChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onChange }),
+ );
+
+ act(() => {
+ result.current.setContent('{"updated": true}');
+ });
+
+ expect(onChange).toHaveBeenCalledWith({ updated: true });
+ });
+
+ it("does not call onChange for invalid JSON", () => {
+ const onChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onChange }),
+ );
+
+ act(() => {
+ result.current.setContent("{invalid json");
+ });
+
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it("calls onRawChange for all content changes", () => {
+ const onRawChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onRawChange }),
+ );
+
+ act(() => {
+ result.current.setContent("{invalid");
+ });
+
+ expect(onRawChange).toHaveBeenCalledWith("{invalid");
+ });
+
+ it("calls onRawChange for valid content", () => {
+ const onRawChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onRawChange }),
+ );
+
+ act(() => {
+ result.current.setContent('{"valid": true}');
+ });
+
+ expect(onRawChange).toHaveBeenCalledWith('{"valid": true}');
+ });
+
+ it("does not call onChange for whitespace-only valid edits", () => {
+ const onChange = vi.fn();
+ const onRawChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({
+ initialContent: '{"unchanged": true}',
+ onChange,
+ onRawChange,
+ }),
+ );
+
+ act(() => {
+ result.current.setContent('{\n "unchanged": true\n}\n');
+ });
+
+ expect(onRawChange).toHaveBeenCalledWith('{\n "unchanged": true\n}\n');
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it("still calls onChange when parsed JSON changes after whitespace edits", () => {
+ const onChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({
+ initialContent: '{"count": 1}',
+ onChange,
+ }),
+ );
+
+ act(() => {
+ result.current.setContent('{\n "count": 1\n}\n');
+ });
+
+ expect(onChange).not.toHaveBeenCalled();
+
+ act(() => {
+ result.current.setContent('{\n "count": 2\n}\n');
+ });
+
+ expect(onChange).toHaveBeenCalledWith({ count: 2 });
+ });
+ });
+
+ describe("validation", () => {
+ it("validates initial content immediately", () => {
+ const { result: validResult } = renderHook(() =>
+ useJsonEditor({ initialContent: '{"valid": true}' }),
+ );
+ expect(validResult.current.isValid).toBe(true);
+ expect(validResult.current.validationError).toBeNull();
+
+ const { result: invalidResult } = renderHook(() =>
+ useJsonEditor({ initialContent: "{invalid" }),
+ );
+ expect(invalidResult.current.isValid).toBe(false);
+ expect(invalidResult.current.validationError).toEqual(expect.any(String));
+ });
+
+ it("calls onValidationError callback with error", () => {
+ const onValidationError = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onValidationError }),
+ );
+
+ act(() => {
+ result.current.setContent("{bad}");
+ });
+
+ expect(onValidationError).toHaveBeenCalled();
+ const errorArg = onValidationError.mock.calls[0][0];
+ expect(typeof errorArg).toBe("string");
+ expect(errorArg.length).toBeGreaterThan(0);
+ });
+
+ it("calls onValidationError with null when content is valid", () => {
+ const onValidationError = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onValidationError }),
+ );
+
+ act(() => {
+ result.current.setContent('{"valid": true}');
+ });
+
+ expect(onValidationError).toHaveBeenCalledWith(null);
+ });
+ });
+
+ describe("format function", () => {
+ it("formats compact JSON to pretty-printed", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: '{"a":1,"b":2}' }),
+ );
+
+ act(() => {
+ result.current.format();
+ });
+
+ expect(result.current.content).toBe(
+ JSON.stringify({ a: 1, b: 2 }, null, 2),
+ );
+ });
+
+ it("does not change invalid JSON on format", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: "{invalid" }),
+ );
+
+ const originalContent = result.current.content;
+
+ act(() => {
+ result.current.format();
+ });
+
+ expect(result.current.content).toBe(originalContent);
+ });
+ });
+
+ describe("reset function", () => {
+ it("resets content to initial value", () => {
+ const initialValue = { original: true };
+ const { result } = renderHook(() => useJsonEditor({ initialValue }));
+
+ const expectedContent = JSON.stringify(initialValue, null, 2);
+
+ act(() => {
+ result.current.setContent('{"changed": true}');
+ });
+
+ act(() => {
+ result.current.reset();
+ });
+
+ expect(result.current.content).toBe(expectedContent);
+ });
+
+ it("resets content to initial raw content when provided", () => {
+ const initialContent = '{"raw": "original"}';
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent, initialValue: { ignored: true } }),
+ );
+
+ act(() => {
+ result.current.setContent('{"raw": "changed"}');
+ });
+
+ act(() => {
+ result.current.reset();
+ });
+
+ expect(result.current.content).toBe(initialContent);
+ });
+
+ it("clears validation error on reset", () => {
+ const onValidationError = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onValidationError }),
+ );
+
+ act(() => {
+ result.current.setContent("{invalid");
+ });
+
+ // Verify validation error was set (look for non-null error in calls)
+ const errorCalls = onValidationError.mock.calls.filter(
+ (call) => call[0] !== null,
+ );
+ expect(errorCalls.length).toBeGreaterThan(0);
+
+ act(() => {
+ result.current.reset();
+ });
+
+ expect(result.current.isValid).toBe(true);
+ expect(result.current.validationError).toBeNull();
+ });
+
+ it("calls onValidationError with null on reset", () => {
+ const onValidationError = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: {}, onValidationError }),
+ );
+
+ act(() => {
+ result.current.setContent("{invalid");
+ });
+
+ onValidationError.mockClear();
+
+ act(() => {
+ result.current.reset();
+ });
+
+ expect(onValidationError).toHaveBeenCalledWith(null);
+ });
+
+ it("notifies change callbacks on reset", () => {
+ const onChange = vi.fn();
+ const onRawChange = vi.fn();
+ const initialValue = { original: true };
+ const expectedContent = JSON.stringify(initialValue, null, 2);
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, onChange, onRawChange }),
+ );
+
+ act(() => {
+ result.current.setContent('{"changed": true}');
+ });
+
+ onChange.mockClear();
+ onRawChange.mockClear();
+
+ act(() => {
+ result.current.reset();
+ });
+
+ expect(onRawChange).toHaveBeenCalledWith(expectedContent);
+ expect(onChange).toHaveBeenCalledWith(initialValue);
+ });
+ });
+
+ describe("getParsedValue", () => {
+ it("returns parsed value for valid JSON", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue: { test: true } }),
+ );
+
+ expect(result.current.getParsedValue()).toEqual({ test: true });
+ });
+
+ it("returns undefined for invalid JSON", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: "{invalid" }),
+ );
+
+ expect(result.current.getParsedValue()).toBeUndefined();
+ });
+
+ it("returns null for null content", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: "null" }),
+ );
+
+ expect(result.current.getParsedValue()).toBeNull();
+ });
+
+ it("returns array for array content", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: "[1, 2, 3]" }),
+ );
+
+ expect(result.current.getParsedValue()).toEqual([1, 2, 3]);
+ });
+ });
+
+ describe("cursor position", () => {
+ it("initializes cursor at line 1, column 1", () => {
+ const { result } = renderHook(() => useJsonEditor({ initialValue: {} }));
+
+ expect(result.current.cursorPosition).toEqual({ line: 1, column: 1 });
+ });
+
+ it("updates cursor position when setCursorPosition is called", () => {
+ const { result } = renderHook(() => useJsonEditor({ initialValue: {} }));
+
+ act(() => {
+ result.current.setCursorPosition({ line: 5, column: 10 });
+ });
+
+ expect(result.current.cursorPosition).toEqual({ line: 5, column: 10 });
+ });
+ });
+
+ describe("undo/redo behavior", () => {
+ it("starts with canUndo false", () => {
+ const { result } = renderHook(() => useJsonEditor({ initialValue: {} }));
+
+ expect(result.current.canUndo).toBe(false);
+ });
+
+ it("starts with canRedo false", () => {
+ const { result } = renderHook(() => useJsonEditor({ initialValue: {} }));
+
+ expect(result.current.canRedo).toBe(false);
+ });
+
+ it("undo function exists and is callable", () => {
+ const { result } = renderHook(() => useJsonEditor({ initialValue: {} }));
+
+ expect(typeof result.current.undo).toBe("function");
+
+ // Should not throw when called with no history
+ act(() => {
+ result.current.undo();
+ });
+ });
+
+ it("redo function exists and is callable", () => {
+ const { result } = renderHook(() => useJsonEditor({ initialValue: {} }));
+
+ expect(typeof result.current.redo).toBe("function");
+
+ // Should not throw when called with no redo history
+ act(() => {
+ result.current.redo();
+ });
+ });
+
+ it("undo restores previous content after change", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: '{"v": 1}' }),
+ );
+
+ act(() => {
+ result.current.setContent('{"v": 2}');
+ });
+
+ expect(result.current.content).toBe('{"v": 2}');
+
+ act(() => {
+ result.current.undo();
+ });
+
+ expect(result.current.content).toBe('{"v": 1}');
+ });
+
+ it("redo restores content after undo", () => {
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: '{"v": 1}' }),
+ );
+
+ act(() => {
+ result.current.setContent('{"v": 2}');
+ });
+
+ act(() => {
+ result.current.undo();
+ });
+
+ expect(result.current.content).toBe('{"v": 1}');
+
+ act(() => {
+ result.current.redo();
+ });
+
+ expect(result.current.content).toBe('{"v": 2}');
+ });
+
+ it("undo validates restored content", () => {
+ const onValidationError = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: '{"valid": true}', onValidationError }),
+ );
+
+ // Initial content is valid
+ expect(result.current.isValid).toBe(true);
+
+ act(() => {
+ result.current.setContent("{invalid");
+ });
+
+ // Now invalid
+ expect(onValidationError).toHaveBeenCalled();
+ const lastCall = onValidationError.mock.calls.pop();
+ expect(lastCall[0]).not.toBeNull();
+
+ act(() => {
+ result.current.undo();
+ });
+
+ // After undo, we're back to valid content
+ expect(result.current.isValid).toBe(true);
+ });
+
+ it("undo and redo notify parent callbacks with restored state", () => {
+ const onChange = vi.fn();
+ const onRawChange = vi.fn();
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialContent: '{"v": 1}', onChange, onRawChange }),
+ );
+
+ act(() => {
+ result.current.setContent('{"v": 2}');
+ });
+
+ onChange.mockClear();
+ onRawChange.mockClear();
+
+ act(() => {
+ result.current.undo();
+ });
+
+ expect(onRawChange).toHaveBeenCalledWith('{"v": 1}');
+ expect(onChange).toHaveBeenCalledWith({ v: 1 });
+
+ onChange.mockClear();
+ onRawChange.mockClear();
+
+ act(() => {
+ result.current.redo();
+ });
+
+ expect(onRawChange).toHaveBeenCalledWith('{"v": 2}');
+ expect(onChange).toHaveBeenCalledWith({ v: 2 });
+ });
+ });
+
+ describe("expandJsonStrings", () => {
+ it("expands JSON string fields into objects on init", () => {
+ const initialValue = {
+ toolInput: { elements: '[{"type":"ellipse","x":10}]' },
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, expandJsonStrings: true }),
+ );
+
+ const parsed = JSON.parse(result.current.content);
+ expect(parsed.toolInput.elements).toEqual([{ type: "ellipse", x: 10 }]);
+ });
+
+ it("collapses expanded fields back to strings on onChange", () => {
+ const onChange = vi.fn();
+ const initialValue = {
+ toolInput: { elements: '[{"type":"ellipse"}]' },
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, onChange, expandJsonStrings: true }),
+ );
+
+ // Modify an unrelated field to trigger onChange
+ const parsed = JSON.parse(result.current.content);
+ parsed.toolInput.elements[0].type = "rectangle";
+ act(() => {
+ result.current.setContent(JSON.stringify(parsed, null, 2));
+ });
+
+ expect(onChange).toHaveBeenCalled();
+ const emitted = onChange.mock.calls[0][0];
+ // elements should be collapsed back to a string
+ expect(typeof emitted.toolInput.elements).toBe("string");
+ expect(JSON.parse(emitted.toolInput.elements)).toEqual([
+ { type: "rectangle" },
+ ]);
+ });
+
+ it("does not fire onChange when content is unchanged (dedup)", () => {
+ const onChange = vi.fn();
+ const initialValue = {
+ toolInput: { elements: '[{"type":"ellipse"}]' },
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, onChange, expandJsonStrings: true }),
+ );
+
+ // Re-set same expanded content
+ act(() => {
+ result.current.setContent(result.current.content);
+ });
+
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it("format preserves expansion", () => {
+ const initialValue = {
+ data: '{"nested":"value"}',
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, expandJsonStrings: true }),
+ );
+
+ // Compact the content manually
+ const parsed = JSON.parse(result.current.content);
+ act(() => {
+ result.current.setContent(JSON.stringify(parsed));
+ });
+
+ // Format should re-expand
+ act(() => {
+ result.current.format();
+ });
+
+ const formatted = JSON.parse(result.current.content);
+ expect(formatted.data).toEqual({ nested: "value" });
+ });
+
+ it("reset re-expands from original value", () => {
+ const initialValue = {
+ items: "[1,2,3]",
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, expandJsonStrings: true }),
+ );
+
+ // Edit content
+ act(() => {
+ result.current.setContent('{"items": [99]}');
+ });
+
+ // Reset
+ act(() => {
+ result.current.reset();
+ });
+
+ const parsed = JSON.parse(result.current.content);
+ expect(parsed.items).toEqual([1, 2, 3]);
+ });
+
+ it("re-expands when value changes externally", () => {
+ const { result, rerender } = renderHook(
+ ({ value }) =>
+ useJsonEditor({ initialValue: value, expandJsonStrings: true }),
+ {
+ initialProps: {
+ value: { data: '{"v":1}' } as unknown,
+ },
+ },
+ );
+
+ let parsed = JSON.parse(result.current.content);
+ expect(parsed.data).toEqual({ v: 1 });
+
+ rerender({ value: { data: '{"v":2}' } });
+
+ parsed = JSON.parse(result.current.content);
+ expect(parsed.data).toEqual({ v: 2 });
+ });
+
+ it("keeps primitive JSON strings as strings", () => {
+ const initialValue = {
+ count: "42",
+ flag: "true",
+ name: "hello",
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, expandJsonStrings: true }),
+ );
+
+ const parsed = JSON.parse(result.current.content);
+ // Primitives should NOT be expanded
+ expect(parsed.count).toBe("42");
+ expect(parsed.flag).toBe("true");
+ expect(parsed.name).toBe("hello");
+ });
+
+ it("does not double-stringify when user changes type", () => {
+ const onChange = vi.fn();
+ const initialValue = {
+ data: '{"key":"val"}',
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, onChange, expandJsonStrings: true }),
+ );
+
+ // User replaces the expanded object with a string
+ act(() => {
+ result.current.setContent('{\n "data": "plain string"\n}');
+ });
+
+ expect(onChange).toHaveBeenCalled();
+ const emitted = onChange.mock.calls[0][0];
+ // Since original was a string and current is also a string, no collapse needed
+ expect(emitted.data).toBe("plain string");
+ });
+
+ it("sourceContent matches content on init", () => {
+ const initialValue = {
+ toolInput: { elements: '[{"type":"ellipse"}]' },
+ };
+ const { result } = renderHook(() =>
+ useJsonEditor({ initialValue, expandJsonStrings: true }),
+ );
+
+ expect(result.current.sourceContent).toBe(result.current.content);
+ });
+
+ it("sourceContent updates on round-trip (parent echoes onChange)", () => {
+ const onChange = vi.fn();
+ const initialValue = {
+ toolInput: { elements: '[{"type":"ellipse"}]' },
+ };
+ const { result, rerender } = renderHook(
+ ({ value }) =>
+ useJsonEditor({
+ initialValue: value,
+ onChange,
+ expandJsonStrings: true,
+ }),
+ { initialProps: { value: initialValue as unknown } },
+ );
+
+ // Edit content
+ const parsed = JSON.parse(result.current.content);
+ parsed.toolInput.elements[0].type = "rectangle";
+ act(() => {
+ result.current.setContent(JSON.stringify(parsed, null, 2));
+ });
+
+ // Simulate parent echoing back the collapsed onChange value
+ const collapsedValue = onChange.mock.calls[0][0];
+ rerender({ value: collapsedValue });
+
+ // sourceContent should track current editor content after round-trip
+ expect(result.current.sourceContent).toBe(result.current.content);
+ });
+
+ it("sourceContent updates on genuine external change", () => {
+ const { result, rerender } = renderHook(
+ ({ value }) =>
+ useJsonEditor({ initialValue: value, expandJsonStrings: true }),
+ {
+ initialProps: {
+ value: { data: '{"v":1}' } as unknown,
+ },
+ },
+ );
+
+ const initialSourceContent = result.current.sourceContent;
+
+ // External change (not a round-trip)
+ rerender({ value: { data: '{"v":2}' } });
+
+ expect(result.current.sourceContent).not.toBe(initialSourceContent);
+ expect(result.current.sourceContent).toBe(result.current.content);
+ });
+ });
+
+ describe("external value sync", () => {
+ it("syncs content when initialValue prop changes", () => {
+ const { result, rerender } = renderHook(
+ ({ value }) => useJsonEditor({ initialValue: value }),
+ { initialProps: { value: { version: 1 } } },
+ );
+
+ expect(result.current.content).toBe('{\n "version": 1\n}');
+
+ rerender({ value: { version: 2 } });
+
+ expect(result.current.content).toBe('{\n "version": 2\n}');
+ });
+
+ it("syncs content when initialContent prop changes", () => {
+ const { result, rerender } = renderHook(
+ ({ content }) => useJsonEditor({ initialContent: content }),
+ { initialProps: { content: '{"v": 1}' } },
+ );
+
+ expect(result.current.content).toBe('{"v": 1}');
+
+ rerender({ content: '{"v": 2}' });
+
+ expect(result.current.content).toBe('{"v": 2}');
+ });
+ });
+});
diff --git a/mcpjam-web/src/components/ui/json-editor/index.ts b/mcpjam-web/src/components/ui/json-editor/index.ts
new file mode 100644
index 000000000..b3c2a1275
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/index.ts
@@ -0,0 +1,16 @@
+export { JsonEditor } from "./json-editor";
+export { JsonEditorEdit } from "./json-editor-edit";
+export { JsonEditorToolbar } from "./json-editor-toolbar";
+export { JsonEditorStatusBar } from "./json-editor-status-bar";
+export { JsonTreeView } from "./json-tree-view";
+export { JsonTreeNode } from "./json-tree-node";
+export { TruncatableString } from "./truncatable-string";
+export { useJsonEditor } from "./use-json-editor";
+export { useJsonTreeState } from "./use-json-tree-state";
+export type {
+ JsonEditorProps,
+ JsonEditorMode,
+ CursorPosition,
+ UseJsonEditorOptions,
+ UseJsonEditorReturn,
+} from "./types";
diff --git a/mcpjam-web/src/components/ui/json-editor/json-editor-edit.tsx b/mcpjam-web/src/components/ui/json-editor/json-editor-edit.tsx
new file mode 100644
index 000000000..05a0c7bb0
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-editor-edit.tsx
@@ -0,0 +1,637 @@
+import { useRef, useEffect, useCallback, useState, useMemo } from "react";
+import { useVirtualizer, type VirtualItem } from "@tanstack/react-virtual";
+import { cn } from "@/lib/utils";
+import type { CursorPosition } from "./types";
+import { highlightJson } from "./json-syntax-highlighter";
+import { JsonHighlighter } from "./json-highlighter";
+
+// Constants for virtualization and viewport highlighting
+const LINE_HEIGHT = 20; // 20px per line (leading-5)
+const VIEWPORT_BUFFER_LINES = 30; // Buffer lines above/below viewport for highlighting
+const EDITOR_VERTICAL_PADDING = 12; // p-3 top/bottom padding
+const DEFAULT_CHARS_PER_VISUAL_LINE = 80;
+const TAB_SIZE = 2;
+
+interface JsonEditorEditProps {
+ content: string;
+ onChange?: (content: string) => void;
+ onCursorChange?: (position: CursorPosition) => void;
+ onUndo?: () => void;
+ onRedo?: () => void;
+ onEscape?: () => void;
+ isValid?: boolean;
+ readOnly?: boolean;
+ className?: string;
+ height?: string | number;
+ maxHeight?: string | number;
+ showLineNumbers?: boolean;
+ collapseStringsAfterLength?: number;
+ wrapLongLinesInEdit?: boolean;
+}
+
+interface LineLayout {
+ top: number;
+ height: number;
+}
+
+function getCursorPosition(textarea: HTMLTextAreaElement): CursorPosition {
+ const text = textarea.value;
+ const selectionStart = textarea.selectionStart;
+ const textBeforeCursor = text.substring(0, selectionStart);
+ const lines = textBeforeCursor.split("\n");
+ const line = lines.length;
+ const column = lines[lines.length - 1].length + 1;
+ return { line, column };
+}
+
+function getCharsPerVisualLine(textarea: HTMLTextAreaElement): number {
+ const styles = window.getComputedStyle(textarea);
+ const probe = document.createElement("span");
+ probe.textContent = "0";
+ probe.style.font = styles.font;
+ probe.style.position = "absolute";
+ probe.style.visibility = "hidden";
+ probe.style.whiteSpace = "pre";
+ document.body.appendChild(probe);
+ const charWidth = probe.getBoundingClientRect().width;
+ probe.remove();
+
+ if (!Number.isFinite(charWidth) || charWidth <= 0) {
+ return DEFAULT_CHARS_PER_VISUAL_LINE;
+ }
+
+ const paddingLeft = Number.parseFloat(styles.paddingLeft) || 0;
+ const paddingRight = Number.parseFloat(styles.paddingRight) || 0;
+ const availableWidth = textarea.clientWidth - paddingLeft - paddingRight;
+
+ if (availableWidth <= 0) {
+ return DEFAULT_CHARS_PER_VISUAL_LINE;
+ }
+
+ return Math.max(1, Math.floor(availableWidth / charWidth));
+}
+
+function countVisualRows(line: string, charsPerVisualLine: number): number {
+ const expandedLine = line.replace(/\t/g, " ".repeat(TAB_SIZE));
+ const displayLength = expandedLine.length;
+
+ if (displayLength === 0) {
+ return 1;
+ }
+
+ return Math.max(1, Math.ceil(displayLength / charsPerVisualLine));
+}
+
+function buildLineLayouts(
+ lines: string[],
+ lineWrapEnabled: boolean,
+ charsPerVisualLine: number,
+): LineLayout[] {
+ let currentTop = 0;
+
+ return lines.map((line, index) => {
+ const height = lineWrapEnabled
+ ? countVisualRows(line, charsPerVisualLine) * LINE_HEIGHT
+ : LINE_HEIGHT;
+ const top = lineWrapEnabled ? currentTop : index * LINE_HEIGHT;
+ currentTop += height;
+ return { top, height };
+ });
+}
+
+/**
+ * Compute viewport-based highlighting.
+ * Only highlights the visible portion + buffer, using lineLayouts for correct
+ * positioning when line wrapping is enabled.
+ */
+function useViewportHighlight(
+ content: string,
+ scrollTop: number,
+ viewportHeight: number,
+ enabled: boolean,
+ lineLayouts: LineLayout[],
+): { highlightedHtml: string; paddingTop: number; paddingBottom: number } {
+ return useMemo(() => {
+ if (!enabled) {
+ return { highlightedHtml: "", paddingTop: 0, paddingBottom: 0 };
+ }
+
+ const lines = content.split("\n");
+ const totalLines = lines.length;
+
+ if (totalLines === 0) {
+ return { highlightedHtml: "", paddingTop: 0, paddingBottom: 0 };
+ }
+
+ // Find first visible line using lineLayouts (binary search)
+ let lo = 0;
+ let hi = totalLines - 1;
+ while (lo <= hi) {
+ const mid = (lo + hi) >>> 1;
+ const layout = lineLayouts[mid];
+ if (!layout || layout.top + layout.height <= scrollTop) {
+ lo = mid + 1;
+ } else {
+ hi = mid - 1;
+ }
+ }
+ const firstVisibleLine = Math.min(lo, totalLines - 1);
+
+ // Find last visible line
+ const viewBottom = scrollTop + viewportHeight;
+ let lastVisibleLine = firstVisibleLine;
+ for (let i = firstVisibleLine; i < totalLines; i++) {
+ const layout = lineLayouts[i];
+ if (!layout || layout.top >= viewBottom) break;
+ lastVisibleLine = i;
+ }
+
+ // Add buffer
+ const startLine = Math.max(0, firstVisibleLine - VIEWPORT_BUFFER_LINES);
+ const endLine = Math.min(
+ totalLines - 1,
+ lastVisibleLine + VIEWPORT_BUFFER_LINES,
+ );
+
+ const visibleContent = lines.slice(startLine, endLine + 1).join("\n");
+
+ // Compute padding from lineLayouts
+ const startLayout = lineLayouts[startLine];
+ const endLayout = lineLayouts[endLine];
+ const lastLayout = lineLayouts[totalLines - 1];
+ const totalHeight = lastLayout ? lastLayout.top + lastLayout.height : 0;
+
+ const paddingTop = startLayout?.top ?? 0;
+ const paddingBottom = Math.max(
+ 0,
+ totalHeight - (endLayout ? endLayout.top + endLayout.height : 0),
+ );
+
+ return {
+ highlightedHtml: highlightJson(visibleContent),
+ paddingTop,
+ paddingBottom,
+ };
+ }, [content, scrollTop, viewportHeight, enabled, lineLayouts]);
+}
+
+export function JsonEditorEdit({
+ content,
+ onChange,
+ onCursorChange,
+ onUndo,
+ onRedo,
+ onEscape,
+ isValid = true,
+ readOnly = false,
+ className,
+ height,
+ maxHeight,
+ showLineNumbers = true,
+ collapseStringsAfterLength,
+ wrapLongLinesInEdit = false,
+}: JsonEditorEditProps) {
+ const textareaRef = useRef(null);
+ const lineNumbersRef = useRef(null);
+ const highlightRef = useRef(null);
+ const containerRef = useRef(null);
+
+ const [isFocused, setIsFocused] = useState(false);
+ const [activeLine, setActiveLine] = useState(1);
+ const [scrollTop, setScrollTop] = useState(0);
+ const [scrollLeft, setScrollLeft] = useState(0);
+ const [viewportHeight, setViewportHeight] = useState(400);
+ const overlayContentRef = useRef(null);
+ const activeHighlightRef = useRef(null);
+ const scrollRafRef = useRef(0);
+ const [charsPerVisualLine, setCharsPerVisualLine] = useState(
+ DEFAULT_CHARS_PER_VISUAL_LINE,
+ );
+ const lineWrapEnabled = wrapLongLinesInEdit && !readOnly;
+ const lines = useMemo(() => content.split("\n"), [content]);
+ const lineLayouts = useMemo(
+ () => buildLineLayouts(lines, lineWrapEnabled, charsPerVisualLine),
+ [charsPerVisualLine, lineWrapEnabled, lines],
+ );
+ const lineCount = lines.length;
+ const activeLineIndex = Math.min(Math.max(activeLine - 1, 0), lineCount - 1);
+ const activeLineLayout = lineLayouts[activeLineIndex];
+ const activeLineTop = activeLineLayout?.top ?? 0;
+ const activeLineHeight = activeLineLayout?.height ?? LINE_HEIGHT;
+ // Always use viewport-based highlighting in edit mode (works with wrapping via lineLayouts)
+ const useViewportBasedHighlighting = !readOnly;
+ const activeHighlightOffset =
+ activeLineTop - scrollTop + EDITOR_VERTICAL_PADDING;
+
+ const refreshCharsPerVisualLine = useCallback(() => {
+ if (!lineWrapEnabled || !textareaRef.current) {
+ return;
+ }
+
+ const nextCharsPerLine = getCharsPerVisualLine(textareaRef.current);
+ setCharsPerVisualLine((current) =>
+ current === nextCharsPerLine ? current : nextCharsPerLine,
+ );
+ }, [lineWrapEnabled]);
+
+ // Phase 2: Virtualized line numbers
+ const lineNumberVirtualizer = useVirtualizer({
+ count: lineCount,
+ getScrollElement: () => lineNumbersRef.current,
+ estimateSize: (index: number) => lineLayouts[index]?.height ?? LINE_HEIGHT,
+ overscan: 20,
+ });
+
+ // Phase 3: Viewport-based highlighting (uses lineLayouts for correct wrapping support)
+ const {
+ highlightedHtml: viewportHighlightedHtml,
+ paddingTop: viewportPaddingTop,
+ } = useViewportHighlight(
+ content,
+ scrollTop,
+ viewportHeight,
+ useViewportBasedHighlighting,
+ lineLayouts,
+ );
+ const highlightedHtml = useMemo(() => {
+ if (readOnly) {
+ return "";
+ }
+
+ return useViewportBasedHighlighting
+ ? viewportHighlightedHtml
+ : highlightJson(content);
+ }, [
+ content,
+ readOnly,
+ useViewportBasedHighlighting,
+ viewportHighlightedHtml,
+ ]);
+ const paddingTop = useViewportBasedHighlighting ? viewportPaddingTop : 0;
+ const paddingTopRef = useRef(0);
+ paddingTopRef.current = paddingTop;
+ const activeLineTopRef = useRef(0);
+ activeLineTopRef.current = activeLineTop;
+
+ // Sync scroll between textarea, line numbers, and highlight overlay
+ const handleScroll = useCallback(() => {
+ if (textareaRef.current) {
+ const currentScrollTop = textareaRef.current.scrollTop;
+ const currentScrollLeft = textareaRef.current.scrollLeft;
+
+ if (lineNumbersRef.current) {
+ lineNumbersRef.current.scrollTop = currentScrollTop;
+ }
+
+ // Immediate visual sync via transform (prevents flicker)
+ if (overlayContentRef.current) {
+ overlayContentRef.current.style.transform = `translate(${-currentScrollLeft}px, ${paddingTopRef.current - currentScrollTop}px)`;
+ }
+
+ // Keep active line highlight locked to cursor position during scroll
+ if (activeHighlightRef.current) {
+ activeHighlightRef.current.style.transform = `translateY(${activeLineTopRef.current - currentScrollTop + EDITOR_VERTICAL_PADDING}px)`;
+ }
+
+ // Debounce React state updates to once per animation frame
+ cancelAnimationFrame(scrollRafRef.current);
+ scrollRafRef.current = requestAnimationFrame(() => {
+ setScrollTop(currentScrollTop);
+ setScrollLeft(currentScrollLeft);
+ });
+ }
+ }, []);
+
+ // Update cursor position on selection change
+ const handleSelectionChange = useCallback(() => {
+ if (textareaRef.current && onCursorChange) {
+ const position = getCursorPosition(textareaRef.current);
+ onCursorChange(position);
+ setActiveLine(position.line);
+ }
+ }, [onCursorChange]);
+
+ // Handle keyboard shortcuts
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ const textarea = e.currentTarget;
+ const { selectionStart, selectionEnd, value } = textarea;
+
+ // Undo: Ctrl/Cmd + Z
+ if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
+ e.preventDefault();
+ onUndo?.();
+ return;
+ }
+
+ // Redo: Ctrl/Cmd + Shift + Z or Ctrl + Y
+ if (
+ ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "z") ||
+ (e.ctrlKey && e.key === "y")
+ ) {
+ e.preventDefault();
+ onRedo?.();
+ return;
+ }
+
+ // Escape: Cancel edit
+ if (e.key === "Escape" && onEscape) {
+ e.preventDefault();
+ onEscape();
+ return;
+ }
+
+ // Tab: Insert/remove indentation
+ if (e.key === "Tab") {
+ e.preventDefault();
+ const indent = " ";
+
+ if (e.shiftKey) {
+ // Unindent
+ const lineStart = value.lastIndexOf("\n", selectionStart - 1) + 1;
+ const lineContent = value.substring(lineStart, selectionStart);
+
+ if (lineContent.startsWith(indent)) {
+ const newValue =
+ value.substring(0, lineStart) +
+ value.substring(lineStart + indent.length);
+ onChange?.(newValue);
+
+ // Restore cursor position
+ requestAnimationFrame(() => {
+ textarea.selectionStart = textarea.selectionEnd =
+ selectionStart - indent.length;
+ });
+ }
+ } else {
+ // Indent
+ const newValue =
+ value.substring(0, selectionStart) +
+ indent +
+ value.substring(selectionEnd);
+ onChange?.(newValue);
+
+ // Move cursor after indent
+ requestAnimationFrame(() => {
+ textarea.selectionStart = textarea.selectionEnd =
+ selectionStart + indent.length;
+ });
+ }
+ return;
+ }
+
+ // Enter: Auto-indent
+ if (e.key === "Enter") {
+ e.preventDefault();
+ const lineStart = value.lastIndexOf("\n", selectionStart - 1) + 1;
+ const currentLine = value.substring(lineStart, selectionStart);
+ const leadingWhitespace = currentLine.match(/^(\s*)/)?.[1] || "";
+
+ // Check if we're after an opening brace/bracket
+ const charBefore = value[selectionStart - 1];
+ const charAfter = value[selectionStart];
+ const isAfterOpening = charBefore === "{" || charBefore === "[";
+ const isBeforeClosing = charAfter === "}" || charAfter === "]";
+
+ let insertion = "\n" + leadingWhitespace;
+ let cursorOffset = insertion.length;
+
+ if (isAfterOpening) {
+ insertion = "\n" + leadingWhitespace + " ";
+ cursorOffset = insertion.length;
+
+ if (isBeforeClosing) {
+ insertion += "\n" + leadingWhitespace;
+ }
+ }
+
+ const newValue =
+ value.substring(0, selectionStart) +
+ insertion +
+ value.substring(selectionEnd);
+ onChange?.(newValue);
+
+ requestAnimationFrame(() => {
+ textarea.selectionStart = textarea.selectionEnd =
+ selectionStart + cursorOffset;
+ });
+ }
+ },
+ [onChange, onUndo, onRedo, onEscape],
+ );
+
+ // Focus textarea on mount (only in edit mode)
+ useEffect(() => {
+ if (!readOnly) {
+ textareaRef.current?.focus();
+ }
+ }, [readOnly]);
+
+ // Track viewport height for viewport-based highlighting
+ useEffect(() => {
+ const updateViewportHeight = () => {
+ if (containerRef.current) {
+ setViewportHeight(containerRef.current.clientHeight);
+ }
+ };
+
+ updateViewportHeight();
+ window.addEventListener("resize", updateViewportHeight);
+ return () => window.removeEventListener("resize", updateViewportHeight);
+ }, []);
+
+ useEffect(() => {
+ return () => cancelAnimationFrame(scrollRafRef.current);
+ }, []);
+
+ useEffect(() => {
+ if (activeLine > lineCount) {
+ setActiveLine(lineCount);
+ }
+ }, [activeLine, lineCount]);
+
+ useEffect(() => {
+ lineNumberVirtualizer.measure();
+ }, [lineLayouts, lineNumberVirtualizer]);
+
+ useEffect(() => {
+ if (!lineWrapEnabled) {
+ return;
+ }
+
+ refreshCharsPerVisualLine();
+ window.addEventListener("resize", refreshCharsPerVisualLine);
+
+ let resizeObserver: ResizeObserver | undefined;
+ if (window.ResizeObserver && textareaRef.current) {
+ resizeObserver = new ResizeObserver(() => refreshCharsPerVisualLine());
+ resizeObserver.observe(textareaRef.current);
+ }
+
+ return () => {
+ window.removeEventListener("resize", refreshCharsPerVisualLine);
+ resizeObserver?.disconnect();
+ };
+ }, [lineWrapEnabled, refreshCharsPerVisualLine]);
+
+ const containerStyle: React.CSSProperties = {
+ height: height ?? "auto",
+ maxHeight: maxHeight ?? "none",
+ };
+
+ const fontStyle: React.CSSProperties = {
+ fontFamily: "var(--font-code)",
+ };
+
+ // Sync scroll for read-only mode (sync line numbers with content)
+ const handleReadOnlyScroll = useCallback(
+ (e: React.UIEvent) => {
+ const scrollTop = e.currentTarget.scrollTop;
+ if (lineNumbersRef.current) {
+ lineNumbersRef.current.scrollTop = scrollTop;
+ }
+ },
+ [],
+ );
+
+ return (
+
+ {/* Line numbers - virtualized for performance */}
+ {showLineNumbers && (
+
+
+ {lineNumberVirtualizer.getVirtualItems().map((virtualRow: VirtualItem) => {
+ const lineNum = virtualRow.index + 1;
+ const lineHeight =
+ lineLayouts[virtualRow.index]?.height ?? LINE_HEIGHT;
+ return (
+
+ {lineNum}
+
+ );
+ })}
+
+
+ )}
+
+ {/* Editor area with overlay */}
+
+ {readOnly ? (
+ /* Read-only mode: Use JsonHighlighter with per-value copy */
+
+
+
+ ) : (
+ <>
+ {/* Syntax highlighted overlay (behind textarea) - viewport-based for performance */}
+
+
+
+
+ {/* Active line highlight (only in edit mode) */}
+ {isFocused && (
+
+ )}
+
+ {/* Transparent textarea (on top for editing) */}
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-editor-status-bar.tsx b/mcpjam-web/src/components/ui/json-editor/json-editor-status-bar.tsx
new file mode 100644
index 000000000..9cc88e7be
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-editor-status-bar.tsx
@@ -0,0 +1,70 @@
+import { XCircle } from "lucide-react";
+import { cn } from "@/lib/utils";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import type { CursorPosition } from "./types";
+
+interface JsonEditorStatusBarProps {
+ cursorPosition: CursorPosition;
+ isValid: boolean;
+ validationError?: string | null;
+ characterCount: number;
+ className?: string;
+}
+
+export function JsonEditorStatusBar({
+ cursorPosition,
+ isValid,
+ validationError,
+ characterCount,
+ className,
+}: JsonEditorStatusBarProps) {
+ return (
+
+ {/* Left side: cursor position */}
+
+
+ Ln {cursorPosition.line}, Col {cursorPosition.column}
+
+
+ {characterCount.toLocaleString()} chars
+
+
+
+ {/* Right side: validation status */}
+
+ {!isValid && (
+
+
+
+
+
+ {validationError ?? "Invalid JSON"}
+
+
+
+
+ {validationError ?? "Invalid JSON"}
+
+
+ )}
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-editor-toolbar.tsx b/mcpjam-web/src/components/ui/json-editor/json-editor-toolbar.tsx
new file mode 100644
index 000000000..05135d67c
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-editor-toolbar.tsx
@@ -0,0 +1,242 @@
+import { type ReactNode, useEffect, useRef, useState } from "react";
+import {
+ Eye,
+ Pencil,
+ AlignLeft,
+ Copy,
+ Undo2,
+ Redo2,
+ Maximize2,
+ Minimize2,
+ Check,
+} from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { cn } from "@/lib/utils";
+import type { JsonEditorMode } from "./types";
+import { SegmentedControl } from "./segmented-control";
+
+interface JsonEditorToolbarProps {
+ mode: JsonEditorMode;
+ onModeChange?: (mode: JsonEditorMode) => void;
+ showModeToggle?: boolean;
+ readOnly?: boolean;
+ onFormat?: () => void;
+ onCopy?: () => boolean | void | Promise;
+ onUndo?: () => void;
+ onRedo?: () => void;
+ canUndo?: boolean;
+ canRedo?: boolean;
+ isMaximized?: boolean;
+ onToggleMaximize?: () => void;
+ allowMaximize?: boolean;
+ isValid?: boolean;
+ className?: string;
+ /** Custom content to render on the left side of the toolbar */
+ leftContent?: ReactNode;
+ /** Custom content to render on the right side (after built-in actions) */
+ rightContent?: ReactNode;
+}
+
+const modeOptions = [
+ { value: "view" as const, label: "View", icon: },
+ {
+ value: "edit" as const,
+ label: "Edit",
+ icon: ,
+ },
+];
+
+export function JsonEditorToolbar({
+ mode,
+ onModeChange,
+ showModeToggle = true,
+ readOnly = false,
+ onFormat,
+ onCopy,
+ onUndo,
+ onRedo,
+ canUndo = false,
+ canRedo = false,
+ isMaximized = false,
+ onToggleMaximize,
+ allowMaximize = false,
+ isValid = true,
+ className,
+ leftContent,
+ rightContent,
+}: JsonEditorToolbarProps) {
+ const [copied, setCopied] = useState(false);
+ const copyResetTimeoutRef = useRef | null>(
+ null,
+ );
+
+ useEffect(() => {
+ return () => {
+ if (copyResetTimeoutRef.current) {
+ clearTimeout(copyResetTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ const handleCopy = async () => {
+ let copyResult: boolean | void;
+ try {
+ copyResult = await onCopy?.();
+ } catch {
+ return;
+ }
+
+ if (copyResult === false) {
+ return;
+ }
+
+ if (copyResetTimeoutRef.current) {
+ clearTimeout(copyResetTimeoutRef.current);
+ }
+
+ setCopied(true);
+ copyResetTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
+ };
+
+ return (
+
+ {/* Left side: custom content or mode toggle */}
+
+ {leftContent}
+ {showModeToggle && !readOnly && onModeChange && (
+
+ )}
+
+
+ {/* Right side: actions + custom content */}
+
+ {mode === "edit" && (
+ <>
+
+
+
+
+
+
+
+ Undo (Ctrl+Z)
+
+
+
+
+
+
+
+
+
+
+ Redo (Ctrl+Shift+Z)
+
+
+
+
+
+
+
+
+
+
+
+
+ Format JSON
+
+
+ >
+ )}
+
+
+
+
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+
+ {copied ? "Copied!" : "Copy to clipboard"}
+
+
+
+ {allowMaximize && (
+ <>
+
+
+
+
+
+ {isMaximized ? (
+
+ ) : (
+
+ )}
+
+
+
+ {isMaximized ? "Exit fullscreen" : "Fullscreen"}
+
+
+ >
+ )}
+
+ {rightContent && (
+ <>
+
+ {rightContent}
+ >
+ )}
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-editor-view.tsx b/mcpjam-web/src/components/ui/json-editor/json-editor-view.tsx
new file mode 100644
index 000000000..3b04bd28b
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-editor-view.tsx
@@ -0,0 +1,67 @@
+import { useMemo } from "react";
+import { JsonEditorEdit } from "./json-editor-edit";
+import { JsonTreeView } from "./json-tree-view";
+
+interface JsonEditorViewProps {
+ value: unknown;
+ className?: string;
+ height?: string | number;
+ maxHeight?: string | number;
+ showLineNumbers?: boolean;
+ collapseStringsAfterLength?: number;
+ collapsible?: boolean;
+ defaultExpandDepth?: number;
+ collapsedPaths?: Set;
+ onCollapseChange?: (paths: Set) => void;
+}
+
+export function JsonEditorView({
+ value,
+ className,
+ height,
+ maxHeight,
+ showLineNumbers = true,
+ collapseStringsAfterLength,
+ collapsible = false,
+ defaultExpandDepth,
+ collapsedPaths,
+ onCollapseChange,
+}: JsonEditorViewProps) {
+ // Convert value to formatted JSON string (only needed for flat view)
+ const content = useMemo(() => {
+ if (value === null || value === undefined) {
+ return "null";
+ }
+ try {
+ return JSON.stringify(value, null, 2);
+ } catch {
+ return String(value);
+ }
+ }, [value]);
+
+ // Use collapsible tree view when enabled
+ if (collapsible) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-editor.tsx b/mcpjam-web/src/components/ui/json-editor/json-editor.tsx
new file mode 100644
index 000000000..91f364f7f
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-editor.tsx
@@ -0,0 +1,274 @@
+import { useState, useCallback, useEffect, useRef } from "react";
+import { AlertTriangle } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { copyToClipboard } from "@/lib/clipboard";
+import { ErrorBoundary } from "@/components/evals/ErrorBoundary";
+import { useJsonEditor } from "./use-json-editor";
+import { JsonEditorView } from "./json-editor-view";
+import { JsonEditorEdit } from "./json-editor-edit";
+import { JsonEditorToolbar } from "./json-editor-toolbar";
+import { JsonEditorStatusBar } from "./json-editor-status-bar";
+import type { JsonEditorProps, JsonEditorMode } from "./types";
+
+function stringifyValue(value: unknown): string {
+ if (value === undefined) {
+ return "null";
+ }
+
+ try {
+ return JSON.stringify(value, null, 2) ?? "null";
+ } catch {
+ return "null";
+ }
+}
+
+function JsonEditorErrorFallback() {
+ return (
+
+
+ Failed to render JSON content
+
+ );
+}
+
+export function JsonEditor({
+ value,
+ onChange,
+ rawContent,
+ onRawChange,
+ mode: controlledMode,
+ onModeChange,
+ readOnly = false,
+ showModeToggle = true,
+ showToolbar = true,
+ allowMaximize = false,
+ height,
+ maxHeight,
+ className,
+ onValidationError,
+ collapsible = false,
+ defaultExpandDepth,
+ collapsedPaths,
+ onCollapseChange,
+ collapseStringsAfterLength,
+ viewOnly = false,
+ expandJsonStrings = false,
+ autoFormatOnEdit = true,
+ wrapLongLinesInEdit = false,
+ showLineNumbers = true,
+ toolbarLeftContent,
+ toolbarRightContent,
+}: JsonEditorProps) {
+ // Determine if we're in raw mode (string content) vs parsed mode
+ const isRawMode = rawContent !== undefined;
+
+ // Mode state (controlled or uncontrolled)
+ // Always call hooks to preserve hook order even in viewOnly mode
+ const [internalMode, setInternalMode] = useState("view");
+ const mode = readOnly ? "view" : (controlledMode ?? internalMode);
+ const [isMaximized, setIsMaximized] = useState(false);
+
+ // Editor hook for edit mode
+ const editor = useJsonEditor({
+ initialValue: isRawMode ? undefined : value,
+ initialContent: isRawMode ? rawContent : undefined,
+ onChange,
+ onRawChange: isRawMode ? onRawChange : undefined,
+ onValidationError,
+ expandJsonStrings,
+ });
+
+ const sourceContent = isRawMode ? (rawContent ?? "") : editor.sourceContent;
+ const hasUnsavedChanges = editor.content !== sourceContent;
+ const previousModeRef = useRef(mode);
+ const hasMountedRef = useRef(false);
+
+ useEffect(() => {
+ const previousMode = previousModeRef.current;
+ const isFirstRun = !hasMountedRef.current;
+
+ hasMountedRef.current = true;
+ previousModeRef.current = mode;
+
+ if (viewOnly || readOnly || !autoFormatOnEdit) {
+ return;
+ }
+
+ const enteredEditMode =
+ mode === "edit" && (isFirstRun || previousMode !== "edit");
+ if (!enteredEditMode || !editor.isValid) {
+ return;
+ }
+
+ editor.format();
+ }, [
+ mode,
+ editor.format,
+ editor.isValid,
+ autoFormatOnEdit,
+ readOnly,
+ viewOnly,
+ ]);
+
+ const handleModeChange = useCallback(
+ (newMode: JsonEditorMode) => {
+ if (readOnly) {
+ return;
+ }
+
+ // Warn before switching from edit to view if there are unsaved changes
+ if (mode === "edit" && newMode === "view" && hasUnsavedChanges) {
+ if (!editor.isValid) {
+ const confirmed = window.confirm(
+ "The JSON is invalid. Switching to view mode will lose your changes. Continue?",
+ );
+ if (!confirmed) return;
+ editor.reset();
+ }
+ }
+
+ setInternalMode(newMode);
+ onModeChange?.(newMode);
+ },
+ [mode, hasUnsavedChanges, editor, onModeChange, readOnly],
+ );
+
+ const handleCopy = useCallback(async () => {
+ let textToCopy: string;
+ if (mode === "edit") {
+ textToCopy = editor.content;
+ } else if (isRawMode) {
+ textToCopy = rawContent ?? "";
+ } else {
+ textToCopy = stringifyValue(value);
+ }
+
+ return copyToClipboard(textToCopy);
+ }, [mode, editor.content, value, isRawMode, rawContent]);
+
+ const handleEscape = useCallback(() => {
+ if (hasUnsavedChanges) {
+ const confirmed = window.confirm(
+ "You have unsaved changes. Discard them?",
+ );
+ if (!confirmed) return;
+ }
+ editor.reset();
+ setInternalMode("view");
+ onModeChange?.("view");
+ }, [hasUnsavedChanges, editor, onModeChange]);
+
+ const toggleMaximize = useCallback(() => {
+ setIsMaximized((prev) => !prev);
+ }, []);
+
+ // Lightweight render path for view-only mode (after all hooks to preserve hook order)
+ if (viewOnly) {
+ return (
+ }>
+
+
+ );
+ }
+
+ // Calculate container styles
+ const containerStyle: React.CSSProperties = isMaximized
+ ? {
+ position: "fixed",
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ zIndex: 50,
+ }
+ : {
+ height: height ?? "100%",
+ maxHeight: maxHeight ?? "none",
+ };
+
+ return (
+ }>
+
+ {/* Toolbar */}
+ {showToolbar && (
+
+ )}
+
+ {/* Content area */}
+
+ {mode === "view" ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Status bar (only in edit mode) */}
+ {mode === "edit" && (
+
+ )}
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-highlighter.tsx b/mcpjam-web/src/components/ui/json-editor/json-highlighter.tsx
new file mode 100644
index 000000000..439218292
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-highlighter.tsx
@@ -0,0 +1,248 @@
+import { useState, useCallback, useMemo, Fragment } from "react";
+import { Copy, Check } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { copyToClipboard } from "@/lib/clipboard";
+import { tokenizeJson } from "./json-syntax-highlighter";
+import { TruncatableString } from "./truncatable-string";
+
+interface JsonHighlighterProps {
+ content: string;
+ onCopy?: (value: string) => void;
+ collapseStringsAfterLength?: number;
+}
+
+interface CopyableValueProps {
+ children: React.ReactNode;
+ value: string;
+ onCopy?: (value: string) => void;
+}
+
+function CopyableValue({ children, value, onCopy }: CopyableValueProps) {
+ const [copied, setCopied] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+
+ const handleCopy = useCallback(
+ async (text: string) => {
+ const success = await copyToClipboard(text);
+ if (success) {
+ setCopied(true);
+ onCopy?.(text);
+ setTimeout(() => setCopied(false), 1500);
+ }
+ },
+ [onCopy],
+ );
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ {children}
+ {
+ e.stopPropagation();
+ handleCopy(value);
+ }}
+ className={cn(
+ "inline-flex items-center justify-center ml-1 p-0.5 rounded",
+ "transition-all duration-150",
+ "hover:bg-muted",
+ isHovered || copied ? "opacity-100" : "opacity-0",
+ )}
+ style={{ verticalAlign: "middle" }}
+ >
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+// Helper to extract the full object/array content from a position
+function extractObjectOrArray(content: string, startPos: number): string {
+ const openChar = content[startPos];
+ const closeChar = openChar === "{" ? "}" : "]";
+ let depth = 1;
+ let i = startPos + 1;
+ let inString = false;
+ let escape = false;
+
+ while (i < content.length && depth > 0) {
+ const char = content[i];
+
+ if (escape) {
+ escape = false;
+ } else if (char === "\\") {
+ escape = true;
+ } else if (char === '"') {
+ inString = !inString;
+ } else if (!inString) {
+ if (char === openChar) {
+ depth++;
+ } else if (char === closeChar) {
+ depth--;
+ }
+ }
+ i++;
+ }
+
+ return content.slice(startPos, i);
+}
+
+export function JsonHighlighter({
+ content,
+ onCopy,
+ collapseStringsAfterLength,
+}: JsonHighlighterProps) {
+ const elements = useMemo(() => {
+ const tokens = tokenizeJson(content);
+ const result: React.ReactNode[] = [];
+ let lastIndex = 0;
+ // Track when a key's value is an object/array
+ let pendingObjectCopy: { start: number } | null = null;
+
+ // Helper to find the next value token (skip colon punctuation)
+ const findNextValueToken = (startIndex: number) => {
+ for (let j = startIndex + 1; j < tokens.length; j++) {
+ const nextToken = tokens[j];
+ if (nextToken.type === "punctuation" && nextToken.value === ":")
+ continue;
+ return nextToken;
+ }
+ return null;
+ };
+
+ for (let i = 0; i < tokens.length; i++) {
+ const token = tokens[i];
+
+ // Add any whitespace between tokens
+ if (token.start > lastIndex) {
+ result.push(
+
+ {content.slice(lastIndex, token.start)}
+ ,
+ );
+ }
+
+ const className = `json-${token.type}`;
+
+ // Check if this key's value is an object or array
+ if (token.type === "key") {
+ const nextValueToken = findNextValueToken(i);
+ if (
+ nextValueToken &&
+ nextValueToken.type === "punctuation" &&
+ (nextValueToken.value === "{" || nextValueToken.value === "[")
+ ) {
+ // Mark that we need to add copy to the opening brace
+ pendingObjectCopy = { start: nextValueToken.start };
+ }
+ // Render key without copy button
+ result.push(
+
+ {token.value}
+ ,
+ );
+ lastIndex = token.end;
+ continue;
+ }
+
+ // Handle opening brace/bracket with pending object copy
+ if (
+ pendingObjectCopy &&
+ token.type === "punctuation" &&
+ (token.value === "{" || token.value === "[") &&
+ token.start === pendingObjectCopy.start
+ ) {
+ const objectContent = extractObjectOrArray(content, token.start);
+ pendingObjectCopy = null;
+ result.push(
+
+ {token.value}
+ ,
+ );
+ lastIndex = token.end;
+ continue;
+ }
+
+ // Determine if this token should be copyable (values only, not keys)
+ const isCopyable =
+ token.type === "string" ||
+ token.type === "number" ||
+ token.type === "boolean" ||
+ token.type === "boolean-false" ||
+ token.type === "null";
+
+ // Get the raw value to copy (without quotes for strings)
+ const getCopyValue = () => {
+ if (token.type === "string") {
+ // Remove surrounding quotes and unescape
+ try {
+ return JSON.parse(token.value);
+ } catch {
+ // If parsing fails, just remove quotes
+ return token.value.slice(1, -1);
+ }
+ }
+ return token.value;
+ };
+
+ // Use TruncatableString for strings when truncation is enabled
+ if (token.type === "string" && collapseStringsAfterLength !== undefined) {
+ const rawValue = getCopyValue();
+ result.push(
+ ,
+ );
+ } else if (isCopyable) {
+ result.push(
+
+ {token.value}
+ ,
+ );
+ } else {
+ result.push(
+
+ {token.value}
+ ,
+ );
+ }
+
+ lastIndex = token.end;
+ }
+
+ // Add any remaining content
+ if (lastIndex < content.length) {
+ result.push(
+ {content.slice(lastIndex)} ,
+ );
+ }
+
+ // Add trailing newline like the HTML version
+ result.push({"\n"} );
+
+ return result;
+ }, [content, onCopy, collapseStringsAfterLength]);
+
+ return <>{elements}>;
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-syntax-highlighter.ts b/mcpjam-web/src/components/ui/json-editor/json-syntax-highlighter.ts
new file mode 100644
index 000000000..b5ffda056
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-syntax-highlighter.ts
@@ -0,0 +1,344 @@
+type TokenType =
+ | "string"
+ | "number"
+ | "boolean"
+ | "boolean-false"
+ | "null"
+ | "key"
+ | "punctuation";
+
+interface Token {
+ type: TokenType;
+ value: string;
+ start: number;
+ end: number;
+ path?: (string | number)[]; // e.g., ["user", "profile", 0]
+ keyName?: string; // The key this value belongs to
+}
+
+/**
+ * Formats a path array into a string with dot notation and bracket notation for arrays.
+ * e.g., ["user", "profile", 0, "name"] -> "user.profile[0].name"
+ */
+export function formatPath(path: (string | number)[]): string {
+ return path
+ .map((p, i) => (typeof p === "number" ? `[${p}]` : i === 0 ? p : `.${p}`))
+ .join("");
+}
+
+/**
+ * Tokenizes a JSON string for syntax highlighting.
+ * Returns an array of tokens with their types and positions.
+ */
+export function tokenizeJson(json: string): Token[] {
+ const tokens: Token[] = [];
+ let i = 0;
+
+ const skipWhitespace = () => {
+ while (i < json.length && /\s/.test(json[i])) {
+ i++;
+ }
+ };
+
+ const readString = (): string => {
+ const start = i;
+ i++; // Skip opening quote
+ while (i < json.length) {
+ if (json[i] === "\\") {
+ i += 2; // Skip escaped character
+ } else if (json[i] === '"') {
+ i++; // Skip closing quote
+ break;
+ } else {
+ i++;
+ }
+ }
+ return json.slice(start, i);
+ };
+
+ const readNumber = (): string => {
+ const start = i;
+ if (json[i] === "-") i++;
+ while (i < json.length && /[0-9]/.test(json[i])) i++;
+ if (json[i] === ".") {
+ i++;
+ while (i < json.length && /[0-9]/.test(json[i])) i++;
+ }
+ if (json[i] === "e" || json[i] === "E") {
+ i++;
+ if (json[i] === "+" || json[i] === "-") i++;
+ while (i < json.length && /[0-9]/.test(json[i])) i++;
+ }
+ return json.slice(start, i);
+ };
+
+ const readWord = (): string => {
+ const start = i;
+ while (i < json.length && /[a-z]/.test(json[i])) i++;
+ return json.slice(start, i);
+ };
+
+ // Stack to track context (for determining if a string is a key)
+ const contextStack: ("object" | "array")[] = [];
+ let expectingKey = false;
+
+ // Path tracking
+ const pathStack: (string | number)[] = [];
+ let currentKey: string | null = null;
+
+ while (i < json.length) {
+ skipWhitespace();
+ if (i >= json.length) break;
+
+ const char = json[i];
+ const start = i;
+
+ switch (char) {
+ case "{":
+ tokens.push({ type: "punctuation", value: "{", start, end: i + 1 });
+ contextStack.push("object");
+ expectingKey = true;
+ // If we have a current key, it's now part of the path
+ if (currentKey !== null) {
+ pathStack.push(currentKey);
+ currentKey = null;
+ }
+ i++;
+ break;
+
+ case "}":
+ tokens.push({ type: "punctuation", value: "}", start, end: i + 1 });
+ contextStack.pop();
+ // Pop the object key from path if we're closing an object
+ if (
+ pathStack.length > 0 &&
+ typeof pathStack[pathStack.length - 1] === "string"
+ ) {
+ pathStack.pop();
+ }
+ expectingKey = false;
+ i++;
+ break;
+
+ case "[":
+ tokens.push({ type: "punctuation", value: "[", start, end: i + 1 });
+ contextStack.push("array");
+ // If we have a current key, it's now part of the path
+ if (currentKey !== null) {
+ pathStack.push(currentKey);
+ currentKey = null;
+ }
+ // Push array index 0
+ pathStack.push(0);
+ expectingKey = false;
+ i++;
+ break;
+
+ case "]":
+ tokens.push({ type: "punctuation", value: "]", start, end: i + 1 });
+ contextStack.pop();
+ // Pop the array index
+ if (
+ pathStack.length > 0 &&
+ typeof pathStack[pathStack.length - 1] === "number"
+ ) {
+ pathStack.pop();
+ }
+ // Pop the key that held the array if any
+ if (
+ pathStack.length > 0 &&
+ typeof pathStack[pathStack.length - 1] === "string"
+ ) {
+ pathStack.pop();
+ }
+ expectingKey = false;
+ i++;
+ break;
+
+ case ":":
+ tokens.push({ type: "punctuation", value: ":", start, end: i + 1 });
+ expectingKey = false;
+ i++;
+ break;
+
+ case ",":
+ tokens.push({ type: "punctuation", value: ",", start, end: i + 1 });
+ // After comma in object, expect key
+ expectingKey =
+ contextStack.length > 0 &&
+ contextStack[contextStack.length - 1] === "object";
+ // In array context, increment the index
+ if (
+ contextStack.length > 0 &&
+ contextStack[contextStack.length - 1] === "array" &&
+ pathStack.length > 0 &&
+ typeof pathStack[pathStack.length - 1] === "number"
+ ) {
+ pathStack[pathStack.length - 1] =
+ (pathStack[pathStack.length - 1] as number) + 1;
+ }
+ i++;
+ break;
+
+ case '"': {
+ const value = readString();
+ const isKey =
+ expectingKey &&
+ contextStack.length > 0 &&
+ contextStack[contextStack.length - 1] === "object";
+
+ if (isKey) {
+ // Parse the key name (remove quotes)
+ let keyName: string;
+ try {
+ keyName = JSON.parse(value);
+ } catch {
+ keyName = value.slice(1, -1);
+ }
+ // Store the key for the upcoming value
+ currentKey = keyName;
+ // For key tokens, the path is the current path + this key
+ tokens.push({
+ type: "key",
+ value,
+ start,
+ end: i,
+ path: [...pathStack, keyName],
+ keyName,
+ });
+ } else {
+ // String value - store current path and key info
+ const valuePath =
+ currentKey !== null ? [...pathStack, currentKey] : [...pathStack];
+ tokens.push({
+ type: "string",
+ value,
+ start,
+ end: i,
+ path: valuePath,
+ keyName: currentKey ?? undefined,
+ });
+ currentKey = null;
+ }
+ break;
+ }
+
+ case "-":
+ case "0":
+ case "1":
+ case "2":
+ case "3":
+ case "4":
+ case "5":
+ case "6":
+ case "7":
+ case "8":
+ case "9": {
+ const value = readNumber();
+ const valuePath =
+ currentKey !== null ? [...pathStack, currentKey] : [...pathStack];
+ tokens.push({
+ type: "number",
+ value,
+ start,
+ end: i,
+ path: valuePath,
+ keyName: currentKey ?? undefined,
+ });
+ currentKey = null;
+ expectingKey = false;
+ break;
+ }
+
+ case "t":
+ case "f": {
+ const value = readWord();
+ const valuePath =
+ currentKey !== null ? [...pathStack, currentKey] : [...pathStack];
+ if (value === "true") {
+ tokens.push({
+ type: "boolean",
+ value,
+ start,
+ end: i,
+ path: valuePath,
+ keyName: currentKey ?? undefined,
+ });
+ } else if (value === "false") {
+ tokens.push({
+ type: "boolean-false",
+ value,
+ start,
+ end: i,
+ path: valuePath,
+ keyName: currentKey ?? undefined,
+ });
+ }
+ currentKey = null;
+ expectingKey = false;
+ break;
+ }
+
+ case "n": {
+ const value = readWord();
+ if (value === "null") {
+ const valuePath =
+ currentKey !== null ? [...pathStack, currentKey] : [...pathStack];
+ tokens.push({
+ type: "null",
+ value,
+ start,
+ end: i,
+ path: valuePath,
+ keyName: currentKey ?? undefined,
+ });
+ currentKey = null;
+ }
+ expectingKey = false;
+ break;
+ }
+
+ default:
+ // Skip unknown characters
+ i++;
+ }
+ }
+
+ return tokens;
+}
+
+/**
+ * Converts JSON string to highlighted HTML.
+ * Returns HTML with span elements containing appropriate classes.
+ */
+export function highlightJson(json: string): string {
+ const tokens = tokenizeJson(json);
+ let result = "";
+ let lastIndex = 0;
+
+ for (const token of tokens) {
+ // Add any characters between tokens (whitespace)
+ if (token.start > lastIndex) {
+ result += escapeHtml(json.slice(lastIndex, token.start));
+ }
+
+ // Add the token with its class
+ const className = `json-${token.type}`;
+ result += `${escapeHtml(token.value)} `;
+ lastIndex = token.end;
+ }
+
+ // Add any remaining characters
+ if (lastIndex < json.length) {
+ result += escapeHtml(json.slice(lastIndex));
+ }
+
+ return result;
+}
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, "&")
+ .replace(//g, ">");
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/json-tree-node.tsx b/mcpjam-web/src/components/ui/json-editor/json-tree-node.tsx
new file mode 100644
index 000000000..cce956c99
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-tree-node.tsx
@@ -0,0 +1,557 @@
+import { useState, useCallback, useEffect, Fragment, memo } from "react";
+import { ChevronRight, Copy, Check } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { copyToClipboard } from "@/lib/clipboard";
+import { TruncatableString } from "./truncatable-string";
+
+// Progressive rendering constants
+const INITIAL_CHUNK_SIZE = 50;
+const CHUNK_SIZE = 100;
+
+// Fallback for Safari (no requestIdleCallback)
+const scheduleChunk =
+ typeof requestIdleCallback !== "undefined"
+ ? requestIdleCallback
+ : (cb: IdleRequestCallback, options?: IdleRequestOptions) => {
+ const start = Date.now();
+ return setTimeout(() => {
+ cb({
+ didTimeout: options?.timeout
+ ? Date.now() - start >= options.timeout
+ : false,
+ timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
+ });
+ }, 1) as unknown as number;
+ };
+
+const cancelChunk =
+ typeof cancelIdleCallback !== "undefined" ? cancelIdleCallback : clearTimeout;
+
+/**
+ * Hook for progressive/chunked rendering of large collections.
+ * Renders items in chunks across frames for instant perceived performance.
+ */
+function useProgressiveChildren(items: T[], isExpanded: boolean): T[] {
+ const [renderedCount, setRenderedCount] = useState(
+ items.length <= INITIAL_CHUNK_SIZE ? items.length : INITIAL_CHUNK_SIZE,
+ );
+
+ useEffect(() => {
+ // Reset when items change or collapse
+ if (!isExpanded) {
+ setRenderedCount(Math.min(items.length, INITIAL_CHUNK_SIZE));
+ return;
+ }
+
+ // If items array changed, reset to initial chunk
+ setRenderedCount((prev) => {
+ if (prev > items.length) {
+ return Math.min(items.length, INITIAL_CHUNK_SIZE);
+ }
+ return prev;
+ });
+ }, [items.length, isExpanded]);
+
+ useEffect(() => {
+ // Don't schedule if collapsed or already showing all
+ if (!isExpanded || renderedCount >= items.length) return;
+
+ // Schedule next chunk
+ const id = scheduleChunk(
+ () => {
+ setRenderedCount((prev) => Math.min(prev + CHUNK_SIZE, items.length));
+ },
+ { timeout: 100 }, // Fallback: render within 100ms even if not idle
+ );
+
+ return () => cancelChunk(id as number);
+ }, [items.length, renderedCount, isExpanded]);
+
+ return items.slice(0, renderedCount);
+}
+
+interface CopyableValueProps {
+ children: React.ReactNode;
+ value: string;
+ onCopy?: (value: string) => void;
+}
+
+const CopyableValue = memo(function CopyableValue({
+ children,
+ value,
+ onCopy,
+}: CopyableValueProps) {
+ const [copied, setCopied] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+
+ const handleCopy = useCallback(
+ async (e: React.MouseEvent) => {
+ e.stopPropagation();
+ const success = await copyToClipboard(value);
+ if (success) {
+ setCopied(true);
+ onCopy?.(value);
+ setTimeout(() => setCopied(false), 1500);
+ }
+ },
+ [value, onCopy],
+ );
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ {children}
+
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+ );
+});
+
+interface JsonTreeNodeProps {
+ value: unknown;
+ path: string;
+ keyName?: string;
+ isLast?: boolean;
+ depth?: number;
+ isCollapsed: (path: string) => boolean;
+ toggleCollapse: (path: string) => void;
+ collapseStringsAfterLength?: number;
+ onCopy?: (value: string) => void;
+}
+
+interface JsonArrayNodeProps extends Omit {
+ value: unknown[];
+}
+
+interface JsonObjectNodeProps extends Omit {
+ value: Record;
+}
+
+/**
+ * Array node component with progressive rendering
+ */
+function JsonArrayNode({
+ value,
+ path,
+ keyName,
+ isLast = true,
+ depth = 0,
+ isCollapsed: isCollapsedFn,
+ toggleCollapse,
+ collapseStringsAfterLength,
+ onCopy,
+}: JsonArrayNodeProps) {
+ const indent = depth * 16;
+ const collapsed = isCollapsedFn(path);
+
+ // Use progressive rendering for large arrays
+ const visibleItems = useProgressiveChildren(value, !collapsed);
+
+ const renderKeyPrefix = () => {
+ if (keyName === undefined) return null;
+ return (
+ <>
+ "{keyName}"
+ :
+ >
+ );
+ };
+
+ const renderComma = () => {
+ if (isLast) return null;
+ return , ;
+ };
+
+ if (value.length === 0) {
+ return (
+
+ {renderKeyPrefix()}
+
+ []
+
+ {renderComma()}
+
+ );
+ }
+
+ const handleToggle = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ toggleCollapse(path);
+ };
+
+ if (collapsed) {
+ return (
+
+
+
+
+ {renderKeyPrefix()}
+
+ [
+
+ {value.length} {value.length === 1 ? "item" : "items"}
+
+ ]
+
+ {renderComma()}
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {renderKeyPrefix()}
+
+ [
+
+
+ {visibleItems.map((item, index) => (
+
+ ))}
+ {visibleItems.length < value.length && (
+
+ Loading {value.length - visibleItems.length} more items...
+
+ )}
+
+ ]
+ {renderComma()}
+
+
+ );
+}
+
+/**
+ * Object node component with progressive rendering
+ */
+function JsonObjectNode({
+ value,
+ path,
+ keyName,
+ isLast = true,
+ depth = 0,
+ isCollapsed: isCollapsedFn,
+ toggleCollapse,
+ collapseStringsAfterLength,
+ onCopy,
+}: JsonObjectNodeProps) {
+ const indent = depth * 16;
+ const collapsed = isCollapsedFn(path);
+ const entries = Object.entries(value);
+
+ // Use progressive rendering for large objects
+ const visibleEntries = useProgressiveChildren(entries, !collapsed);
+
+ const renderKeyPrefix = () => {
+ if (keyName === undefined) return null;
+ return (
+ <>
+ "{keyName}"
+ :
+ >
+ );
+ };
+
+ const renderComma = () => {
+ if (isLast) return null;
+ return , ;
+ };
+
+ if (entries.length === 0) {
+ return (
+
+ {renderKeyPrefix()}
+
+ {"{}"}
+
+ {renderComma()}
+
+ );
+ }
+
+ const handleToggle = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ toggleCollapse(path);
+ };
+
+ if (collapsed) {
+ return (
+
+
+
+
+ {renderKeyPrefix()}
+
+ {"{"}
+
+ {entries.length} {entries.length === 1 ? "key" : "keys"}
+
+ {"}"}
+
+ {renderComma()}
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {renderKeyPrefix()}
+
+ {"{"}
+
+
+ {visibleEntries.map(([key, val], index) => (
+
+ ))}
+ {visibleEntries.length < entries.length && (
+
+ Loading {entries.length - visibleEntries.length} more keys...
+
+ )}
+
+ {"}"}
+ {renderComma()}
+
+
+ );
+}
+
+function JsonTreeNodeInner({
+ value,
+ path,
+ keyName,
+ isLast = true,
+ depth = 0,
+ isCollapsed,
+ toggleCollapse,
+ collapseStringsAfterLength,
+ onCopy,
+}: JsonTreeNodeProps) {
+ const indent = depth * 16;
+
+ const renderValue = () => {
+ if (value === null) {
+ return (
+
+ null
+
+ );
+ }
+
+ if (typeof value === "boolean") {
+ return (
+
+
+ {String(value)}
+
+
+ );
+ }
+
+ if (typeof value === "number") {
+ return (
+
+ {String(value)}
+
+ );
+ }
+
+ if (typeof value === "string") {
+ const displayValue = JSON.stringify(value);
+ if (collapseStringsAfterLength !== undefined) {
+ return (
+
+ );
+ }
+ return (
+
+ {displayValue}
+
+ );
+ }
+
+ return null;
+ };
+
+ const renderKeyPrefix = () => {
+ if (keyName === undefined) return null;
+ return (
+ <>
+ "{keyName}"
+ :
+ >
+ );
+ };
+
+ const renderComma = () => {
+ if (isLast) return null;
+ return , ;
+ };
+
+ // Primitive values
+ if (
+ value === null ||
+ typeof value === "boolean" ||
+ typeof value === "number" ||
+ typeof value === "string"
+ ) {
+ return (
+
+ {renderKeyPrefix()}
+ {renderValue()}
+ {renderComma()}
+
+ );
+ }
+
+ // Arrays
+ if (Array.isArray(value)) {
+ return (
+
+ );
+ }
+
+ // Objects
+ if (typeof value === "object") {
+ return (
+ }
+ path={path}
+ keyName={keyName}
+ isLast={isLast}
+ depth={depth}
+ isCollapsed={isCollapsed}
+ toggleCollapse={toggleCollapse}
+ collapseStringsAfterLength={collapseStringsAfterLength}
+ onCopy={onCopy}
+ />
+ );
+ }
+
+ // Fallback for undefined or other types
+ return (
+
+ {renderKeyPrefix()}
+ undefined
+ {renderComma()}
+
+ );
+}
+
+// Custom comparator - only re-render if relevant props change
+function arePropsEqual(
+ prevProps: JsonTreeNodeProps,
+ nextProps: JsonTreeNodeProps,
+): boolean {
+ // Always re-render if value changes
+ if (prevProps.value !== nextProps.value) return false;
+
+ // Re-render if structural props change
+ if (prevProps.path !== nextProps.path) return false;
+ if (prevProps.keyName !== nextProps.keyName) return false;
+ if (prevProps.isLast !== nextProps.isLast) return false;
+ if (prevProps.depth !== nextProps.depth) return false;
+ if (
+ prevProps.collapseStringsAfterLength !==
+ nextProps.collapseStringsAfterLength
+ )
+ return false;
+
+ // Re-render when isCollapsed function changes (happens when collapsedPaths changes)
+ if (prevProps.isCollapsed !== nextProps.isCollapsed) return false;
+
+ return true;
+}
+
+// Create memoized component and export
+// Note: JsonArrayNode and JsonObjectNode reference this, creating a circular dependency
+// which is fine because they're only called at runtime after this is defined
+export const JsonTreeNode = memo(JsonTreeNodeInner, arePropsEqual);
diff --git a/mcpjam-web/src/components/ui/json-editor/json-tree-view.tsx b/mcpjam-web/src/components/ui/json-editor/json-tree-view.tsx
new file mode 100644
index 000000000..412f5ad88
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/json-tree-view.tsx
@@ -0,0 +1,58 @@
+import { useEffect } from "react";
+import { cn } from "@/lib/utils";
+import { useJsonTreeState } from "./use-json-tree-state";
+import { JsonTreeNode } from "./json-tree-node";
+
+interface JsonTreeViewProps {
+ value: unknown;
+ className?: string;
+ defaultExpandDepth?: number;
+ collapsedPaths?: Set;
+ onCollapseChange?: (paths: Set) => void;
+ collapseStringsAfterLength?: number;
+ onCopy?: (value: string) => void;
+}
+
+export function JsonTreeView({
+ value,
+ className,
+ defaultExpandDepth,
+ collapsedPaths: controlledCollapsedPaths,
+ onCollapseChange,
+ collapseStringsAfterLength,
+ onCopy,
+}: JsonTreeViewProps) {
+ const { isCollapsed, toggleCollapse, initializeFromValue } = useJsonTreeState(
+ {
+ defaultExpandDepth,
+ initialCollapsedPaths: controlledCollapsedPaths,
+ onCollapseChange,
+ },
+ );
+
+ // Initialize collapse state based on defaultExpandDepth
+ useEffect(() => {
+ initializeFromValue(value);
+ }, [value, initializeFromValue]);
+
+ return (
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/segmented-control.tsx b/mcpjam-web/src/components/ui/json-editor/segmented-control.tsx
new file mode 100644
index 000000000..81506a112
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/segmented-control.tsx
@@ -0,0 +1,81 @@
+import { useRef, useEffect, useState } from "react";
+import { cn } from "@/lib/utils";
+
+interface SegmentedControlOption {
+ value: T;
+ label: string;
+ icon?: React.ReactNode;
+}
+
+interface SegmentedControlProps {
+ options: SegmentedControlOption[];
+ value: T;
+ onChange: (value: T) => void;
+ className?: string;
+}
+
+export function SegmentedControl({
+ options,
+ value,
+ onChange,
+ className,
+}: SegmentedControlProps) {
+ const containerRef = useRef(null);
+ const [indicatorStyle, setIndicatorStyle] = useState({});
+
+ useEffect(() => {
+ const container = containerRef.current;
+ if (!container) return;
+
+ const selectedIndex = options.findIndex((opt) => opt.value === value);
+ const buttons = container.querySelectorAll("button");
+ const selectedButton = buttons[selectedIndex];
+
+ if (selectedButton) {
+ setIndicatorStyle({
+ width: selectedButton.offsetWidth,
+ transform: `translateX(${selectedButton.offsetLeft}px)`,
+ });
+ }
+ }, [value, options]);
+
+ return (
+
+ {/* Sliding indicator */}
+
+
+ {/* Options */}
+ {options.map((option) => (
+
onChange(option.value)}
+ className={cn(
+ "relative z-10 flex items-center gap-1.5 px-2.5 py-1",
+ "text-xs font-medium rounded-md",
+ "transition-colors duration-200",
+ value === option.value
+ ? "text-foreground"
+ : "text-muted-foreground hover:text-foreground/80",
+ )}
+ >
+ {option.icon}
+ {option.label}
+
+ ))}
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/truncatable-string.tsx b/mcpjam-web/src/components/ui/json-editor/truncatable-string.tsx
new file mode 100644
index 000000000..edde4c8a4
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/truncatable-string.tsx
@@ -0,0 +1,167 @@
+import { useState, useCallback } from "react";
+import { Copy, Check, ChevronDown } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { copyToClipboard } from "@/lib/clipboard";
+import { formatPath } from "./json-syntax-highlighter";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+interface TruncatableStringProps {
+ value: string;
+ displayValue: string;
+ maxLength: number;
+ onCopy?: (value: string) => void;
+ keyName?: string;
+ path?: (string | number)[];
+}
+
+export function TruncatableString({
+ value,
+ displayValue,
+ maxLength,
+ onCopy,
+ keyName,
+ path,
+}: TruncatableStringProps) {
+ const [copied, setCopied] = useState(null);
+ const [isHovered, setIsHovered] = useState(false);
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const shouldTruncate = value.length > maxLength;
+ const truncatedDisplay =
+ shouldTruncate && !isExpanded
+ ? `"${value.slice(0, maxLength)}..."`
+ : displayValue;
+
+ const handleCopy = useCallback(
+ async (text: string, label: string) => {
+ const success = await copyToClipboard(text);
+ if (success) {
+ setCopied(label);
+ onCopy?.(text);
+ setTimeout(() => setCopied(null), 1500);
+ }
+ },
+ [onCopy],
+ );
+
+ const handleToggleExpand = useCallback(
+ (e: React.MouseEvent) => {
+ if (shouldTruncate) {
+ e.stopPropagation();
+ setIsExpanded((prev) => !prev);
+ }
+ },
+ [shouldTruncate],
+ );
+
+ const formattedPath = path && path.length > 0 ? formatPath(path) : null;
+
+ // Build menu items
+ const menuItems: { label: string; value: string; key: string }[] = [];
+ menuItems.push({ label: "Copy value", value: value, key: "value" });
+ if (keyName) {
+ menuItems.push({ label: "Copy key", value: keyName, key: "key" });
+ }
+ if (formattedPath) {
+ menuItems.push({ label: "Copy path", value: formattedPath, key: "path" });
+ }
+
+ // If only one option, show simple button
+ if (menuItems.length === 1) {
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+ {truncatedDisplay}
+
+ {
+ e.stopPropagation();
+ handleCopy(menuItems[0].value, menuItems[0].key);
+ }}
+ className={cn(
+ "inline-flex items-center justify-center ml-1 p-0.5 rounded",
+ "transition-all duration-150",
+ "hover:bg-muted",
+ isHovered || copied ? "opacity-100" : "opacity-0",
+ )}
+ style={{ verticalAlign: "middle" }}
+ >
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ }
+
+ // Multiple options: show dropdown
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+ {truncatedDisplay}
+
+
+
+ e.stopPropagation()}
+ >
+ {copied ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+
+ {menuItems.map((item) => (
+ handleCopy(item.value, item.key)}
+ className="text-xs"
+ >
+
+ {item.label}
+
+ ))}
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/types.ts b/mcpjam-web/src/components/ui/json-editor/types.ts
new file mode 100644
index 000000000..acc116f4b
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/types.ts
@@ -0,0 +1,83 @@
+import type { ReactNode } from "react";
+
+export type JsonEditorMode = "view" | "edit";
+
+export interface JsonEditorProps {
+ // Parsed value mode (default)
+ value?: unknown;
+ onChange?: (value: unknown) => void;
+
+ // Raw string mode (for edit-only use cases like import)
+ rawContent?: string;
+ onRawChange?: (content: string) => void;
+
+ mode?: JsonEditorMode;
+ onModeChange?: (mode: JsonEditorMode) => void;
+ readOnly?: boolean;
+ showModeToggle?: boolean;
+ showToolbar?: boolean;
+ allowMaximize?: boolean;
+ height?: string | number;
+ maxHeight?: string | number;
+ className?: string;
+ onValidationError?: (error: string | null) => void;
+
+ // Collapsible tree view options (view mode only)
+ collapsible?: boolean;
+ defaultExpandDepth?: number;
+ collapsedPaths?: Set;
+ onCollapseChange?: (paths: Set) => void;
+
+ // String truncation (view mode only)
+ collapseStringsAfterLength?: number;
+
+ // View-only mode: renders just the view without toolbar or edit capabilities
+ viewOnly?: boolean;
+
+ // Expand stringified JSON values for display and collapse back on change
+ expandJsonStrings?: boolean;
+
+ // Automatically format valid JSON when entering edit mode
+ autoFormatOnEdit?: boolean;
+
+ // Soft-wrap long lines in edit mode while preserving logical line numbers
+ wrapLongLinesInEdit?: boolean;
+
+ // Show or hide the line number gutter
+ showLineNumbers?: boolean;
+
+ // Custom toolbar content
+ toolbarLeftContent?: ReactNode;
+ toolbarRightContent?: ReactNode;
+}
+
+export interface CursorPosition {
+ line: number;
+ column: number;
+}
+
+export interface UseJsonEditorOptions {
+ initialValue?: unknown;
+ initialContent?: string;
+ onChange?: (value: unknown) => void;
+ onRawChange?: (content: string) => void;
+ onValidationError?: (error: string | null) => void;
+ expandJsonStrings?: boolean;
+}
+
+export interface UseJsonEditorReturn {
+ content: string;
+ setContent: (value: string) => void;
+ isValid: boolean;
+ validationError: string | null;
+ cursorPosition: CursorPosition;
+ setCursorPosition: (position: CursorPosition) => void;
+ undo: () => void;
+ redo: () => void;
+ canUndo: boolean;
+ canRedo: boolean;
+ format: () => void;
+ reset: () => void;
+ getParsedValue: () => unknown;
+ sourceContent: string;
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/use-json-editor.ts b/mcpjam-web/src/components/ui/json-editor/use-json-editor.ts
new file mode 100644
index 000000000..d3a13db34
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/use-json-editor.ts
@@ -0,0 +1,436 @@
+import { useState, useCallback, useRef, useEffect } from "react";
+import type {
+ UseJsonEditorOptions,
+ UseJsonEditorReturn,
+ CursorPosition,
+} from "./types";
+
+interface HistoryEntry {
+ content: string;
+ cursorPosition: CursorPosition;
+}
+
+const MAX_HISTORY_SIZE = 50;
+const FALLBACK_JSON_CONTENT = "null";
+
+function getDefaultCursorPosition(): CursorPosition {
+ return { line: 1, column: 1 };
+}
+
+function stringifyValue(value: unknown): string {
+ if (value === undefined) {
+ return FALLBACK_JSON_CONTENT;
+ }
+
+ try {
+ return JSON.stringify(value, null, 2) ?? FALLBACK_JSON_CONTENT;
+ } catch {
+ return FALLBACK_JSON_CONTENT;
+ }
+}
+
+function parseJson(content: string): { value: unknown; error: string | null } {
+ try {
+ const value = JSON.parse(content);
+ return { value, error: null };
+ } catch (e) {
+ const error = e instanceof Error ? e.message : "Invalid JSON";
+ return { value: undefined, error };
+ }
+}
+
+function serializeParsedValue(value: unknown): string | null {
+ try {
+ return JSON.stringify(value);
+ } catch {
+ return null;
+ }
+}
+
+export function expandJsonStringsInValue(value: unknown): unknown {
+ if (typeof value === "string") {
+ try {
+ const parsed = JSON.parse(value);
+ if (typeof parsed === "object" && parsed !== null) {
+ return expandJsonStringsInValue(parsed);
+ }
+ } catch {
+ // not valid JSON, keep as string
+ }
+ return value;
+ }
+ if (Array.isArray(value)) return value.map(expandJsonStringsInValue);
+ if (typeof value === "object" && value !== null) {
+ const result: Record = {};
+ for (const [key, val] of Object.entries(value))
+ result[key] = expandJsonStringsInValue(val);
+ return result;
+ }
+ return value;
+}
+
+function collapseJsonStringsInValue(
+ current: unknown,
+ original: unknown,
+): unknown {
+ if (typeof original === "string" && typeof current !== "string") {
+ try {
+ const parsed = JSON.parse(original);
+ if (typeof parsed === "object" && parsed !== null) {
+ return JSON.stringify(current);
+ }
+ } catch {
+ // original wasn't a JSON string, return as-is
+ }
+ }
+ if (
+ typeof current === "object" &&
+ current !== null &&
+ !Array.isArray(current) &&
+ typeof original === "object" &&
+ original !== null &&
+ !Array.isArray(original)
+ ) {
+ const result: Record = {};
+ const origObj = original as Record;
+ for (const [key, val] of Object.entries(current as Record))
+ result[key] =
+ key in origObj ? collapseJsonStringsInValue(val, origObj[key]) : val;
+ return result;
+ }
+ if (Array.isArray(current) && Array.isArray(original)) {
+ return current.map((item, i) =>
+ i < original.length
+ ? collapseJsonStringsInValue(item, original[i])
+ : item,
+ );
+ }
+ return current;
+}
+
+function computeDisplayContent(
+ rawContent: string | undefined,
+ value: unknown,
+ expand: boolean,
+): string {
+ if (rawContent !== undefined) return rawContent;
+ return stringifyValue(expand ? expandJsonStringsInValue(value) : value);
+}
+
+export function useJsonEditor({
+ initialValue,
+ initialContent: initialContentProp,
+ onChange,
+ onRawChange,
+ onValidationError,
+ expandJsonStrings = false,
+}: UseJsonEditorOptions): UseJsonEditorReturn {
+ // Lazy first-render initialization — avoids expensive expand+stringify on every render
+ const initRef = useRef<{
+ content: string;
+ lastEmitted: string | null;
+ error: string | null;
+ } | null>(null);
+ if (initRef.current === null) {
+ const content = computeDisplayContent(
+ initialContentProp,
+ initialValue,
+ expandJsonStrings,
+ );
+ const parsed = parseJson(content);
+ let lastEmitted: string | null;
+ if (parsed.error !== null) {
+ lastEmitted = null;
+ } else if (expandJsonStrings) {
+ lastEmitted = serializeParsedValue(
+ collapseJsonStringsInValue(parsed.value, initialValue),
+ );
+ } else {
+ lastEmitted = serializeParsedValue(parsed.value);
+ }
+ initRef.current = {
+ content,
+ lastEmitted,
+ error: parsed.error,
+ };
+ }
+
+ const [content, setContentInternal] = useState(initRef.current.content);
+
+ // Track the original (unexpanded) value for collapsing back on onChange
+ const originalValueRef = useRef(initialValue);
+ const contentRef = useRef(initRef.current.content);
+ const sourceContentRef = useRef(initRef.current.content);
+ const lastEmittedParsedValueRef = useRef(
+ initRef.current.lastEmitted,
+ );
+ const [validationError, setValidationError] = useState(
+ initRef.current.error,
+ );
+ const [cursorPosition, setCursorPosition] = useState(
+ getDefaultCursorPosition,
+ );
+
+ // History for undo/redo
+ const historyRef = useRef([
+ {
+ content: initRef.current.content,
+ cursorPosition: getDefaultCursorPosition(),
+ },
+ ]);
+ const historyIndexRef = useRef(0);
+
+ const validateContent = useCallback(
+ (text: string) => {
+ const parsed = parseJson(text);
+ setValidationError(parsed.error);
+ onValidationError?.(parsed.error);
+ return parsed;
+ },
+ [onValidationError],
+ );
+
+ useEffect(() => {
+ contentRef.current = content;
+ }, [content]);
+
+ // Render-time sourceContent update: detect when inputs change and update
+ // sourceContentRef synchronously so the returned value is always current.
+ // Uses round-trip detection to skip expensive expand+stringify during typing.
+ const prevInputsRef = useRef({
+ value: initialValue,
+ content: initialContentProp,
+ });
+ if (
+ initialValue !== prevInputsRef.current.value ||
+ initialContentProp !== prevInputsRef.current.content
+ ) {
+ prevInputsRef.current = {
+ value: initialValue,
+ content: initialContentProp,
+ };
+ if (initialContentProp === undefined && expandJsonStrings) {
+ const incomingSerialized = serializeParsedValue(initialValue);
+ if (
+ incomingSerialized !== null &&
+ incomingSerialized === lastEmittedParsedValueRef.current
+ ) {
+ // Round-trip: parent echoed back our onChange — no expensive computation
+ originalValueRef.current = initialValue;
+ sourceContentRef.current = contentRef.current;
+ } else {
+ // Genuine external change
+ originalValueRef.current = initialValue;
+ sourceContentRef.current = computeDisplayContent(
+ undefined,
+ initialValue,
+ true,
+ );
+ }
+ } else {
+ originalValueRef.current = initialValue;
+ sourceContentRef.current = computeDisplayContent(
+ initialContentProp,
+ initialValue,
+ expandJsonStrings,
+ );
+ }
+ }
+
+ // Sync initial value/content when it changes externally (state updates)
+ useEffect(() => {
+ const newContent = sourceContentRef.current;
+ if (newContent === contentRef.current) {
+ return;
+ }
+
+ contentRef.current = newContent;
+ setContentInternal(newContent);
+ setCursorPosition(getDefaultCursorPosition());
+ historyRef.current = [
+ { content: newContent, cursorPosition: getDefaultCursorPosition() },
+ ];
+ historyIndexRef.current = 0;
+ validateContent(newContent);
+ // Compute lastEmitted from collapsed form so dedup works correctly
+ const parsed = parseJson(newContent);
+ if (parsed.error === null && expandJsonStrings) {
+ const collapsed = collapseJsonStringsInValue(parsed.value, initialValue);
+ lastEmittedParsedValueRef.current = serializeParsedValue(collapsed);
+ } else {
+ lastEmittedParsedValueRef.current =
+ parsed.error === null ? serializeParsedValue(parsed.value) : null;
+ }
+ }, [initialValue, initialContentProp, validateContent, expandJsonStrings]);
+
+ const notifyChangeCallbacks = useCallback(
+ (newContent: string, parsedValue?: unknown, error?: string | null) => {
+ onRawChange?.(newContent);
+
+ const emitOnChangeIfNeeded = (value: unknown) => {
+ const valueToEmit = expandJsonStrings
+ ? collapseJsonStringsInValue(value, originalValueRef.current)
+ : value;
+ const serialized = serializeParsedValue(valueToEmit);
+
+ // Fallback for unexpected non-serializable values
+ if (serialized === null) {
+ onChange?.(valueToEmit);
+ return;
+ }
+
+ if (serialized === lastEmittedParsedValueRef.current) {
+ return;
+ }
+
+ lastEmittedParsedValueRef.current = serialized;
+ onChange?.(valueToEmit);
+ };
+
+ if (error === undefined) {
+ const result = parseJson(newContent);
+ if (result.error === null) {
+ emitOnChangeIfNeeded(result.value);
+ }
+ return;
+ }
+
+ if (error === null) {
+ emitOnChangeIfNeeded(parsedValue);
+ }
+ },
+ [onChange, onRawChange, expandJsonStrings],
+ );
+
+ const setContent = useCallback(
+ (newContent: string) => {
+ if (newContent === contentRef.current) {
+ return;
+ }
+
+ contentRef.current = newContent;
+ setContentInternal(newContent);
+ const { value, error } = validateContent(newContent);
+
+ // Add to history
+ const currentIndex = historyIndexRef.current;
+ let history = historyRef.current;
+
+ // Remove any forward history if we're not at the end
+ if (currentIndex < history.length - 1) {
+ history = history.slice(0, currentIndex + 1);
+ }
+
+ const currentEntry = history[history.length - 1];
+
+ if (!currentEntry || currentEntry.content !== newContent) {
+ history = [...history, { content: newContent, cursorPosition }];
+ }
+
+ // Trim history if too large
+ if (history.length > MAX_HISTORY_SIZE) {
+ history = history.slice(-MAX_HISTORY_SIZE);
+ }
+
+ historyRef.current = history;
+ historyIndexRef.current = history.length - 1;
+
+ notifyChangeCallbacks(newContent, value, error);
+ },
+ [cursorPosition, notifyChangeCallbacks, validateContent],
+ );
+
+ const applyHistoryEntry = useCallback(
+ (entry: HistoryEntry) => {
+ contentRef.current = entry.content;
+ setContentInternal(entry.content);
+ setCursorPosition(entry.cursorPosition);
+ const { value, error } = validateContent(entry.content);
+ notifyChangeCallbacks(entry.content, value, error);
+ },
+ [notifyChangeCallbacks, validateContent],
+ );
+
+ const undo = useCallback(() => {
+ const history = historyRef.current;
+ const currentIndex = historyIndexRef.current;
+
+ if (currentIndex > 0) {
+ historyIndexRef.current = currentIndex - 1;
+ const entry = history[currentIndex - 1];
+ applyHistoryEntry(entry);
+ }
+ }, [applyHistoryEntry]);
+
+ const redo = useCallback(() => {
+ const history = historyRef.current;
+ const currentIndex = historyIndexRef.current;
+
+ if (currentIndex < history.length - 1) {
+ historyIndexRef.current = currentIndex + 1;
+ const entry = history[currentIndex + 1];
+ applyHistoryEntry(entry);
+ }
+ }, [applyHistoryEntry]);
+
+ const format = useCallback(() => {
+ const { value, error } = parseJson(content);
+ if (error === null) {
+ const toFormat = expandJsonStrings
+ ? expandJsonStringsInValue(value)
+ : value;
+ const formatted = JSON.stringify(toFormat, null, 2);
+ setContent(formatted);
+ }
+ }, [content, setContent, expandJsonStrings]);
+
+ const reset = useCallback(() => {
+ originalValueRef.current = initialValue;
+ const newContent = computeDisplayContent(
+ initialContentProp,
+ initialValue,
+ expandJsonStrings,
+ );
+ sourceContentRef.current = newContent;
+ contentRef.current = newContent;
+ setContentInternal(newContent);
+ setCursorPosition(getDefaultCursorPosition());
+ historyRef.current = [
+ { content: newContent, cursorPosition: getDefaultCursorPosition() },
+ ];
+ historyIndexRef.current = 0;
+ const { value, error } = validateContent(newContent);
+ notifyChangeCallbacks(newContent, value, error);
+ }, [
+ initialValue,
+ initialContentProp,
+ validateContent,
+ notifyChangeCallbacks,
+ expandJsonStrings,
+ ]);
+
+ const getParsedValue = useCallback(() => {
+ const { value, error } = parseJson(content);
+ return error === null ? value : undefined;
+ }, [content]);
+
+ const canUndo = historyIndexRef.current > 0;
+ const canRedo = historyIndexRef.current < historyRef.current.length - 1;
+
+ return {
+ content,
+ setContent,
+ isValid: validationError === null,
+ validationError,
+ cursorPosition,
+ setCursorPosition,
+ undo,
+ redo,
+ canUndo,
+ canRedo,
+ format,
+ reset,
+ getParsedValue,
+ sourceContent: sourceContentRef.current,
+ };
+}
diff --git a/mcpjam-web/src/components/ui/json-editor/use-json-tree-state.ts b/mcpjam-web/src/components/ui/json-editor/use-json-tree-state.ts
new file mode 100644
index 000000000..8af72c8b2
--- /dev/null
+++ b/mcpjam-web/src/components/ui/json-editor/use-json-tree-state.ts
@@ -0,0 +1,177 @@
+import { useState, useCallback, useMemo, useEffect } from "react";
+
+interface UseJsonTreeStateOptions {
+ defaultExpandDepth?: number;
+ initialCollapsedPaths?: Set;
+ onCollapseChange?: (paths: Set) => void;
+}
+
+interface UseJsonTreeStateReturn {
+ collapsedPaths: Set;
+ isCollapsed: (path: string) => boolean;
+ toggleCollapse: (path: string) => void;
+ expandAll: () => void;
+ collapseAll: (value: unknown) => void;
+ initializeFromValue: (value: unknown) => void;
+}
+
+function getPathsAtDepth(
+ value: unknown,
+ maxDepth: number,
+ currentPath = "root",
+ currentDepth = 0,
+): string[] {
+ const paths: string[] = [];
+
+ if (currentDepth >= maxDepth) {
+ if (
+ (typeof value === "object" &&
+ value !== null &&
+ Object.keys(value).length > 0) ||
+ (Array.isArray(value) && value.length > 0)
+ ) {
+ paths.push(currentPath);
+ }
+ }
+
+ if (typeof value === "object" && value !== null) {
+ if (Array.isArray(value)) {
+ value.forEach((item, index) => {
+ paths.push(
+ ...getPathsAtDepth(
+ item,
+ maxDepth,
+ `${currentPath}.${index}`,
+ currentDepth + 1,
+ ),
+ );
+ });
+ } else {
+ Object.entries(value).forEach(([key, val]) => {
+ paths.push(
+ ...getPathsAtDepth(
+ val,
+ maxDepth,
+ `${currentPath}.${key}`,
+ currentDepth + 1,
+ ),
+ );
+ });
+ }
+ }
+
+ return paths;
+}
+
+function getAllPaths(value: unknown, currentPath = "root"): string[] {
+ const paths: string[] = [];
+
+ if (typeof value === "object" && value !== null) {
+ if (Array.isArray(value)) {
+ if (value.length > 0) {
+ paths.push(currentPath);
+ value.forEach((item, index) => {
+ paths.push(...getAllPaths(item, `${currentPath}.${index}`));
+ });
+ }
+ } else {
+ const keys = Object.keys(value);
+ if (keys.length > 0) {
+ paths.push(currentPath);
+ Object.entries(value).forEach(([key, val]) => {
+ paths.push(...getAllPaths(val, `${currentPath}.${key}`));
+ });
+ }
+ }
+ }
+
+ return paths;
+}
+
+export function useJsonTreeState({
+ defaultExpandDepth,
+ initialCollapsedPaths,
+ onCollapseChange,
+}: UseJsonTreeStateOptions = {}): UseJsonTreeStateReturn {
+ const [collapsedPaths, setCollapsedPaths] = useState>(
+ () => initialCollapsedPaths ?? new Set(),
+ );
+ const [initialized, setInitialized] = useState(false);
+
+ const isCollapsed = useCallback(
+ (path: string): boolean => collapsedPaths.has(path),
+ [collapsedPaths],
+ );
+
+ const toggleCollapse = useCallback(
+ (path: string) => {
+ setCollapsedPaths((prev) => {
+ const next = new Set(prev);
+ if (next.has(path)) {
+ next.delete(path);
+ } else {
+ next.add(path);
+ }
+ onCollapseChange?.(next);
+ return next;
+ });
+ },
+ [onCollapseChange],
+ );
+
+ const expandAll = useCallback(() => {
+ setCollapsedPaths(new Set());
+ onCollapseChange?.(new Set());
+ }, [onCollapseChange]);
+
+ const collapseAll = useCallback(
+ (value: unknown) => {
+ const allPaths = getAllPaths(value);
+ const newCollapsed = new Set(allPaths);
+ setCollapsedPaths(newCollapsed);
+ onCollapseChange?.(newCollapsed);
+ },
+ [onCollapseChange],
+ );
+
+ const initializeFromValue = useCallback(
+ (value: unknown) => {
+ if (initialized || initialCollapsedPaths !== undefined) return;
+
+ if (defaultExpandDepth !== undefined) {
+ const pathsToCollapse = getPathsAtDepth(value, defaultExpandDepth);
+ const newCollapsed = new Set(pathsToCollapse);
+ setCollapsedPaths(newCollapsed);
+ onCollapseChange?.(newCollapsed);
+ }
+ setInitialized(true);
+ },
+ [initialized, defaultExpandDepth, initialCollapsedPaths, onCollapseChange],
+ );
+
+ // Sync with external collapsed paths if controlled
+ useEffect(() => {
+ if (initialCollapsedPaths !== undefined) {
+ setCollapsedPaths(initialCollapsedPaths);
+ }
+ }, [initialCollapsedPaths]);
+
+ return useMemo(
+ () => ({
+ collapsedPaths,
+ isCollapsed,
+ toggleCollapse,
+ expandAll,
+ collapseAll,
+ initializeFromValue,
+ }),
+ [
+ collapsedPaths,
+ isCollapsed,
+ toggleCollapse,
+ expandAll,
+ collapseAll,
+ initializeFromValue,
+ ],
+ );
+}
diff --git a/mcpjam-web/src/components/ui/label.tsx b/mcpjam-web/src/components/ui/label.tsx
new file mode 100644
index 000000000..61818eba8
--- /dev/null
+++ b/mcpjam-web/src/components/ui/label.tsx
@@ -0,0 +1,22 @@
+import * as React from "react";
+import { Label as LabelPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Label };
diff --git a/mcpjam-web/src/components/ui/mcp-icon.tsx b/mcpjam-web/src/components/ui/mcp-icon.tsx
new file mode 100644
index 000000000..806701acf
--- /dev/null
+++ b/mcpjam-web/src/components/ui/mcp-icon.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+export const MCPIcon = React.forwardRef<
+ React.ElementRef<"svg">,
+ React.ComponentProps<"svg">
+>(({ className, ...props }, ref) => (
+
+ ModelContextProtocol
+
+
+
+));
+MCPIcon.displayName = "MCPIcon";
diff --git a/mcpjam-web/src/components/ui/select.tsx b/mcpjam-web/src/components/ui/select.tsx
new file mode 100644
index 000000000..5fb584336
--- /dev/null
+++ b/mcpjam-web/src/components/ui/select.tsx
@@ -0,0 +1,183 @@
+import * as React from "react";
+import { Select as SelectPrimitive } from "radix-ui";
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default";
+}) {
+ return (
+
+ {children}
+
+
+
+
+ );
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "popper",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+};
diff --git a/mcpjam-web/src/components/ui/separator.tsx b/mcpjam-web/src/components/ui/separator.tsx
new file mode 100644
index 000000000..9d3e2fd66
--- /dev/null
+++ b/mcpjam-web/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+import { Separator as SeparatorPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Separator };
diff --git a/mcpjam-web/src/components/ui/sonner.tsx b/mcpjam-web/src/components/ui/sonner.tsx
new file mode 100644
index 000000000..d5244c21b
--- /dev/null
+++ b/mcpjam-web/src/components/ui/sonner.tsx
@@ -0,0 +1,18 @@
+import { Toaster as Sonner, type ToasterProps } from "sonner";
+import type { CSSProperties } from "react";
+
+export function Toaster(props: ToasterProps) {
+ return (
+
+ );
+}
diff --git a/mcpjam-web/src/components/ui/switch.tsx b/mcpjam-web/src/components/ui/switch.tsx
new file mode 100644
index 000000000..752812aea
--- /dev/null
+++ b/mcpjam-web/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+import * as React from "react";
+import { Switch as SwitchPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { Switch };
diff --git a/mcpjam-web/src/components/ui/tabs.tsx b/mcpjam-web/src/components/ui/tabs.tsx
new file mode 100644
index 000000000..56761057b
--- /dev/null
+++ b/mcpjam-web/src/components/ui/tabs.tsx
@@ -0,0 +1,64 @@
+import * as React from "react";
+import { Tabs as TabsPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function Tabs({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/mcpjam-web/src/components/ui/tooltip.tsx b/mcpjam-web/src/components/ui/tooltip.tsx
new file mode 100644
index 000000000..94635925f
--- /dev/null
+++ b/mcpjam-web/src/components/ui/tooltip.tsx
@@ -0,0 +1,59 @@
+import * as React from "react";
+import { Tooltip as TooltipPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/mcpjam-web/src/hooks/McpConnectionsProvider.tsx b/mcpjam-web/src/hooks/McpConnectionsProvider.tsx
new file mode 100644
index 000000000..aa1756c43
--- /dev/null
+++ b/mcpjam-web/src/hooks/McpConnectionsProvider.tsx
@@ -0,0 +1,472 @@
+import { MCPClientManager, type HttpServerConfig } from "@mcpjam/sdk/browser";
+import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
+import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
+import {
+ consumeOAuthReturnHash,
+ getPendingOAuthServer,
+ getStoredOAuthTokens,
+ setPendingOAuthServer,
+} from "../lib/oauth/mcpOAuthProvider";
+import { finishOAuthCallback, startOAuth } from "../lib/oauth/oauthFlow";
+import {
+ McpConnectionsContext,
+ type ConnectServerInput,
+ type MCPConnectionError,
+ type MCPServerConnection,
+ type McpConnectionsContextValue,
+} from "./mcpConnectionsContext";
+
+const STORAGE_SERVERS_KEY = "mcpjam-web-servers";
+const STORAGE_ACTIVE_SERVER_KEY = "mcpjam-web-active-server-id";
+
+function createServerId(name: string, url: string) {
+ return `server-${name}-${url}`
+ .replace(/[^a-zA-Z0-9]/g, "-")
+ .replace(/-+/g, "-");
+}
+
+function isLocalAddress(hostname: string): boolean {
+ const lowered = hostname.toLowerCase();
+ if (lowered === "localhost") return true;
+ if (lowered.endsWith(".localhost")) return true;
+ if (lowered.endsWith(".local")) return true;
+ if (lowered === "127.0.0.1" || lowered === "::1") return true;
+ if (/^10\./.test(lowered)) return true;
+ if (/^192\.168\./.test(lowered)) return true;
+ if (/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(lowered)) return true;
+ return false;
+}
+
+function normalizeHeaders(headers?: Record) {
+ if (!headers) return undefined;
+ const normalized: Record = {};
+ for (const [key, value] of Object.entries(headers)) {
+ if (!key.trim()) continue;
+ normalized[key] = value;
+ }
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
+}
+
+function toConnectionError(error: unknown): MCPConnectionError {
+ const message =
+ error instanceof Error ? error.message : "Unknown MCP connection error";
+ const lowered = message.toLowerCase();
+ const retryable =
+ lowered.includes("timeout") ||
+ lowered.includes("network") ||
+ lowered.includes("fetch") ||
+ lowered.includes("disconnect") ||
+ lowered.includes("503");
+
+ return {
+ message,
+ retryable,
+ code: error instanceof Error ? error.name : undefined,
+ };
+}
+
+function isUnauthorizedError(error: unknown): boolean {
+ if (error instanceof UnauthorizedError) return true;
+ if (!(error instanceof Error)) return false;
+ const message = error.message.toLowerCase();
+ return (
+ message.includes("unauthorized") ||
+ message.includes("invalid token") ||
+ message.includes("authentication") ||
+ message.includes("401") ||
+ message.includes("403")
+ );
+}
+
+function loadServersFromStorage(): MCPServerConnection[] {
+ const raw = localStorage.getItem(STORAGE_SERVERS_KEY);
+ if (!raw) return [];
+ try {
+ const parsed = JSON.parse(raw) as MCPServerConnection[];
+ return parsed.map((server) => ({
+ ...server,
+ connectionStatus: "disconnected",
+ lastError: undefined,
+ retryCount: server.retryCount ?? 0,
+ }));
+ } catch {
+ return [];
+ }
+}
+
+function loadActiveServerFromStorage(): string | null {
+ return localStorage.getItem(STORAGE_ACTIVE_SERVER_KEY);
+}
+
+export function McpConnectionsProvider({ children }: { children: ReactNode }) {
+ const managerRef = useRef(null);
+ const inFlightConnectionsRef = useRef>>(new Map());
+ const serverConfigsRef = useRef>(new Map());
+
+ if (!managerRef.current) {
+ managerRef.current = new MCPClientManager();
+ }
+
+ const [servers, setServers] = useState(() =>
+ loadServersFromStorage(),
+ );
+ const [activeServerId, setActiveServerId] = useState(() =>
+ loadActiveServerFromStorage(),
+ );
+
+ const upsertServer = useCallback((server: MCPServerConnection) => {
+ setServers((current) => {
+ const index = current.findIndex((entry) => entry.id === server.id);
+ if (index < 0) return [...current, server];
+ const next = [...current];
+ next[index] = server;
+ return next;
+ });
+ }, []);
+
+ const patchServer = useCallback(
+ (serverId: string, patch: Partial) => {
+ setServers((current) =>
+ current.map((server) =>
+ server.id === serverId ? { ...server, ...patch } : server,
+ ),
+ );
+ },
+ [],
+ );
+
+ const validateInput = useCallback((input: ConnectServerInput) => {
+ let url: URL;
+ try {
+ url = new URL(input.url);
+ } catch {
+ throw new Error("Invalid URL");
+ }
+
+ if (url.protocol !== "https:") {
+ throw new Error("Only HTTPS MCP servers are allowed in the web app.");
+ }
+
+ if (isLocalAddress(url.hostname)) {
+ throw new Error("Localhost/private network MCP servers are blocked.");
+ }
+
+ return url;
+ }, []);
+
+ const connectServer = useCallback(
+ async (input: ConnectServerInput) => {
+ const manager = managerRef.current;
+ if (!manager) {
+ throw new Error("MCP manager is not initialized.");
+ }
+
+ const url = validateInput(input);
+ const id = input.id ?? createServerId(input.name.trim(), url.toString());
+
+ const existing = servers.find((server) => server.id === id);
+ const mergedInput: ConnectServerInput = {
+ id,
+ name: input.name,
+ url: url.toString(),
+ transport: input.transport ?? existing?.transport ?? "streamable-http",
+ oauth: input.oauth ?? existing?.oauth,
+ headers: normalizeHeaders(input.headers ?? existing?.headers),
+ sessionId: input.sessionId ?? existing?.sessionId,
+ accessToken: input.accessToken,
+ };
+ serverConfigsRef.current.set(id, mergedInput);
+
+ const existingPromise = inFlightConnectionsRef.current.get(id);
+ if (existingPromise) return existingPromise;
+
+ const baseServer: MCPServerConnection = {
+ id,
+ name: mergedInput.name,
+ url: mergedInput.url,
+ transport: mergedInput.transport ?? "streamable-http",
+ headers: mergedInput.headers,
+ oauth: mergedInput.oauth,
+ sessionId: mergedInput.sessionId,
+ createdAt: existing?.createdAt ?? new Date().toISOString(),
+ connectionStatus: "connecting",
+ lastError: undefined,
+ retryCount: existing?.retryCount ?? 0,
+ initializationInfo: existing?.initializationInfo,
+ serverCapabilities: existing?.serverCapabilities,
+ lastConnectedAt: existing?.lastConnectedAt,
+ };
+ upsertServer(baseServer);
+
+ const doConnect = (async () => {
+ try {
+ await manager.disconnectServer(id);
+
+ const headers = normalizeHeaders(mergedInput.headers) ?? {};
+ if (mergedInput.accessToken && !headers.Authorization) {
+ headers.Authorization = `Bearer ${mergedInput.accessToken}`;
+ }
+
+ const authTokens = getStoredOAuthTokens(id);
+ if (
+ mergedInput.oauth?.enabled &&
+ authTokens &&
+ typeof authTokens.access_token === "string" &&
+ !headers.Authorization
+ ) {
+ headers.Authorization = `Bearer ${authTokens.access_token}`;
+ }
+
+ const config: HttpServerConfig = {
+ url: mergedInput.url,
+ preferSSE: mergedInput.transport === "sse",
+ sessionId: mergedInput.sessionId,
+ requestInit:
+ Object.keys(headers).length > 0
+ ? {
+ headers,
+ }
+ : undefined,
+ };
+
+ if (mergedInput.oauth?.enabled) {
+ const { createMcpOAuthProvider } = await import(
+ "../lib/oauth/mcpOAuthProvider"
+ );
+ config.authProvider = createMcpOAuthProvider({
+ serverId: id,
+ serverName: mergedInput.name,
+ serverUrl: mergedInput.url,
+ oauth: mergedInput.oauth,
+ });
+ }
+
+ console.log("config", config);
+
+ await manager.connectToServer(id, config);
+ await manager.listTools(id).catch(() => undefined);
+
+ const capabilities = manager.getServerCapabilities(id);
+ const initializationInfo = manager.getInitializationInfo(id);
+ const sessionId = (() => {
+ try {
+ return manager.getSessionIdByServer(id);
+ } catch {
+ return undefined;
+ }
+ })();
+
+ patchServer(id, {
+ connectionStatus: "connected",
+ lastError: undefined,
+ retryCount: 0,
+ initializationInfo,
+ serverCapabilities: capabilities,
+ sessionId,
+ lastConnectedAt: new Date().toISOString(),
+ });
+ setActiveServerId(id);
+ } catch (error) {
+ if (mergedInput.oauth?.enabled && isUnauthorizedError(error)) {
+ setPendingOAuthServer(id);
+ const oauthResult = await startOAuth(id, mergedInput);
+ if (oauthResult === "REDIRECT") {
+ patchServer(id, {
+ connectionStatus: "oauth-pending",
+ lastError: undefined,
+ });
+ return;
+ }
+ }
+
+ patchServer(id, {
+ connectionStatus: "error",
+ lastError: toConnectionError(error),
+ retryCount: (existing?.retryCount ?? 0) + 1,
+ });
+ throw error;
+ } finally {
+ inFlightConnectionsRef.current.delete(id);
+ }
+ })();
+
+ inFlightConnectionsRef.current.set(id, doConnect);
+ return doConnect;
+ },
+ [patchServer, servers, upsertServer, validateInput],
+ );
+
+ const disconnectServer = useCallback(async (serverId: string) => {
+ const manager = managerRef.current;
+ if (!manager) return;
+
+ try {
+ await manager.disconnectServer(serverId);
+ } finally {
+ patchServer(serverId, { connectionStatus: "disconnected" });
+ setActiveServerId((current) => (current === serverId ? null : current));
+ }
+ }, [patchServer]);
+
+ const reconnectServer = useCallback(
+ async (serverId: string) => {
+ const config = serverConfigsRef.current.get(serverId);
+ const server = servers.find((entry) => entry.id === serverId);
+ if (!config && !server) {
+ throw new Error("Server config not found for reconnect");
+ }
+
+ await connectServer({
+ id: serverId,
+ name: config?.name ?? server?.name ?? serverId,
+ url: config?.url ?? server?.url ?? "",
+ headers: config?.headers ?? server?.headers,
+ oauth: config?.oauth ?? server?.oauth,
+ transport: config?.transport ?? server?.transport,
+ sessionId: config?.sessionId ?? server?.sessionId,
+ });
+ },
+ [connectServer, servers],
+ );
+
+ const removeServer = useCallback(
+ async (serverId: string) => {
+ await disconnectServer(serverId);
+ serverConfigsRef.current.delete(serverId);
+ setServers((current) => current.filter((server) => server.id !== serverId));
+ setActiveServerId((current) => (current === serverId ? null : current));
+ },
+ [disconnectServer],
+ );
+
+ const refreshServerCapabilities = useCallback(
+ async (serverId: string) => {
+ const manager = managerRef.current;
+ if (!manager) return;
+
+ try {
+ await manager.listTools(serverId);
+ patchServer(serverId, {
+ serverCapabilities: manager.getServerCapabilities(serverId),
+ initializationInfo: manager.getInitializationInfo(serverId),
+ lastError: undefined,
+ retryCount: 0,
+ });
+ } catch (error) {
+ patchServer(serverId, {
+ lastError: toConnectionError(error),
+ });
+ }
+ },
+ [patchServer],
+ );
+
+ useEffect(() => {
+ localStorage.setItem(STORAGE_SERVERS_KEY, JSON.stringify(servers));
+ }, [servers]);
+
+ useEffect(() => {
+ if (activeServerId) {
+ localStorage.setItem(STORAGE_ACTIVE_SERVER_KEY, activeServerId);
+ } else {
+ localStorage.removeItem(STORAGE_ACTIVE_SERVER_KEY);
+ }
+ }, [activeServerId]);
+
+ useEffect(() => {
+ for (const server of servers) {
+ const existing = serverConfigsRef.current.get(server.id);
+ if (existing) continue;
+ serverConfigsRef.current.set(server.id, {
+ id: server.id,
+ name: server.name,
+ url: server.url,
+ transport: server.transport,
+ headers: server.headers,
+ oauth: server.oauth,
+ sessionId: server.sessionId,
+ });
+ }
+ }, [servers]);
+
+ useEffect(() => {
+ const manager = managerRef.current;
+ return () => {
+ if (!manager) return;
+ void manager.disconnectAllServers();
+ };
+ }, []);
+
+ useEffect(() => {
+ const code = new URLSearchParams(window.location.search).get("code");
+ const error = new URLSearchParams(window.location.search).get("error");
+ const pendingServerId = getPendingOAuthServer();
+
+ if (!pendingServerId) return;
+ if (!code && !error) return;
+
+ const pendingConfig = serverConfigsRef.current.get(pendingServerId);
+ if (!pendingConfig) return;
+
+ const cleanUrl = `${window.location.origin}${window.location.pathname}`;
+ const restoreHash = consumeOAuthReturnHash() ?? "#servers";
+
+ const complete = async () => {
+ try {
+ if (error) {
+ patchServer(pendingServerId, {
+ connectionStatus: "error",
+ lastError: {
+ message: `OAuth authorization failed: ${error}`,
+ retryable: true,
+ },
+ });
+ return;
+ }
+
+ if (code) {
+ await finishOAuthCallback(pendingServerId, pendingConfig, code);
+ await reconnectServer(pendingServerId);
+ }
+ } catch (oauthError) {
+ patchServer(pendingServerId, {
+ connectionStatus: "error",
+ lastError: toConnectionError(oauthError),
+ });
+ } finally {
+ window.history.replaceState({}, document.title, `${cleanUrl}${restoreHash}`);
+ }
+ };
+
+ void complete();
+ }, [patchServer, reconnectServer]);
+
+ const contextValue = useMemo(
+ () => ({
+ servers,
+ connectServer,
+ disconnectServer,
+ reconnectServer,
+ removeServer,
+ refreshServerCapabilities,
+ activeServerId,
+ setActiveServerId,
+ getManager: () => managerRef.current,
+ }),
+ [
+ servers,
+ connectServer,
+ disconnectServer,
+ reconnectServer,
+ removeServer,
+ refreshServerCapabilities,
+ activeServerId,
+ ],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/mcpjam-web/src/hooks/mcpConnectionsContext.ts b/mcpjam-web/src/hooks/mcpConnectionsContext.ts
new file mode 100644
index 000000000..44c1e16be
--- /dev/null
+++ b/mcpjam-web/src/hooks/mcpConnectionsContext.ts
@@ -0,0 +1,67 @@
+import { createContext } from "react";
+import type { MCPClientManager } from "@mcpjam/sdk/browser";
+
+export type MCPConnectionStatus =
+ | "connected"
+ | "connecting"
+ | "disconnected"
+ | "oauth-pending"
+ | "error";
+
+export type MCPTransportType = "streamable-http" | "sse";
+
+export interface MCPOAuthConfig {
+ enabled: boolean;
+ scopes?: string[];
+ clientId?: string;
+ clientSecret?: string;
+}
+
+export interface MCPConnectionError {
+ code?: string;
+ message: string;
+ retryable: boolean;
+}
+
+export interface MCPServerConnection {
+ id: string;
+ name: string;
+ url: string;
+ transport: MCPTransportType;
+ headers?: Record;
+ oauth?: MCPOAuthConfig;
+ sessionId?: string;
+ connectionStatus: MCPConnectionStatus;
+ createdAt: string;
+ lastConnectedAt?: string;
+ retryCount: number;
+ initializationInfo?: unknown;
+ serverCapabilities?: unknown;
+ lastError?: MCPConnectionError;
+}
+
+export interface ConnectServerInput {
+ id?: string;
+ name: string;
+ url: string;
+ transport?: MCPTransportType;
+ headers?: Record;
+ oauth?: MCPOAuthConfig;
+ sessionId?: string;
+ accessToken?: string;
+}
+
+export interface McpConnectionsContextValue {
+ servers: MCPServerConnection[];
+ connectServer: (input: ConnectServerInput) => Promise;
+ disconnectServer: (serverId: string) => Promise;
+ reconnectServer: (serverId: string) => Promise;
+ removeServer: (serverId: string) => Promise;
+ refreshServerCapabilities: (serverId: string) => Promise;
+ activeServerId: string | null;
+ setActiveServerId: (id: string | null) => void;
+ getManager: () => MCPClientManager | null;
+}
+
+export const McpConnectionsContext =
+ createContext(null);
diff --git a/mcpjam-web/src/hooks/useMcpConnections.tsx b/mcpjam-web/src/hooks/useMcpConnections.tsx
new file mode 100644
index 000000000..0d6644493
--- /dev/null
+++ b/mcpjam-web/src/hooks/useMcpConnections.tsx
@@ -0,0 +1,22 @@
+import { useContext } from "react";
+import {
+ McpConnectionsContext,
+ type McpConnectionsContextValue,
+} from "./mcpConnectionsContext";
+
+export type {
+ MCPServerConnection,
+ ConnectServerInput,
+ McpConnectionsContextValue,
+ MCPOAuthConfig,
+ MCPConnectionError,
+ MCPTransportType,
+} from "./mcpConnectionsContext";
+
+export function useMcpConnections(): McpConnectionsContextValue {
+ const context = useContext(McpConnectionsContext);
+ if (!context) {
+ throw new Error("useMcpConnections must be used within a McpConnectionsProvider");
+ }
+ return context;
+}
diff --git a/mcpjam-web/src/index.css b/mcpjam-web/src/index.css
new file mode 100644
index 000000000..e7c8e707d
--- /dev/null
+++ b/mcpjam-web/src/index.css
@@ -0,0 +1,419 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+:root {
+ --background: oklch(0.9818 0.0054 95.0986);
+ --foreground: oklch(0.3438 0.0269 95.7226);
+ --card: oklch(0.9818 0.0054 95.0986);
+ --card-foreground: oklch(0.1908 0.002 106.5859);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.2671 0.0196 98.939);
+ --primary: oklch(0.6832 0.1382 38.744);
+ --primary-foreground: oklch(1 0 0);
+ --secondary: oklch(0.9245 0.0138 92.9892);
+ --secondary-foreground: oklch(0.4334 0.0177 98.6048);
+ --accent: oklch(0.9245 0.0138 92.9892);
+ --accent-foreground: oklch(0.2671 0.0196 98.939);
+ --muted: oklch(0.9341 0.0153 90.239);
+ --muted-foreground: oklch(0.6059 0.0075 97.4233);
+ --destructive: oklch(0.55 0.22 27);
+ --destructive-foreground: oklch(0.25 0.1 25);
+ --border: oklch(0.8847 0.0069 97.3627);
+ --input: oklch(0.7621 0.0156 98.3528);
+ --ring: oklch(0.6171 0.1375 39.0427);
+ --sidebar: oklch(0.9663 0.008 98.8792);
+ --sidebar-foreground: oklch(0.359 0.0051 106.6524);
+ --overlay: oklch(0 0 0 / 0.5);
+ --radius: 10px;
+ --shadow-sm: 0 1px 3px hsl(0 0% 0% / 0.08);
+ --shadow-md: 0 8px 24px hsl(0 0% 0% / 0.08);
+ --font-sans:
+ "Inter", "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue",
+ Arial, sans-serif;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-overlay: var(--overlay);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ width: 100%;
+ min-width: 320px;
+ height: 100%;
+ margin: 0;
+}
+
+body {
+ font-family: var(--font-sans);
+ color: var(--foreground);
+ background: var(--background);
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+button,
+input,
+textarea {
+ font: inherit;
+}
+
+.app-shell {
+ display: flex;
+ height: 100%;
+ background: var(--background);
+}
+
+.app-shell__sidebar {
+ width: 240px;
+ flex-shrink: 0;
+ border-right: 1px solid var(--border);
+ background: var(--sidebar);
+ color: var(--sidebar-foreground);
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ padding: 0.75rem;
+}
+
+.app-shell__logo-wrap {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 40px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ background: var(--card);
+}
+
+.app-shell__logo {
+ font-size: 0.92rem;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+}
+
+.sidebar-nav {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.app-shell__main {
+ min-width: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.top-header {
+ min-height: 56px;
+ border-bottom: 1px solid var(--border);
+ background: var(--card);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 0 1rem;
+}
+
+.top-header__brand {
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+.top-header__title {
+ font-size: 0.9rem;
+ color: var(--muted-foreground);
+}
+
+.app-shell__mobile-nav {
+ display: none;
+ border-bottom: 1px solid var(--border);
+ background: var(--card);
+ padding: 0.5rem 1rem;
+}
+
+.compact-nav {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.nav-btn {
+ border: 1px solid transparent;
+ border-radius: calc(var(--radius) - 2px);
+ background: transparent;
+ color: inherit;
+ text-align: left;
+ padding: 0.55rem 0.65rem;
+ cursor: pointer;
+ transition: background-color 120ms ease;
+}
+
+.nav-btn:hover {
+ background: var(--accent);
+}
+
+.nav-btn--active {
+ background: var(--accent);
+ color: var(--accent-foreground);
+}
+
+.app-shell__content {
+ flex: 1;
+ min-height: 0;
+ overflow: auto;
+ padding: 1rem;
+}
+
+.page {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ max-width: 1040px;
+}
+
+.page__header h1 {
+ margin: 0;
+ font-size: 1.6rem;
+}
+
+.page__header p {
+ margin: 0.35rem 0 0;
+ color: var(--muted-foreground);
+}
+
+.card-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.card {
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--card);
+ box-shadow: var(--shadow-sm);
+ padding: 1rem;
+}
+
+.card h2 {
+ margin-top: 0;
+ font-size: 1.02rem;
+}
+
+.card__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+.muted {
+ color: var(--muted-foreground);
+}
+
+.btn {
+ border: 1px solid var(--border);
+ border-radius: calc(var(--radius) - 2px);
+ background: var(--card);
+ color: var(--foreground);
+ padding: 0.45rem 0.7rem;
+ cursor: pointer;
+}
+
+.btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.btn--primary {
+ border-color: transparent;
+ background: var(--primary);
+ color: var(--primary-foreground);
+}
+
+.btn--danger {
+ border-color: transparent;
+ background: #d95050;
+ color: #fff;
+}
+
+.server-form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 0.75rem;
+}
+
+.form-grid label,
+.server-form > label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ font-size: 0.9rem;
+}
+
+.text-input {
+ width: 100%;
+ border: 1px solid var(--border);
+ border-radius: calc(var(--radius) - 2px);
+ padding: 0.52rem 0.6rem;
+ background: var(--card);
+ color: var(--foreground);
+}
+
+.text-input--multiline {
+ min-height: 86px;
+ resize: vertical;
+}
+
+.checkbox-row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.row-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.server-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.server-item {
+ border: 1px solid var(--border);
+ border-radius: calc(var(--radius) - 2px);
+ background: var(--card);
+ padding: 0.75rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+}
+
+.server-item__main p {
+ margin: 0.2rem 0 0;
+ color: var(--muted-foreground);
+ font-size: 0.88rem;
+}
+
+.server-item__actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.server-item__actions--stacked {
+ flex-direction: column;
+ align-items: stretch;
+}
+
+.link-btn {
+ background: none;
+ border: none;
+ color: var(--foreground);
+ padding: 0;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.link-btn--active {
+ color: var(--primary);
+}
+
+.status-badge {
+ border: 1px solid var(--border);
+ border-radius: 999px;
+ background: var(--muted);
+ padding: 0.15rem 0.45rem;
+ font-size: 0.78rem;
+ text-transform: capitalize;
+}
+
+.error-text {
+ margin: 0.2rem 0 0;
+ color: #be2c2c;
+ font-size: 0.84rem;
+}
+
+.chat-placeholder {
+ border: 1px dashed var(--border);
+ border-radius: calc(var(--radius) - 2px);
+ padding: 0.9rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ min-height: 220px;
+}
+
+.chat-placeholder__bubble,
+.chat-placeholder__input {
+ border: 1px solid var(--border);
+ border-radius: calc(var(--radius) - 2px);
+ background: var(--muted);
+ color: var(--muted-foreground);
+ padding: 0.7rem;
+}
+
+.chat-placeholder__bubble {
+ flex: 1;
+}
+
+@media (max-width: 900px) {
+ .app-shell__sidebar {
+ display: none;
+ }
+
+ .app-shell__mobile-nav {
+ display: block;
+ }
+
+ .nav-btn {
+ text-align: center;
+ flex: 1;
+ }
+}
diff --git a/mcpjam-web/src/lib/apis/mcp-export-api.ts b/mcpjam-web/src/lib/apis/mcp-export-api.ts
new file mode 100644
index 000000000..2c9dc3db0
--- /dev/null
+++ b/mcpjam-web/src/lib/apis/mcp-export-api.ts
@@ -0,0 +1,34 @@
+import type { MCPClientManager } from "@mcpjam/sdk/browser";
+
+export async function exportServerApi(
+ manager: MCPClientManager,
+ serverId: string,
+) {
+ const [toolsResult, resourcesResult, promptsResult] = await Promise.all([
+ manager.listTools(serverId),
+ manager.listResources(serverId),
+ manager.listPrompts(serverId),
+ ]);
+
+ return {
+ serverId,
+ exportedAt: new Date().toISOString(),
+ tools: toolsResult.tools.map((tool) => ({
+ name: tool.name,
+ description: tool.description,
+ inputSchema: tool.inputSchema,
+ outputSchema: tool.outputSchema,
+ })),
+ resources: resourcesResult.resources.map((resource) => ({
+ uri: resource.uri,
+ name: resource.name,
+ description: resource.description,
+ mimeType: resource.mimeType,
+ })),
+ prompts: promptsResult.prompts.map((prompt) => ({
+ name: prompt.name,
+ description: prompt.description,
+ arguments: prompt.arguments,
+ })),
+ };
+}
diff --git a/mcpjam-web/src/lib/apis/mcp-tools-api.ts b/mcpjam-web/src/lib/apis/mcp-tools-api.ts
new file mode 100644
index 000000000..5d73fe1af
--- /dev/null
+++ b/mcpjam-web/src/lib/apis/mcp-tools-api.ts
@@ -0,0 +1,5 @@
+import type { ListToolsResult } from "@modelcontextprotocol/sdk/types.js";
+
+export type ListToolsResultWithMetadata = ListToolsResult & {
+ toolsMetadata?: Record>;
+};
diff --git a/mcpjam-web/src/lib/clipboard.ts b/mcpjam-web/src/lib/clipboard.ts
new file mode 100644
index 000000000..753d9c128
--- /dev/null
+++ b/mcpjam-web/src/lib/clipboard.ts
@@ -0,0 +1,20 @@
+export async function copyToClipboard(text: string): Promise {
+ try {
+ await navigator.clipboard.writeText(text);
+ return true;
+ } catch {
+ try {
+ const textarea = document.createElement("textarea");
+ textarea.value = text;
+ textarea.style.position = "fixed";
+ textarea.style.opacity = "0";
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textarea);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+}
diff --git a/mcpjam-web/src/lib/config.ts b/mcpjam-web/src/lib/config.ts
new file mode 100644
index 000000000..4e93c8da6
--- /dev/null
+++ b/mcpjam-web/src/lib/config.ts
@@ -0,0 +1,2 @@
+export const HOSTED_MODE =
+ import.meta.env.VITE_MCPJAM_HOSTED_MODE === "false" ? false : true;
diff --git a/mcpjam-web/src/lib/json-config-parser.ts b/mcpjam-web/src/lib/json-config-parser.ts
new file mode 100644
index 000000000..0ac60bda2
--- /dev/null
+++ b/mcpjam-web/src/lib/json-config-parser.ts
@@ -0,0 +1,234 @@
+import type { ServerFormData } from "@/types/server-types";
+import type { ServerWithName } from "@/types/server-types";
+
+export interface JsonServerConfig {
+ command?: string;
+ args?: string[];
+ env?: Record;
+ type?: "sse";
+ url?: string;
+}
+
+export interface JsonConfig {
+ mcpServers: Record;
+}
+
+function isLocalAddress(hostname: string): boolean {
+ const lowered = hostname.toLowerCase();
+ if (lowered === "localhost") return true;
+ if (lowered.endsWith(".localhost")) return true;
+ if (lowered.endsWith(".local")) return true;
+ if (lowered === "127.0.0.1" || lowered === "::1") return true;
+ if (/^10\./.test(lowered)) return true;
+ if (/^192\.168\./.test(lowered)) return true;
+ if (/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(lowered)) return true;
+ return false;
+}
+
+function validateHostedUrl(urlValue: string, serverName: string): string {
+ let url: URL;
+ try {
+ url = new URL(urlValue);
+ } catch {
+ throw new Error(`Server "${serverName}" has invalid URL format`);
+ }
+
+ if (url.protocol !== "https:") {
+ throw new Error(`Server "${serverName}" must use HTTPS`);
+ }
+
+ if (isLocalAddress(url.hostname)) {
+ throw new Error(
+ `Server "${serverName}" cannot use localhost/private network URLs`,
+ );
+ }
+
+ return url.toString();
+}
+
+/**
+ * Formats ServerWithName objects to JSON config format
+ * @param serversObj - Record of server names to ServerWithName objects
+ * @returns JsonConfig object ready for export
+ */
+export function formatJsonConfig(
+ serversObj: Record,
+): JsonConfig {
+ const mcpServers: Record = {};
+
+ for (const [key, server] of Object.entries(serversObj)) {
+ const { config } = server;
+
+ // Check if it's an SSE type (has URL) or stdio type (has command)
+ if ("url" in config && config.url) {
+ mcpServers[key] = {
+ type: "sse",
+ url: config.url.toString(),
+ };
+ } else if ("command" in config && config.command) {
+ const serverConfig: JsonServerConfig = {
+ command: config.command,
+ args: config.args || [],
+ };
+
+ // Only add env if it exists and has properties
+ if (config.env && Object.keys(config.env).length > 0) {
+ serverConfig.env = config.env;
+ }
+
+ mcpServers[key] = serverConfig;
+ } else {
+ console.warn(`Skipping server "${key}": missing required url or command`);
+ }
+ }
+
+ return { mcpServers };
+}
+
+/**
+ * Parses a JSON config file and converts it to ServerFormData array
+ * @param jsonContent - The JSON string content
+ * @returns Array of ServerFormData objects
+ */
+export function parseJsonConfig(jsonContent: string): ServerFormData[] {
+ try {
+ const config: JsonConfig = JSON.parse(jsonContent);
+
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
+ throw new Error(
+ 'Invalid JSON config: missing or invalid "mcpServers" property',
+ );
+ }
+
+ const servers: ServerFormData[] = [];
+
+ for (const [serverName, serverConfig] of Object.entries(
+ config.mcpServers,
+ )) {
+ if (!serverConfig || typeof serverConfig !== "object") {
+ throw new Error(`Invalid server config for "${serverName}"`);
+ }
+
+ if (serverConfig.command) {
+ throw new Error(
+ `Server "${serverName}" is STDIO and unsupported in mcpjam-web`,
+ );
+ }
+
+ if (!serverConfig.url || typeof serverConfig.url !== "string") {
+ throw new Error(`Server "${serverName}" must include an HTTPS "url"`);
+ }
+
+ servers.push({
+ name: serverName,
+ type: "http",
+ url: validateHostedUrl(serverConfig.url, serverName),
+ headers: {},
+ env: {},
+ useOAuth: false,
+ });
+ }
+
+ return servers;
+ } catch (error) {
+ if (error instanceof SyntaxError) {
+ throw new Error("Invalid JSON format: " + error.message);
+ }
+ throw error;
+ }
+}
+
+/**
+ * Validates a JSON config file without parsing it
+ * @param jsonContent - The JSON string content
+ * @returns Validation result with success status and error message
+ */
+export function validateJsonConfig(jsonContent: string): {
+ success: boolean;
+ error?: string;
+} {
+ try {
+ const config = JSON.parse(jsonContent);
+
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
+ return {
+ success: false,
+ error: 'Missing or invalid "mcpServers" property',
+ };
+ }
+
+ const serverNames = Object.keys(config.mcpServers);
+ if (serverNames.length === 0) {
+ return {
+ success: false,
+ error: 'No servers found in "mcpServers" object',
+ };
+ }
+
+ // Validate each server config
+ for (const [serverName, serverConfig] of Object.entries(
+ config.mcpServers,
+ )) {
+ if (!serverConfig || typeof serverConfig !== "object") {
+ return {
+ success: false,
+ error: `Invalid server config for "${serverName}"`,
+ };
+ }
+
+ const configObj = serverConfig as JsonServerConfig;
+ const hasUrl = configObj.url && typeof configObj.url === "string";
+ if (configObj.command) {
+ return {
+ success: false,
+ error: `Server "${serverName}" uses STDIO and is unsupported in mcpjam-web`,
+ };
+ }
+
+ if (!hasUrl) {
+ return {
+ success: false,
+ error: `Server "${serverName}" must include an HTTPS "url"`,
+ };
+ }
+
+ try {
+ validateHostedUrl(configObj.url!, serverName);
+ } catch (error) {
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "Invalid URL",
+ };
+ }
+ }
+
+ return { success: true };
+ } catch (error) {
+ if (error instanceof SyntaxError) {
+ return { success: false, error: "Invalid JSON format: " + error.message };
+ }
+ return {
+ success: false,
+ error: "Unknown error: " + (error as Error).message,
+ };
+ }
+}
+
+/**
+ * Downloads an object as a formatted JSON file.
+ * @param filename - Output filename
+ * @param data - Serializable JSON data
+ */
+export function downloadJsonFile(filename: string, data: unknown): void {
+ const blob = new Blob([JSON.stringify(data, null, 2)], {
+ type: "application/json;charset=utf-8",
+ });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+}
diff --git a/mcpjam-web/src/lib/mcp-ui/mcp-apps-utils.ts b/mcpjam-web/src/lib/mcp-ui/mcp-apps-utils.ts
new file mode 100644
index 000000000..e723891b2
--- /dev/null
+++ b/mcpjam-web/src/lib/mcp-ui/mcp-apps-utils.ts
@@ -0,0 +1,143 @@
+import { isUIResource } from "@mcp-ui/client";
+import type { ListToolsResultWithMetadata } from "@/lib/apis/mcp-tools-api";
+import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
+import type { Tool } from "@modelcontextprotocol/sdk/types.js";
+
+export const UIType = {
+ MCP_APPS: "mcp-apps",
+ OPENAI_SDK: "openai-sdk",
+ OPENAI_SDK_AND_MCP_APPS: "openai-sdk-and-mcp-apps",
+ MCP_UI: "mcp-ui",
+} as const;
+
+export type UIType = (typeof UIType)[keyof typeof UIType];
+
+export function detectUiTypeFromTool(tool: Tool): UIType | null {
+ const toolMeta = tool._meta;
+ if (!toolMeta) return null;
+ return detectUIType(toolMeta, undefined);
+}
+
+export function detectUIType(
+ toolMeta: Record | undefined,
+ toolResult: unknown,
+): UIType | null {
+ // 1. OpenAI SDK and MCP Apps: Check for openai/outputTemplate and ui.resourceUri metadata
+ if (
+ toolMeta?.["openai/outputTemplate"] &&
+ getToolUiResourceUri({ _meta: toolMeta })
+ ) {
+ return UIType.OPENAI_SDK_AND_MCP_APPS;
+ }
+
+ // 2. OpenAI SDK: Check for openai/outputTemplate metadata
+ if (toolMeta?.["openai/outputTemplate"]) {
+ return UIType.OPENAI_SDK;
+ }
+
+ // 3. MCP Apps (SEP-1865): Check for ui.resourceUri metadata
+ if (getToolUiResourceUri({ _meta: toolMeta })) {
+ return UIType.MCP_APPS;
+ }
+
+ // 4. MCP-UI: Check for inline ui:// resource in result
+ const directResource = (toolResult as { resource?: { uri?: string } })
+ ?.resource;
+ if (directResource?.uri?.startsWith("ui://")) {
+ return UIType.MCP_UI;
+ }
+
+ const content = (toolResult as { content?: unknown[] })?.content;
+ if (Array.isArray(content)) {
+ for (const item of content) {
+ // isUIResource is a type guard, cast to any for runtime type check
+ if (isUIResource(item as any)) {
+ return UIType.MCP_UI;
+ }
+ }
+ }
+ return null;
+}
+
+export function getUIResourceUri(
+ uiType: UIType | null,
+ toolMeta: Record | undefined,
+): string | null {
+ switch (uiType) {
+ case UIType.MCP_APPS:
+ case UIType.OPENAI_SDK_AND_MCP_APPS:
+ return getToolUiResourceUri({ _meta: toolMeta }) ?? null;
+ case UIType.OPENAI_SDK:
+ return (toolMeta?.["openai/outputTemplate"] as string) ?? null;
+ default:
+ return null;
+ }
+}
+
+export function isMCPApp(
+ toolsData?: ListToolsResultWithMetadata | null,
+): boolean {
+ const metadata = toolsData?.toolsMetadata;
+ if (!metadata) return false;
+
+ return Object.values(metadata).some(
+ (meta) => detectUIType(meta, undefined) === UIType.MCP_APPS,
+ );
+}
+
+export function isOpenAIApp(
+ toolsData?: ListToolsResultWithMetadata | null,
+): boolean {
+ const metadata = toolsData?.toolsMetadata;
+ if (!metadata) return false;
+
+ return Object.values(metadata).some(
+ (meta) => detectUIType(meta, undefined) === UIType.OPENAI_SDK,
+ );
+}
+
+export function isOpenAIAppAndMCPApp(
+ toolsData?: ListToolsResultWithMetadata | null,
+): boolean {
+ const metadata = toolsData?.toolsMetadata;
+ if (!metadata) return false;
+
+ return Object.values(metadata).some(
+ (meta) => detectUIType(meta, undefined) === UIType.OPENAI_SDK_AND_MCP_APPS,
+ );
+}
+
+/**
+ * Get the visibility array from tool metadata.
+ * Default: ["model", "app"] if not specified (per SEP-1865)
+ */
+export function getToolVisibility(
+ toolMeta: Record | undefined,
+): Array<"model" | "app"> {
+ const ui = toolMeta?.ui as
+ | { visibility?: Array<"model" | "app"> }
+ | undefined;
+ return ui?.visibility ?? ["model", "app"];
+}
+
+/**
+ * Check if tool is visible to model only (not callable by apps).
+ * True when visibility is exactly ["model"]
+ */
+export function isVisibleToModelOnly(
+ toolMeta: Record | undefined,
+): boolean {
+ const visibility = getToolVisibility(toolMeta);
+ return visibility.length === 1 && visibility[0] === "model";
+}
+
+/**
+ * Check if tool is visible to app only (hidden from model).
+ * True when visibility is exactly ["app"]
+ */
+export function isVisibleToAppOnly(
+ toolMeta: Record | undefined,
+): boolean {
+ const visibility = getToolVisibility(toolMeta);
+ return visibility.length === 1 && visibility[0] === "app";
+}
diff --git a/mcpjam-web/src/lib/oauth/jwt-decoder.ts b/mcpjam-web/src/lib/oauth/jwt-decoder.ts
new file mode 100644
index 000000000..4070a7892
--- /dev/null
+++ b/mcpjam-web/src/lib/oauth/jwt-decoder.ts
@@ -0,0 +1,42 @@
+/**
+ * Safely decode a JWT token without verification
+ * Returns the decoded payload or null if invalid
+ */
+export function decodeJWT(token: string): Record | null {
+ try {
+ // JWT format: header.payload.signature
+ const parts = token.split(".");
+ if (parts.length !== 3) {
+ return null;
+ }
+
+ // Decode the payload (second part)
+ const payload = parts[1];
+
+ // Handle base64url decoding (replace - with + and _ with /)
+ const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
+
+ // Add padding if needed
+ const paddedBase64 = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
+
+ // Decode base64 to string
+ const decoded = atob(paddedBase64);
+
+ // Parse JSON
+ return JSON.parse(decoded);
+ } catch (error) {
+ console.error("Failed to decode JWT:", error);
+ return null;
+ }
+}
+
+/**
+ * Format timestamp to readable date
+ */
+export function formatJWTTimestamp(timestamp: number): string {
+ try {
+ return new Date(timestamp * 1000).toLocaleString();
+ } catch {
+ return String(timestamp);
+ }
+}
diff --git a/mcpjam-web/src/lib/oauth/mcp-oauth.ts b/mcpjam-web/src/lib/oauth/mcp-oauth.ts
new file mode 100644
index 000000000..7269e3314
--- /dev/null
+++ b/mcpjam-web/src/lib/oauth/mcp-oauth.ts
@@ -0,0 +1,21 @@
+import type { OAuthTokens } from "@/types/server-types";
+
+const TOKENS_KEY_PREFIX = "mcpjam-web-oauth-tokens-";
+
+function tokensKey(serverId: string) {
+ return `${TOKENS_KEY_PREFIX}${serverId}`;
+}
+
+export function getStoredTokens(serverId: string): OAuthTokens | null {
+ const raw = localStorage.getItem(tokensKey(serverId));
+ if (!raw) return null;
+ try {
+ return JSON.parse(raw) as OAuthTokens;
+ } catch {
+ return null;
+ }
+}
+
+export function hasOAuthConfig(serverId: string): boolean {
+ return getStoredTokens(serverId) != null;
+}
diff --git a/mcpjam-web/src/lib/oauth/mcpOAuthProvider.ts b/mcpjam-web/src/lib/oauth/mcpOAuthProvider.ts
new file mode 100644
index 000000000..e4d715ea9
--- /dev/null
+++ b/mcpjam-web/src/lib/oauth/mcpOAuthProvider.ts
@@ -0,0 +1,159 @@
+import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
+import type { OAuthClientInformationMixed } from "@modelcontextprotocol/sdk/shared/auth.js";
+import type { MCPOAuthConfig } from "../../hooks/mcpConnectionsContext";
+
+const CLIENT_INFO_KEY_PREFIX = "mcpjam-web-oauth-client-";
+const TOKENS_KEY_PREFIX = "mcpjam-web-oauth-tokens-";
+const VERIFIER_KEY_PREFIX = "mcpjam-web-oauth-verifier-";
+const PENDING_KEY = "mcpjam-web-oauth-pending-server-id";
+const RETURN_HASH_KEY = "mcpjam-web-oauth-return-hash";
+
+function clientInfoKey(serverId: string) {
+ return `${CLIENT_INFO_KEY_PREFIX}${serverId}`;
+}
+
+function tokensKey(serverId: string) {
+ return `${TOKENS_KEY_PREFIX}${serverId}`;
+}
+
+function verifierKey(serverId: string) {
+ return `${VERIFIER_KEY_PREFIX}${serverId}`;
+}
+
+function getFromStorage(key: string): T | undefined {
+ const raw = localStorage.getItem(key);
+ if (!raw) return undefined;
+ try {
+ return JSON.parse(raw) as T;
+ } catch {
+ return undefined;
+ }
+}
+
+function setInStorage(key: string, value: unknown) {
+ localStorage.setItem(key, JSON.stringify(value));
+}
+
+export function setPendingOAuthServer(serverId: string) {
+ localStorage.setItem(PENDING_KEY, serverId);
+}
+
+export function getPendingOAuthServer(): string | null {
+ return localStorage.getItem(PENDING_KEY);
+}
+
+export function clearPendingOAuthServer() {
+ localStorage.removeItem(PENDING_KEY);
+}
+
+export function setOAuthReturnHash(hash: string) {
+ localStorage.setItem(RETURN_HASH_KEY, hash);
+}
+
+export function consumeOAuthReturnHash(): string | null {
+ const hash = localStorage.getItem(RETURN_HASH_KEY);
+ if (hash) {
+ localStorage.removeItem(RETURN_HASH_KEY);
+ }
+ return hash;
+}
+
+export function getStoredOAuthTokens(serverId: string) {
+ return getFromStorage>(tokensKey(serverId));
+}
+
+interface ProviderConfig {
+ serverId: string;
+ serverName: string;
+ serverUrl: string;
+ oauth?: MCPOAuthConfig;
+}
+
+export function createMcpOAuthProvider({
+ serverId,
+ serverName,
+ serverUrl,
+ oauth,
+}: ProviderConfig): OAuthClientProvider {
+ const redirectUri = `${window.location.origin}${window.location.pathname}`;
+
+ return {
+ get redirectUrl() {
+ return redirectUri;
+ },
+ get clientMetadata() {
+ return {
+ client_name: `MCPJam Web - ${serverName}`,
+ client_uri: "https://mcpjam.com",
+ redirect_uris: [redirectUri],
+ grant_types: ["authorization_code", "refresh_token"],
+ response_types: ["code"],
+ token_endpoint_auth_method: oauth?.clientSecret ? "client_secret_post" : "none",
+ };
+ },
+ state() {
+ return crypto.randomUUID();
+ },
+ clientInformation() {
+ const stored = getFromStorage>(clientInfoKey(serverId));
+ if (!stored) {
+ const preset: Record = {
+ redirect_uris: [redirectUri],
+ };
+ if (oauth?.clientId) {
+ preset.client_id = oauth.clientId;
+ }
+ if (oauth?.clientSecret) {
+ preset.client_secret = oauth.clientSecret;
+ }
+ return oauth?.clientId
+ ? (preset as OAuthClientInformationMixed)
+ : undefined;
+ }
+
+ const next = { ...stored };
+ if (!next.redirect_uris) {
+ next.redirect_uris = [redirectUri];
+ }
+ if (oauth?.clientId) next.client_id = oauth.clientId;
+ if (oauth?.clientSecret) next.client_secret = oauth.clientSecret;
+ return next as OAuthClientInformationMixed;
+ },
+ saveClientInformation(clientInformation) {
+ setInStorage(clientInfoKey(serverId), clientInformation);
+ },
+ tokens() {
+ return getFromStorage(tokensKey(serverId));
+ },
+ saveTokens(tokens) {
+ setInStorage(tokensKey(serverId), tokens);
+ },
+ redirectToAuthorization(authorizationUrl) {
+ setPendingOAuthServer(serverId);
+ setOAuthReturnHash(window.location.hash || "#servers");
+ localStorage.setItem(`mcpjam-web-oauth-server-url-${serverId}`, serverUrl);
+ window.location.href = authorizationUrl.toString();
+ },
+ saveCodeVerifier(codeVerifier) {
+ localStorage.setItem(verifierKey(serverId), codeVerifier);
+ },
+ codeVerifier() {
+ const verifier = localStorage.getItem(verifierKey(serverId));
+ if (!verifier) {
+ throw new Error("OAuth code verifier not found");
+ }
+ return verifier;
+ },
+ invalidateCredentials(scope) {
+ if (scope === "all" || scope === "client") {
+ localStorage.removeItem(clientInfoKey(serverId));
+ }
+ if (scope === "all" || scope === "tokens") {
+ localStorage.removeItem(tokensKey(serverId));
+ }
+ if (scope === "all" || scope === "verifier") {
+ localStorage.removeItem(verifierKey(serverId));
+ }
+ },
+ };
+}
diff --git a/mcpjam-web/src/lib/oauth/oauthFlow.ts b/mcpjam-web/src/lib/oauth/oauthFlow.ts
new file mode 100644
index 000000000..d61a2925e
--- /dev/null
+++ b/mcpjam-web/src/lib/oauth/oauthFlow.ts
@@ -0,0 +1,51 @@
+import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
+import type { ConnectServerInput } from "../../hooks/mcpConnectionsContext";
+import {
+ clearPendingOAuthServer,
+ createMcpOAuthProvider,
+} from "./mcpOAuthProvider";
+
+function toScope(config: ConnectServerInput): string | undefined {
+ const scopes = config.oauth?.scopes;
+ if (!scopes || scopes.length === 0) return undefined;
+ return scopes.join(" ");
+}
+
+export async function startOAuth(
+ serverId: string,
+ config: ConnectServerInput,
+): Promise<"AUTHORIZED" | "REDIRECT"> {
+ const provider = createMcpOAuthProvider({
+ serverId,
+ serverName: config.name,
+ serverUrl: config.url,
+ oauth: config.oauth,
+ });
+
+ return auth(provider, {
+ serverUrl: config.url,
+ scope: toScope(config),
+ });
+}
+
+export async function finishOAuthCallback(
+ serverId: string,
+ config: ConnectServerInput,
+ code: string,
+): Promise<"AUTHORIZED" | "REDIRECT"> {
+ const provider = createMcpOAuthProvider({
+ serverId,
+ serverName: config.name,
+ serverUrl: config.url,
+ oauth: config.oauth,
+ });
+
+ const result = await auth(provider, {
+ serverUrl: config.url,
+ authorizationCode: code,
+ scope: toScope(config),
+ });
+
+ clearPendingOAuthServer();
+ return result;
+}
diff --git a/mcpjam-web/src/lib/utils.ts b/mcpjam-web/src/lib/utils.ts
new file mode 100644
index 000000000..a5ef19350
--- /dev/null
+++ b/mcpjam-web/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/mcpjam-web/src/main.tsx b/mcpjam-web/src/main.tsx
new file mode 100644
index 000000000..d1f13cd6f
--- /dev/null
+++ b/mcpjam-web/src/main.tsx
@@ -0,0 +1,15 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+import { McpConnectionsProvider } from "./hooks/McpConnectionsProvider";
+import { Toaster } from "./components/ui/sonner";
+import "./index.css";
+
+createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+ ,
+);
diff --git a/mcpjam-web/src/pages/PlaygroundPage.tsx b/mcpjam-web/src/pages/PlaygroundPage.tsx
new file mode 100644
index 000000000..5cb9af10a
--- /dev/null
+++ b/mcpjam-web/src/pages/PlaygroundPage.tsx
@@ -0,0 +1,37 @@
+import { useMcpConnections } from "../hooks/useMcpConnections";
+
+export function PlaygroundPage() {
+ const { servers, activeServerId } = useMcpConnections();
+ const activeServer = servers.find((server) => server.id === activeServerId) ?? null;
+
+ return (
+
+
+
LLM Playground
+
Chat UI scaffold for MCP-connected model interactions.
+
+
+
+
+ Session Context
+
+ {activeServer
+ ? `Active server: ${activeServer.name}`
+ : "No active server selected. Choose one from Server Connections."}
+
+
+
+
+ Chat Area (Placeholder)
+
+ Streaming, model selection, and MCP tool invocation will be wired in phase 2.
+
+
+
Assistant output will stream here.
+
Message composer goes here.
+
+
+
+
+ );
+}
diff --git a/mcpjam-web/src/pages/ServerConnectionsPage.tsx b/mcpjam-web/src/pages/ServerConnectionsPage.tsx
new file mode 100644
index 000000000..fcc4f0a57
--- /dev/null
+++ b/mcpjam-web/src/pages/ServerConnectionsPage.tsx
@@ -0,0 +1,454 @@
+import { useEffect, useMemo, useState } from "react";
+import { Plus, FileText } from "lucide-react";
+import { toast } from "sonner";
+import {
+ DndContext,
+ closestCenter,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ DragOverlay,
+ type DragStartEvent,
+ type DragEndEvent,
+} from "@dnd-kit/core";
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ useSortable,
+ rectSortingStrategy,
+} from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import type { MCPClientManager } from "@mcpjam/sdk/browser";
+import { Card } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { MCPIcon } from "@/components/ui/mcp-icon";
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/components/ui/hover-card";
+import { AddServerModal } from "@/components/connection/AddServerModal";
+import { EditServerModal } from "@/components/connection/EditServerModal";
+import { JsonImportModal } from "@/components/connection/JsonImportModal";
+import { ServerConnectionCard } from "@/components/connection/ServerConnectionCard";
+import type { ServerFormData, ServerWithName } from "@/types/server-types";
+import { useMcpConnections } from "@/hooks/useMcpConnections";
+import { getStoredTokens } from "@/lib/oauth/mcp-oauth";
+
+const ORDER_STORAGE_KEY = "mcpjam-web-server-order";
+
+function loadServerOrder(): string[] | undefined {
+ try {
+ const raw = localStorage.getItem(ORDER_STORAGE_KEY);
+ return raw ? (JSON.parse(raw) as string[]) : undefined;
+ } catch {
+ return undefined;
+ }
+}
+
+function saveServerOrder(orderedIds: string[]): void {
+ try {
+ localStorage.setItem(ORDER_STORAGE_KEY, JSON.stringify(orderedIds));
+ } catch {
+ // ignore
+ }
+}
+
+function mapStatus(status: string): ServerWithName["connectionStatus"] {
+ switch (status) {
+ case "connected":
+ return "connected";
+ case "connecting":
+ return "connecting";
+ case "oauth-pending":
+ return "oauth-flow";
+ case "error":
+ return "failed";
+ default:
+ return "disconnected";
+ }
+}
+
+function SortableServerCard({
+ id,
+ server,
+ onDisconnect,
+ onReconnect,
+ onEdit,
+ onRemove,
+ manager,
+}: {
+ id: string;
+ server: ServerWithName;
+ manager: MCPClientManager | null;
+ onDisconnect: (id: string) => void;
+ onReconnect: (id: string, opts?: { forceOAuthFlow?: boolean }) => void;
+ onEdit: (server: ServerWithName) => void;
+ onRemove: (id: string) => void;
+}) {
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ isDragging,
+ } = useSortable({ id });
+
+ const style = {
+ transform: CSS.Translate.toString(transform),
+ transition,
+ opacity: isDragging ? 0 : 1,
+ };
+
+ return (
+
+
+
+ );
+}
+
+export function ServerConnectionsPage() {
+ const {
+ servers,
+ connectServer,
+ disconnectServer,
+ reconnectServer,
+ removeServer,
+ getManager,
+ } = useMcpConnections();
+
+ const manager = getManager();
+ const [isAddingServer, setIsAddingServer] = useState(false);
+ const [isImportingJson, setIsImportingJson] = useState(false);
+ const [isEditingServer, setIsEditingServer] = useState(false);
+ const [serverToEdit, setServerToEdit] = useState(null);
+ const [isActionMenuOpen, setIsActionMenuOpen] = useState(false);
+ const [activeId, setActiveId] = useState(null);
+
+ const serversById = useMemo>(() => {
+ return Object.fromEntries(
+ servers.map((server) => {
+ const urlConfig = {
+ url: server.url,
+ preferSSE: server.transport === "sse",
+ timeout: 10000,
+ requestInit: server.headers ? { headers: server.headers } : undefined,
+ sessionId: server.sessionId,
+ };
+
+ const mapped: ServerWithName = {
+ id: server.id,
+ name: server.name,
+ config: urlConfig,
+ oauthTokens: getStoredTokens(server.id) ?? undefined,
+ initializationInfo: server.initializationInfo as
+ | ServerWithName["initializationInfo"]
+ | undefined,
+ lastConnectionTime: new Date(server.lastConnectedAt ?? server.createdAt),
+ connectionStatus: mapStatus(server.connectionStatus),
+ retryCount: server.retryCount,
+ lastError: server.lastError?.message,
+ useOAuth: server.oauth?.enabled,
+ };
+
+ return [server.id, mapped];
+ }),
+ );
+ }, [servers]);
+
+ const allIds = useMemo(() => Object.keys(serversById), [serversById]);
+
+ const [orderedServerIds, setOrderedServerIds] = useState(() => {
+ const saved = loadServerOrder();
+ if (saved && saved.length > 0) {
+ const existing = saved.filter((id) => allIds.includes(id));
+ const added = allIds.filter((id) => !existing.includes(id));
+ return [...existing, ...added];
+ }
+ return allIds;
+ });
+
+ useEffect(() => {
+ setOrderedServerIds((prev) => {
+ const saved = loadServerOrder();
+ const base = saved && saved.length > 0 ? saved : prev;
+ const existing = base.filter((id) => allIds.includes(id));
+ const added = allIds.filter((id) => !existing.includes(id));
+ return [...existing, ...added];
+ });
+ }, [allIds]);
+
+ const sensors = useSensors(
+ useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ }),
+ );
+
+ const handleDragStart = (event: DragStartEvent) => {
+ setActiveId(event.active.id as string);
+ };
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event;
+ if (over && active.id !== over.id) {
+ const oldIndex = orderedServerIds.findIndex((id) => id === active.id);
+ const newIndex = orderedServerIds.findIndex((id) => id === over.id);
+ if (oldIndex !== -1 && newIndex !== -1) {
+ const newOrder = arrayMove(orderedServerIds, oldIndex, newIndex);
+ setOrderedServerIds(newOrder);
+ saveServerOrder(newOrder);
+ }
+ }
+ setActiveId(null);
+ };
+
+ const activeServer = activeId ? serversById[activeId] : null;
+
+ const handleConnect = async (formData: ServerFormData) => {
+ if (formData.type !== "http" || !formData.url) {
+ throw new Error("Only remote HTTPS HTTP MCP servers are supported.");
+ }
+ if (Object.values(serversById).some((server) => server.name === formData.name)) {
+ throw new Error(`A server named "${formData.name}" already exists.`);
+ }
+
+ await connectServer({
+ name: formData.name,
+ url: formData.url,
+ transport: "streamable-http",
+ headers: formData.headers,
+ oauth: {
+ enabled: formData.useOAuth === true,
+ scopes: formData.oauthScopes,
+ clientId: formData.clientId,
+ clientSecret: formData.clientSecret,
+ },
+ });
+ };
+
+ const handleUpdate = async (
+ originalServerId: string,
+ formData: ServerFormData,
+ skipAutoConnect?: boolean,
+ ) => {
+ const current = servers.find((s) => s.id === originalServerId);
+ if (!current) return;
+ if (
+ Object.values(serversById).some(
+ (server) =>
+ server.id !== originalServerId && server.name === formData.name.trim(),
+ )
+ ) {
+ throw new Error(`A server named "${formData.name}" already exists.`);
+ }
+
+ if (skipAutoConnect) {
+ await disconnectServer(originalServerId);
+ return;
+ }
+
+ await connectServer({
+ id: originalServerId,
+ name: formData.name,
+ url: formData.url ?? current.url,
+ transport: "streamable-http",
+ headers: formData.headers,
+ oauth: {
+ enabled: formData.useOAuth === true,
+ scopes: formData.oauthScopes,
+ clientId: formData.clientId,
+ clientSecret: formData.clientSecret,
+ },
+ });
+ };
+
+ const handleEditServer = (server: ServerWithName) => {
+ setServerToEdit(server);
+ setIsEditingServer(true);
+ };
+
+ const handleCloseEditModal = () => {
+ setIsEditingServer(false);
+ setServerToEdit(null);
+ };
+
+ const handleJsonImport = (importedServers: ServerFormData[]) => {
+ importedServers.forEach((server) => {
+ void handleConnect(server).catch((error) => {
+ toast.error(error instanceof Error ? error.message : "Connection failed");
+ });
+ });
+ };
+
+ const renderServerActionsMenu = () => (
+
+
+ {
+ setIsAddingServer(true);
+ setIsActionMenuOpen(false);
+ }}
+ className="cursor-pointer"
+ >
+
+ Add Server
+
+
+
+
+
{
+ setIsAddingServer(true);
+ setIsActionMenuOpen(false);
+ }}
+ >
+
+ Add manually
+
+
{
+ setIsImportingJson(true);
+ setIsActionMenuOpen(false);
+ }}
+ >
+
+ Import JSON
+
+
+
+
+ );
+
+ const connectedCount = Object.values(serversById).filter(
+ (s) => s.connectionStatus === "connected",
+ ).length;
+
+ return (
+
+
+
+
+
Server Connections
+
+ {connectedCount} connected / {allIds.length} total
+
+
+
{renderServerActionsMenu()}
+
+
+ {allIds.length > 0 ? (
+
setActiveId(null)}
+ >
+
+
+ {orderedServerIds.map((id) => {
+ const server = serversById[id];
+ if (!server) return null;
+ return (
+ {
+ void disconnectServer(serverId);
+ }}
+ onReconnect={(serverId, _opts) => {
+ void reconnectServer(serverId);
+ }}
+ onEdit={handleEditServer}
+ onRemove={(serverId) => {
+ void removeServer(serverId);
+ }}
+ />
+ );
+ })}
+
+
+
+ {activeServer ? (
+
+ {}}
+ onReconnect={() => {}}
+ onEdit={() => {}}
+ onRemove={() => {}}
+ />
+
+ ) : null}
+
+
+ ) : (
+
+
+
+
No servers configured
+
+ Get started by connecting to your first MCP server
+
+
setIsAddingServer(true)}
+ className="mt-4 cursor-pointer"
+ >
+
+ Add Your First Server
+
+
+
+ )}
+
+
+
setIsAddingServer(false)}
+ onSubmit={handleConnect}
+ />
+
+ {serverToEdit && (
+
+ handleUpdate(originalServerId, formData, skipAutoConnect)
+ }
+ server={serverToEdit}
+ existingServerNames={Object.values(serversById).map((s) => s.name)}
+ />
+ )}
+
+ setIsImportingJson(false)}
+ onImport={handleJsonImport}
+ />
+
+ );
+}
diff --git a/mcpjam-web/src/types/server-types.ts b/mcpjam-web/src/types/server-types.ts
new file mode 100644
index 000000000..538a36ae1
--- /dev/null
+++ b/mcpjam-web/src/types/server-types.ts
@@ -0,0 +1,62 @@
+import type { MCPServerConfig } from "@mcpjam/sdk/browser";
+
+export interface ServerFormData {
+ name: string;
+ type: "stdio" | "http";
+ command?: string;
+ args?: string[];
+ url?: string;
+ headers?: Record;
+ env?: Record;
+ useOAuth?: boolean;
+ oauthScopes?: string[];
+ clientId?: string;
+ clientSecret?: string;
+ requestTimeout?: number;
+}
+
+export interface OAuthTokens {
+ access_token: string;
+ refresh_token?: string;
+ token_type?: string;
+ expires_in?: number;
+ scope?: string;
+ id_token?: string;
+ client_id?: string;
+ client_secret?: string;
+}
+
+export type ConnectionStatus =
+ | "connected"
+ | "connecting"
+ | "failed"
+ | "disconnected"
+ | "oauth-flow";
+
+export interface InitializationInfo {
+ protocolVersion?: string;
+ transport?: string;
+ serverCapabilities?: Record;
+ serverVersion?: {
+ name: string;
+ version: string;
+ title?: string;
+ websiteUrl?: string;
+ icons?: Array<{ src: string; mimeType?: string; sizes?: string[] }>;
+ };
+ instructions?: string;
+ clientCapabilities?: Record;
+}
+
+export interface ServerWithName {
+ id: string;
+ name: string;
+ config: MCPServerConfig;
+ oauthTokens?: OAuthTokens;
+ initializationInfo?: InitializationInfo;
+ lastConnectionTime: Date;
+ connectionStatus: ConnectionStatus;
+ retryCount: number;
+ lastError?: string;
+ useOAuth?: boolean;
+}
diff --git a/mcpjam-web/tsconfig.app.json b/mcpjam-web/tsconfig.app.json
new file mode 100644
index 000000000..cb3649e80
--- /dev/null
+++ b/mcpjam-web/tsconfig.app.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"],
+ "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/**/__tests__/**"]
+}
diff --git a/mcpjam-web/tsconfig.json b/mcpjam-web/tsconfig.json
new file mode 100644
index 000000000..1ffef600d
--- /dev/null
+++ b/mcpjam-web/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/mcpjam-web/tsconfig.node.json b/mcpjam-web/tsconfig.node.json
new file mode 100644
index 000000000..8a67f62f4
--- /dev/null
+++ b/mcpjam-web/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/mcpjam-web/vite.config.ts b/mcpjam-web/vite.config.ts
new file mode 100644
index 000000000..d95d65968
--- /dev/null
+++ b/mcpjam-web/vite.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+import path from "path";
+import { fileURLToPath } from "url";
+
+const rootDir = fileURLToPath(new URL(".", import.meta.url));
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ "@": path.resolve(rootDir, "./src"),
+ },
+ },
+});
diff --git a/sdk/package-lock.json b/sdk/package-lock.json
index 853cb9c51..35c548d1c 100644
--- a/sdk/package-lock.json
+++ b/sdk/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@mcpjam/sdk",
- "version": "0.8.4",
+ "version": "0.8.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mcpjam/sdk",
- "version": "0.8.4",
+ "version": "0.8.5",
"license": "MIT",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.17",
@@ -6601,9 +6601,9 @@
"license": "MIT"
},
"node_modules/qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
diff --git a/sdk/package.json b/sdk/package.json
index ec901fce8..dce15b024 100644
--- a/sdk/package.json
+++ b/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@mcpjam/sdk",
- "version": "0.8.4",
+ "version": "0.8.5",
"description": "MCP server unit testing, end to end (e2e) testing, and server evals",
"main": "dist/index.js",
"module": "dist/index.mjs",
@@ -10,6 +10,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
+ },
+ "./browser": {
+ "types": "./dist/browser.d.ts",
+ "import": "./dist/browser.mjs",
+ "require": "./dist/browser.js"
}
},
"files": [
diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts
new file mode 100644
index 000000000..a1f73f842
--- /dev/null
+++ b/sdk/src/browser.ts
@@ -0,0 +1,56 @@
+/**
+ * Browser-safe entrypoint for @mcpjam/sdk.
+ *
+ * This export surface intentionally excludes node-oriented eval/test utilities.
+ */
+
+export { MCPClientManager } from "./mcp-client-manager/index.js";
+
+export type {
+ MCPClientManagerConfig,
+ MCPClientManagerOptions,
+ MCPServerConfig,
+ StdioServerConfig,
+ HttpServerConfig,
+ BaseServerConfig,
+ MCPConnectionStatus,
+ ServerSummary,
+ MCPServerSummary,
+ ElicitationHandler,
+ ElicitationCallback,
+ ElicitationCallbackRequest,
+ ElicitResult,
+ ProgressHandler,
+ ProgressEvent,
+ RpcLogger,
+ RpcLogEvent,
+ Tool,
+ ToolExecuteOptions,
+ AiSdkTool,
+ ExecuteToolArguments,
+ TaskOptions,
+ ClientCapabilityOptions,
+ MCPTask,
+ MCPTaskStatus,
+ MCPListTasksResult,
+ ListToolsResult,
+ MCPPromptListResult,
+ MCPPrompt,
+ MCPGetPromptResult,
+ MCPResourceListResult,
+ MCPResource,
+ MCPReadResourceResult,
+ MCPResourceTemplateListResult,
+ MCPResourceTemplate,
+} from "./mcp-client-manager/index.js";
+
+export {
+ isChatGPTAppTool,
+ isMcpAppTool,
+ scrubMetaFromToolResult,
+ scrubMetaAndStructuredContentFromToolResult,
+ MCPError,
+ MCPAuthError,
+ isAuthError,
+ isMCPAuthError,
+} from "./mcp-client-manager/index.js";
diff --git a/sdk/src/mcp-client-manager/MCPClientManager.ts b/sdk/src/mcp-client-manager/MCPClientManager.ts
index 8f3340fd1..957c365f5 100644
--- a/sdk/src/mcp-client-manager/MCPClientManager.ts
+++ b/sdk/src/mcp-client-manager/MCPClientManager.ts
@@ -3,10 +3,6 @@
*/
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
-import {
- getDefaultEnvironment,
- StdioClientTransport,
-} from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import {
@@ -430,7 +426,10 @@ export class MCPClientManager {
*/
async getToolsForAiSdk(
serverIds?: string[] | string,
- options: { schemas?: ToolSchemaOverrides | "automatic"; needsApproval?: boolean } = {}
+ options: {
+ schemas?: ToolSchemaOverrides | "automatic";
+ needsApproval?: boolean;
+ } = {}
): Promise {
const ids = Array.isArray(serverIds)
? serverIds
@@ -1013,6 +1012,19 @@ export class MCPClientManager {
config: StdioServerConfig,
timeout: number
): Promise {
+ const isBrowser =
+ typeof window !== "undefined" && typeof document !== "undefined";
+ if (isBrowser) {
+ throw new Error(
+ `STDIO transport is not supported in browser environments for server "${serverId}".`
+ );
+ }
+
+ const stdioImportPath = "@modelcontextprotocol/sdk/client/" + "stdio.js";
+ const { StdioClientTransport, getDefaultEnvironment } = await import(
+ /* @vite-ignore */ stdioImportPath
+ );
+
const underlying = new StdioClientTransport({
command: config.command,
args: config.args,
diff --git a/sdk/tsup.config.ts b/sdk/tsup.config.ts
index 96cb009ca..345768df7 100644
--- a/sdk/tsup.config.ts
+++ b/sdk/tsup.config.ts
@@ -1,7 +1,7 @@
import { defineConfig } from "tsup";
export default defineConfig({
- entry: ["src/index.ts"],
+ entry: ["src/index.ts", "src/browser.ts"],
format: ["cjs", "esm"],
dts: true,
splitting: false,