diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3dc7ae6 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,48 @@ +name: ๐Ÿš€ Deploy + +on: + push: + branches: + - main + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + name: ๐Ÿ—๏ธ Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: โŽ” Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: โŽ” Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: ๐Ÿ“ฅ Install dependencies + run: pnpm install --ignore-scripts --frozen-lockfile + + - name: ๐Ÿ› ๏ธ Build project + run: pnpm demo + + - name: ๐Ÿ“„ Setup Pages + uses: actions/configure-pages@v3 + + - name: ๐Ÿ“ค Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: './demo/dist' + + - name: ๐Ÿš€ Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2749e44 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: โŽ” Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: โŽ” Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: 'https://registry.npmjs.org' + cache: pnpm + + - name: ๐Ÿ“ฅ Install dependencies + run: pnpm install --frozen-lockfile + + - name: ๐Ÿ” Type Check + run: pnpm run typecheck + + - name: ๐Ÿงช Test + run: pnpm run test + + - name: ๐Ÿ—๏ธ Build + run: pnpm run build + + - name: ๐Ÿ“ฆ Validate build artifacts + run: | + # Check if dist directory exists + if [ ! -d "dist" ]; then + echo "โŒ dist directory is missing but declared in package.json" + exit 1 + fi + + # Check if dist contains files + if [ -z "$(ls -A dist)" ]; then + echo "โŒ dist directory is empty" + exit 1 + fi + + # Check for index.js and index.d.ts + if [ ! -f "dist/index.js" ] || [ ! -f "dist/index.d.ts" ]; then + echo "โŒ Missing required files in dist/" + echo "Required: index.js and index.d.ts" + echo "Found: $(ls dist)" + exit 1 + fi + + echo "โœ… Build artifacts validation passed" + + - name: ๐Ÿ“ฆ Publish to NPM + run: pnpm publish --no-git-checks + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..bf99942 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: CI + +on: push + +jobs: + test: + runs-on: ubuntu-latest + + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/checkout@v4 + + - name: โŽ” Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: โŽ” Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: ๐Ÿ“ฅ Install dependencies + run: pnpm install --ignore-scripts --frozen-lockfile + + - name: ๐Ÿ” Type Check + run: pnpm run typecheck + + - name: ๐Ÿงช Test + run: pnpm run test + + - name: ๐Ÿ“Š Report Coverage + if: always() + uses: davelosert/vitest-coverage-report-action@v2 + with: + pr-number: auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..089cd0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +coverage +src/**.js +scripts/**.js +dist +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5911d29 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to codemirror-mcp + +Thanks for your interest in contributing! Here's how you can help: + +## Development Setup + +1. Fork and clone the repo +2. Install dependencies: `pnpm install` +3. Run tests: `pnpm test` +4. Start dev environment: `pnpm dev` + +## Development Process + +1. Create a feature branch: `git checkout -b feature-name` +2. Make your changes +3. Run tests: `pnpm test` +4. Run type checks: `pnpm typecheck` +5. Run linting: `pnpm lint` +6. Commit your changes using conventional commits +7. Push to your fork and submit a pull request + +## Pull Request Guidelines + +- Include tests for any new functionality +- Update documentation for API changes +- Follow the existing code style +- Keep PRs focused - one feature/fix per PR + +## Commit Messages + +We use conventional commits. Format: + +```markdown +type(scope): description + +[optional body] +[optional footer] +``` + +Types: + +- feat: New feature +- fix: Bug fix +- docs: Documentation only +- style: Code style changes +- refactor: Code changes that neither fixes a bug nor adds a feature +- perf: Performance improvements +- test: Adding or updating tests +- chore: Changes to build process or auxiliary tools + +## Release Process + +1. Changes are merged to main +2. GitHub Actions automatically runs tests +3. Maintainers will handle versioning and publishing to npm + +## Questions? + +Open an issue or discussion if you have questions! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b3ab10 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# codemirror-mcp + +> โš ๏ธ **Warning**: This package is in active development and the API may change without notice. + +A CodeMirror extension that implements the Model Context Protocol (MCP) for enhanced code editing capabilities. + +## Features + +- Resource Completion: Autocomplete for `@resource` mentions +- Resource Decorations: Visual styling for `@resource` mentions with click handling +- Prompt Completion: Autocomplete for `/prompt` commands +- Theme Support: Customizable styling + +## Installation + +```bash +npm install @marimo-team/codemirror-mcp +# or +pnpm add @marimo-team/codemirror-mcp +``` + +## Peer Dependencies + +This module requires the following peer dependencies: + +- `@codemirror/view` +- `@codemirror/state` +- `@modelcontextprotocol/sdk` + +## Usage + +```ts +import { mcpExtension } from '@marimo-team/codemirror-mcp'; +import { EditorView } from '@codemirror/view'; + +const view = new EditorView({ + extensions: [ + // ... other extensions + mcpExtension({ + // Required + transport: yourMCPTransport, + + // Optional + logger: console, + clientOptions: { + name: 'your-client', + version: '1.0.0' + }, + onResourceClick: (resource) => { + // Open resource + }, + }) + ], + parent: document.querySelector('#editor') +}); +``` + +## Resource Features + +- Use `@resource-uri` syntax to reference resources +- Resources are visually decorated and clickable +- Click handling for resource interactions +- Hover tooltips show resource details +- Customizable theme + +## Prompt Features + +- Use `/command` syntax for prompt commands +- Autocomplete for available prompts + +## Development + +```bash +# Install dependencies +pnpm install + +# Run tests +pnpm test +``` + +## License + +MIT diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..66a2e25 --- /dev/null +++ b/biome.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true, + "ignore": ["**/dist/**", "**/coverage/**", "**/demo/**", "**/scripts/**", "**/test/**"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 100 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off", + "useConst": "error" + }, + "suspicious": { + "noConsole": "error" + }, + "correctness": { + "noUnusedVariables": "error", + "noUndeclaredVariables": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "trailingCommas": "all", + "semicolons": "always" + } + } +} diff --git a/demo/demo.css b/demo/demo.css new file mode 100644 index 0000000..c3330c4 --- /dev/null +++ b/demo/demo.css @@ -0,0 +1,6 @@ +/* Style for the MCP tooltips */ +.cm-tooltip-cursor { + background-color: #f0f0f0; + padding: 4px; + border-radius: 4px; +} diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..fe2a3e9 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,36 @@ + + + + + + codemirror-mcp demo + + + + + + + + +
+

+ codemirror-mcp +

+

by marimo

+

+ codemirror-mcp is an extension for + CodeMirror 6 that adds + autocomplete support for + ModelContextProtocol. +

+

Demo

+ +
+
+
+
+ + + diff --git a/demo/index.ts b/demo/index.ts new file mode 100644 index 0000000..59f0d80 --- /dev/null +++ b/demo/index.ts @@ -0,0 +1,116 @@ +import { EditorView, basicSetup } from 'codemirror'; +import { markdown } from '@codemirror/lang-markdown'; +import { mcpExtension } from '../src'; +import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import { InitializeResult, JSONRPCMessage, JSONRPCRequest } from '@modelcontextprotocol/sdk/types.js'; +import { tooltips } from '@codemirror/view'; + +class DemoTransport implements Transport { + constructor() {} + + onclose?: () => void; + onerror?: (error: Error) => void; + onmessage?: (message: JSONRPCMessage) => void; + + async start(): Promise { + // Simulate connection delay + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + async send(message: JSONRPCMessage): Promise { + // Simulate sending message + console.log('Sending message:', message); + + if (!('method' in message)) { + return; + } + const req = message as JSONRPCRequest; + + if (req.method === 'initialize') { + setTimeout(() => { + const res: InitializeResult = { + jsonrpc: '2.0', + id: req.id, + result: { + capabilities: {}, + }, + protocolVersion: '2024-11-05', + serverInfo: { + name: 'codemirror-mcp', + version: '0.1.0', + }, + capabilities: {}, + }; + this.onmessage?.({ + jsonrpc: '2.0', + id: req.id, + result: res, + }); + }, 100); + } + + // Simulate response for resources/list + if (req.method === 'resources/list') { + setTimeout(() => { + if (this.onmessage) { + this.onmessage({ + jsonrpc: '2.0', + id: req.id, + result: { + resources: [ + { + name: 'readme', + uri: 'file://docs/README.md', + type: 'file', + mimeType: 'text/markdown', + description: 'Project documentation and getting started guide', + }, + { + name: 'calculateTotal', + uri: 'function://utils/calculateTotal', + type: 'function', + mimeType: 'application/javascript', + description: 'Calculates total price including tax and shipping', + }, + { + name: 'API_KEY', + uri: 'var://config/API_KEY', + type: 'variable', + mimeType: 'text/plain', + description: 'Authentication key for external API access', + }, + { + name: 'salesData2023', + uri: 'data://analytics/sales2023.csv', + type: 'dataset', + mimeType: 'text/csv', + description: 'Annual sales records with customer demographics', + }, + ], + }, + }); + } + }, 100); + } + } + + async close(): Promise { + this.onclose?.(); + } +} + +(async () => { + // Create a transport - you'll need to replace this with your actual MCP server + const transport = new DemoTransport(); + + const editor = new EditorView({ + doc: `# Example Document + +Try typing @ to see MCP completions! + +@`, + // extensions: [basicSetup, markdown(), mcpExtension({ transport, logger: console }), tooltips()], + extensions: [basicSetup, markdown(), mcpExtension({ transport, logger: console }), tooltips()], + parent: document.querySelector('#editor')!, + }); +})(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..a90d9fa --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "codemirror-mcp", + "version": "0.1.0", + "description": "CodeMirror plugin for Model Context Provider", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/marimo-team/codemirror-mcp" + }, + "scripts": { + "prepare": "pnpm run build", + "dev": "vite", + "typecheck": "tsc --noEmit", + "lint": "biome check --write", + "format": "biome format --write", + "test": "vitest", + "demo": "vite build", + "build": "tsc", + "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build", + "release": "pnpm version" + }, + "keywords": ["codemirror", "codemirror-plugin", "mcp"], + "license": "Apache-2.0", + "peerDependencies": { + "@codemirror/autocomplete": "^6", + "@codemirror/state": "^6", + "@codemirror/view": "^6" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-markdown": "^6.3.1", + "@codemirror/view": "^6.36.1", + "@types/node": "^22.10.5", + "@uiw/codemirror-extensions-mentions": "^4.23.7", + "@vitest/coverage-v8": "2.1.8", + "codemirror": "^6.0.1", + "jsdom": "^25.0.1", + "typescript": "^5.7.3", + "vite": "^6.0.7", + "vitest": "^2.1.8" + }, + "files": ["dist"], + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "types": "./dist/index.d.ts", + "type": "module", + "engines": { + "node": "*" + }, + "module": "./dist/index.js", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.1.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c533667 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2348 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@codemirror/autocomplete': + specifier: ^6 + version: 6.18.4 + '@codemirror/state': + specifier: ^6 + version: 6.5.0 + '@modelcontextprotocol/sdk': + specifier: ^1.1.0 + version: 1.1.0 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@codemirror/lang-javascript': + specifier: ^6.2.2 + version: 6.2.2 + '@codemirror/lang-markdown': + specifier: ^6.3.1 + version: 6.3.1 + '@codemirror/view': + specifier: ^6.36.1 + version: 6.36.1 + '@types/node': + specifier: ^22.10.5 + version: 22.10.5 + '@uiw/codemirror-extensions-mentions': + specifier: ^4.23.7 + version: 4.23.7(@codemirror/state@6.5.0)(@codemirror/view@6.36.1) + '@vitest/coverage-v8': + specifier: 2.1.8 + version: 2.1.8(vitest@2.1.8(@types/node@22.10.5)(happy-dom@15.11.7)(jsdom@25.0.1)) + codemirror: + specifier: ^6.0.1 + version: 6.0.1 + jsdom: + specifier: ^25.0.1 + version: 25.0.1 + typescript: + specifier: ^5.7.3 + version: 5.7.3 + vite: + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.10.5) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.5)(happy-dom@15.11.7)(jsdom@25.0.1) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@codemirror/autocomplete@6.18.4': + resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==} + + '@codemirror/commands@6.8.0': + resolution: {integrity: sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-html@6.4.9': + resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==} + + '@codemirror/lang-javascript@6.2.2': + resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==} + + '@codemirror/lang-markdown@6.3.1': + resolution: {integrity: sha512-y3sSPuQjBKZQbQwe3ZJKrSW6Silyl9PnrU/Mf0m2OQgIlPoSYTtOvEL7xs94SVMkb8f4x+SQFnzXPdX4Wk2lsg==} + + '@codemirror/language@6.10.8': + resolution: {integrity: sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==} + + '@codemirror/lint@6.8.4': + resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==} + + '@codemirror/search@6.5.8': + resolution: {integrity: sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==} + + '@codemirror/state@6.5.0': + resolution: {integrity: sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==} + + '@codemirror/view@6.36.1': + resolution: {integrity: sha512-miD1nyT4m4uopZaDdO2uXU/LLHliKNYL9kB1C1wJHrunHLm/rpkb5QVSokqgw9hFqEZakrdlb/VGWX8aYZTslQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/css@1.1.9': + resolution: {integrity: sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/html@1.3.10': + resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==} + + '@lezer/javascript@1.4.21': + resolution: {integrity: sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/markdown@1.4.0': + resolution: {integrity: sha512-mk4MYeq6ZQdxgsgRAe0G7kqPRV6Desajfa14TcHoGGXIqqj1/2ARN31VFpmrXDgvXiGBWpA7RXtv0he+UdTkGw==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + + '@modelcontextprotocol/sdk@1.1.0': + resolution: {integrity: sha512-o5PIPz0vc1bJYXS0oLvRr8yUOzYtxEFL1rWP4aiO8qLslCksmbKhONy6CTpq0WPuIXUt2YuXoRtVA2EcLix3fw==} + engines: {node: '>=18'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.30.1': + resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.30.1': + resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.30.1': + resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.30.1': + resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.30.1': + resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.30.1': + resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.30.1': + resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.30.1': + resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.30.1': + resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.30.1': + resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + + '@uiw/codemirror-extensions-mentions@4.23.7': + resolution: {integrity: sha512-7/7paNbzCWcD5GrzzcKi8GQXF1KuTdGNCX9KA6EfU8naRgkp/8ZuLahHmDd9Hoz9uhikhKY8xc6afDuZM7BaDQ==} + peerDependencies: + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} + peerDependencies: + '@vitest/browser': 2.1.8 + vitest: 2.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} + + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + codemirror@6.0.1: + resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} + engines: {node: '>=18'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + happy-dom@15.11.7: + resolution: {integrity: sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==} + engines: {node: '>=18.0.0'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + + rollup@4.30.1: + resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.71: + resolution: {integrity: sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==} + + tldts@6.1.71: + resolution: {integrity: sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==} + hasBin: true + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + 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 + + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + + '@codemirror/autocomplete@6.18.4': + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.0': + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@lezer/common': 1.2.3 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.18.4 + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.0 + '@lezer/common': 1.2.3 + '@lezer/css': 1.1.9 + + '@codemirror/lang-html@6.4.9': + dependencies: + '@codemirror/autocomplete': 6.18.4 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.2 + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@lezer/common': 1.2.3 + '@lezer/css': 1.1.9 + '@lezer/html': 1.3.10 + + '@codemirror/lang-javascript@6.2.2': + dependencies: + '@codemirror/autocomplete': 6.18.4 + '@codemirror/language': 6.10.8 + '@codemirror/lint': 6.8.4 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.4.21 + + '@codemirror/lang-markdown@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.18.4 + '@codemirror/lang-html': 6.4.9 + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@lezer/common': 1.2.3 + '@lezer/markdown': 1.4.0 + + '@codemirror/language@6.10.8': + dependencies: + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/lint@6.8.4': + dependencies: + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + crelt: 1.0.6 + + '@codemirror/search@6.5.8': + dependencies: + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + crelt: 1.0.6 + + '@codemirror/state@6.5.0': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.36.1': + dependencies: + '@codemirror/state': 6.5.0 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@lezer/common@1.2.3': {} + + '@lezer/css@1.1.9': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/html@1.3.10': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.4.21': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/markdown@1.4.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + + '@marijn/find-cluster-break@1.0.2': {} + + '@modelcontextprotocol/sdk@1.1.0': + dependencies: + content-type: 1.0.5 + raw-body: 3.0.0 + zod: 3.24.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.30.1': + optional: true + + '@rollup/rollup-android-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-x64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.30.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.30.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.30.1': + optional: true + + '@types/estree@1.0.6': {} + + '@types/node@22.10.5': + dependencies: + undici-types: 6.20.0 + + '@uiw/codemirror-extensions-mentions@4.23.7(@codemirror/state@6.5.0)(@codemirror/view@6.36.1)': + dependencies: + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@22.10.5)(happy-dom@15.11.7)(jsdom@25.0.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@22.10.5)(happy-dom@15.11.7)(jsdom@25.0.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.8': + dependencies: + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.5))': + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.4.11(@types/node@22.10.5) + + '@vitest/pretty-format@2.1.8': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.8': + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 + + '@vitest/spy@2.1.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + agent-base@7.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + bytes@3.1.2: {} + + cac@6.7.14: {} + + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + + check-error@2.1.1: {} + + codemirror@6.0.1: + dependencies: + '@codemirror/autocomplete': 6.18.4 + '@codemirror/commands': 6.8.0 + '@codemirror/language': 6.10.8 + '@codemirror/lint': 6.8.4 + '@codemirror/search': 6.5.8 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + content-type@1.0.5: {} + + crelt@1.0.6: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssstyle@4.1.0: + dependencies: + rrweb-cssom: 0.7.1 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decimal.js@10.4.3: {} + + deep-eql@5.0.2: {} + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.0: {} + + es-module-lexer@1.6.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + expect-type@1.1.0: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + happy-dom@15.11.7: + dependencies: + entities: 4.5.0 + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + optional: true + + has-flag@4.0.0: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + is-fullwidth-code-point@3.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jsdom@25.0.1: + dependencies: + cssstyle: 4.1.0 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + loupe@3.1.2: {} + + lru-cache@10.4.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + nanoid@3.3.8: {} + + nwsapi@2.2.16: {} + + package-json-from-dist@1.0.1: {} + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pathe@1.1.2: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + punycode@2.3.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + + rollup@4.30.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.30.1 + '@rollup/rollup-android-arm64': 4.30.1 + '@rollup/rollup-darwin-arm64': 4.30.1 + '@rollup/rollup-darwin-x64': 4.30.1 + '@rollup/rollup-freebsd-arm64': 4.30.1 + '@rollup/rollup-freebsd-x64': 4.30.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 + '@rollup/rollup-linux-arm-musleabihf': 4.30.1 + '@rollup/rollup-linux-arm64-gnu': 4.30.1 + '@rollup/rollup-linux-arm64-musl': 4.30.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 + '@rollup/rollup-linux-riscv64-gnu': 4.30.1 + '@rollup/rollup-linux-s390x-gnu': 4.30.1 + '@rollup/rollup-linux-x64-gnu': 4.30.1 + '@rollup/rollup-linux-x64-musl': 4.30.1 + '@rollup/rollup-win32-arm64-msvc': 4.30.1 + '@rollup/rollup-win32-ia32-msvc': 4.30.1 + '@rollup/rollup-win32-x64-msvc': 4.30.1 + fsevents: 2.3.3 + + rrweb-cssom@0.7.1: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@7.6.3: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + statuses@2.0.1: {} + + std-env@3.8.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + style-mod@4.1.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.0.2: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.71: {} + + tldts@6.1.71: + dependencies: + tldts-core: 6.1.71 + + toidentifier@1.0.1: {} + + tough-cookie@5.0.0: + dependencies: + tldts: 6.1.71 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + + typescript@5.7.3: {} + + undici-types@6.20.0: {} + + unpipe@1.0.0: {} + + vite-node@2.1.8(@types/node@22.10.5): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 1.1.2 + vite: 5.4.11(@types/node@22.10.5) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.11(@types/node@22.10.5): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.30.1 + optionalDependencies: + '@types/node': 22.10.5 + fsevents: 2.3.3 + + vite@6.0.7(@types/node@22.10.5): + dependencies: + esbuild: 0.24.2 + postcss: 8.4.49 + rollup: 4.30.1 + optionalDependencies: + '@types/node': 22.10.5 + fsevents: 2.3.3 + + vitest@2.1.8(@types/node@22.10.5)(happy-dom@15.11.7)(jsdom@25.0.1): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.5)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11(@types/node@22.10.5) + vite-node: 2.1.8(@types/node@22.10.5) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.10.5 + happy-dom: 15.11.7 + jsdom: 25.0.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@3.0.0: + optional: true + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + zod@3.24.1: {} diff --git a/src/__tests__/decoration.test.ts b/src/__tests__/decoration.test.ts new file mode 100644 index 0000000..5b09722 --- /dev/null +++ b/src/__tests__/decoration.test.ts @@ -0,0 +1,234 @@ +import { EditorState, Extension } from "@codemirror/state"; +import type { RangeSet } from "@codemirror/state"; +import type { Decoration } from "@codemirror/view"; +import { EditorView } from "@codemirror/view"; +import type { Resource } from "@modelcontextprotocol/sdk/types.js"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { resourceDecorations } from "../decoration"; +import { resourceClickHandlerField, resourcesField, updateResources } from "../state"; + +// Helper function to count decorations +function countDecorations(decos: RangeSet): number { + let count = 0; + decos.between(0, Number.POSITIVE_INFINITY, () => { + count++; + }); + return count; +} + +describe("resourceDecorations", () => { + let view: EditorView; + const sampleResources: Resource[] = [ + { name: "repo1", uri: "github://repo1", type: "github" }, + { name: "repo2", uri: "gitlab://repo2", type: "gitlab" }, + ]; + + beforeEach(() => { + // Create a new editor state and view for each test + const state = EditorState.create({ + doc: "", + extensions: [resourcesField, resourceDecorations, resourceClickHandlerField.init(() => null)], + }); + view = new EditorView({ state }); + }); + + afterEach(() => { + view.destroy(); + }); + + test("should create decorations for URIs in text", () => { + // Set up initial resources + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + // Update document with URIs + view.dispatch({ + changes: { + from: 0, + to: 0, + insert: "@github://repo1 some text @gitlab://repo2", + }, + }); + + // Get decorations from the plugin + const decorations = view.plugin(resourceDecorations)?.decorations; + expect(decorations).toBeDefined(); + + // Count decorations + const count = countDecorations(decorations!); + expect(count).toBe(2); + + // Check positions of decorations + const positions: number[] = []; + decorations!.between(0, view.state.doc.length, (from, to) => { + positions.push(from, to); + }); + + expect(positions[0]).toBe(0); + expect(positions[1]).toBe("@github://repo1".length); + expect(positions[2]).toBe(view.state.doc.length - "@gitlab://repo2".length); + expect(positions[3]).toBe(view.state.doc.length); + }); + + test("should update decorations when document changes", () => { + // Set up initial resources + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + // Add first URI + view.dispatch({ + changes: { + from: 0, + to: 0, + insert: "@github://repo1", + }, + }); + + let decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(1); + + // Add second URI + view.dispatch({ + changes: { + from: view.state.doc.length, + to: view.state.doc.length, + insert: " @gitlab://repo2", + }, + }); + + decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(2); + }); + + test("should update decorations when resources change", () => { + // Set up initial document + view.dispatch({ + changes: { + from: 0, + to: 0, + insert: "@github://repo1 @gitlab://repo2 @github://repo3", + }, + }); + + // Add initial resources + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + let decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(2); // Only 2 resources are known + + // Add another resource + const newResources = [ + ...sampleResources, + { + name: "repo3", + uri: "github://repo3", + type: "github", + }, + ]; + + view.dispatch({ + effects: updateResources.of(new Map(newResources.map((r) => [r.uri, r]))), + }); + + decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(3); // Now all 3 URIs should be decorated + }); + + test("should handle empty document", () => { + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + const decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(0); + }); + + test("should handle document without URIs", () => { + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + view.dispatch({ + changes: { + from: 0, + to: 0, + insert: "Just some plain text without any URIs", + }, + }); + + const decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(0); + }); + + test("should handle unknown URIs", () => { + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + view.dispatch({ + changes: { + from: 0, + to: 0, + insert: "@unknown://repo some text @github://repo1", + }, + }); + + const decorations = view.plugin(resourceDecorations)?.decorations; + expect(countDecorations(decorations!)).toBe(1); // Only the known URI should be decorated + }); + + test("should handle click events when handler is provided", () => { + const clickHandler = vi.fn(); + const state = EditorState.create({ + doc: "@github://repo1", + extensions: [ + resourcesField, + resourceDecorations, + resourceClickHandlerField.init(() => clickHandler), + ], + }); + view = new EditorView({ state }); + + // Set up resources + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + // Get the widget element + const decorations = view.plugin(resourceDecorations)?.decorations; + expect(decorations).toBeDefined(); + + // Find the widget in the DOM + const widget = view.contentDOM.querySelector(".cm-resource-widget") as HTMLElement; + expect(widget).toBeDefined(); + expect(widget.style.cursor).toBe("pointer"); + + // Simulate click + widget.click(); + expect(clickHandler).toHaveBeenCalledTimes(1); + const expectedResource = Array.from(sampleResources.values())[0]; + expect(clickHandler).toHaveBeenCalledWith(expectedResource); + }); + + test("should not add click handler when not provided", () => { + const state = EditorState.create({ + doc: "@github://repo1", + extensions: [resourcesField, resourceDecorations], + }); + view = new EditorView({ state }); + + // Set up resources + view.dispatch({ + effects: updateResources.of(new Map(sampleResources.map((r) => [r.uri, r]))), + }); + + // Get the widget element + const widget = view.contentDOM.querySelector(".cm-resource-widget") as HTMLElement; + expect(widget).toBeDefined(); + expect(widget.style.cursor).not.toBe("pointer"); + }); +}); diff --git a/src/__tests__/hover.test.ts b/src/__tests__/hover.test.ts new file mode 100644 index 0000000..b087012 --- /dev/null +++ b/src/__tests__/hover.test.ts @@ -0,0 +1,154 @@ +import type { Resource } from "@modelcontextprotocol/sdk/types.js"; +import { describe, expect, test } from "vitest"; +import { type ResourceMatch, createTooltip, findResourceAtPosition } from "../hover"; + +describe("hover", () => { + const sampleResources = new Map([ + [ + "github://repo1", + { + name: "repo1", + uri: "github://repo1", + type: "github", + description: "A sample repository", + mimeType: "application/x-git", + }, + ], + [ + "gitlab://repo2", + { + name: "repo2", + uri: "gitlab://repo2", + type: "gitlab", + }, + ], + ]); + + describe("findResourceAtPosition", () => { + test("should find resource at cursor position", () => { + const text = "@github://repo1 some text @gitlab://repo2"; + const result = findResourceAtPosition(text, 5, sampleResources); + + expect(result).toBeDefined(); + expect(result?.resource).toEqual(sampleResources.get("github://repo1")); + expect(result?.start).toBe(0); + expect(result?.end).toBe("@github://repo1".length); + }); + + test("should handle cursor at start of resource", () => { + const text = "text @github://repo1"; + const pos = 5; // At the @ + const result = findResourceAtPosition(text, pos, sampleResources); + + expect(result).toBeDefined(); + expect(result?.resource).toEqual(sampleResources.get("github://repo1")); + }); + + test("should handle cursor at end of resource", () => { + const text = "@github://repo1 text"; + const pos = "@github://repo1".length; + const result = findResourceAtPosition(text, pos, sampleResources); + + expect(result).toBeDefined(); + expect(result?.resource).toEqual(sampleResources.get("github://repo1")); + }); + + test("should return null for position not on resource", () => { + const text = "@github://repo1 some text @gitlab://repo2"; + const result = findResourceAtPosition(text, text.indexOf("some"), sampleResources); + + expect(result).toBeNull(); + }); + + test("should return null for unknown resource", () => { + const text = "@unknown://repo some text"; + const result = findResourceAtPosition(text, 5, sampleResources); + + expect(result).toBeNull(); + }); + + test.skip("should handle line offset", () => { + const text = "@github://repo1"; + const lineStart = 100; + const result = findResourceAtPosition(text, 5, sampleResources, lineStart); + + expect(result).toBeDefined(); + expect(result?.start).toBe(lineStart); + expect(result?.end).toBe(lineStart + "@github://repo1".length); + }); + }); + + describe("createTooltip", () => { + test("should create tooltip with resource info", () => { + const resource = sampleResources.get("github://repo1")!; + const tooltip = createTooltip(resource); + + expect(tooltip.dom).toBeDefined(); + expect(tooltip.dom.className).toBe("cm-tooltip-cursor"); + + const title = tooltip.dom.querySelector(".cm-tooltip-cursor-title"); + expect(title).toBeDefined(); + expect(title?.textContent).toBe(`${resource.name} (${resource.uri})`); + + const description = tooltip.dom.querySelector(".cm-tooltip-cursor-description"); + expect(description).toBeDefined(); + expect(description?.textContent).toBe(resource.description); + + const mimeType = tooltip.dom.querySelector(".cm-tooltip-cursor-mimetype"); + expect(mimeType).toBeDefined(); + expect(mimeType?.textContent).toBe(resource.mimeType); + }); + + test("should handle missing optional fields", () => { + const resource = sampleResources.get("gitlab://repo2")!; + const tooltip = createTooltip(resource); + + const title = tooltip.dom.querySelector(".cm-tooltip-cursor-title"); + expect(title).toBeDefined(); + expect(title?.textContent).toBe(`${resource.name} (${resource.uri})`); + + const description = tooltip.dom.querySelector(".cm-tooltip-cursor-description"); + expect(description).toBeNull(); + + const mimeType = tooltip.dom.querySelector(".cm-tooltip-cursor-mimetype"); + expect(mimeType).toBeNull(); + }); + + test("should handle different resource types with all fields", () => { + const resources: Resource[] = [ + { + name: "repo1", + uri: "github://repo1", + type: "github", + description: "A GitHub repository", + mimeType: "application/x-git", + }, + { + name: "ticket1", + uri: "jira://ticket1", + type: "jira", + description: "A Jira ticket", + mimeType: "application/json", + }, + { + name: "page1", + uri: "notion://page1", + type: "notion", + description: "A Notion page", + mimeType: "text/html", + }, + ]; + + for (const resource of resources) { + const tooltip = createTooltip(resource); + const title = tooltip.dom.querySelector(".cm-tooltip-cursor-title"); + const description = tooltip.dom.querySelector(".cm-tooltip-cursor-description"); + const mimeType = tooltip.dom.querySelector(".cm-tooltip-cursor-mimetype"); + + expect(title?.textContent).toBe(`${resource.name} (${resource.uri})`); + expect(description?.textContent).toBe(resource.description); + expect(mimeType?.textContent).toBe(resource.mimeType); + } + }); + }); +}); diff --git a/src/__tests__/mcp.test.ts b/src/__tests__/mcp.test.ts new file mode 100644 index 0000000..60b85f5 --- /dev/null +++ b/src/__tests__/mcp.test.ts @@ -0,0 +1,187 @@ +import { CompletionContext } from "@codemirror/autocomplete"; +import { markdown } from "@codemirror/lang-markdown"; +import { EditorState } from "@codemirror/state"; +import { EditorView, type Tooltip, showTooltip, tooltips } from "@codemirror/view"; +import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import type { + InitializeResult, + JSONRPCMessage, + JSONRPCRequest, +} from "@modelcontextprotocol/sdk/types.js"; +import { type Mock, beforeEach, describe, expect, it, vi } from "vitest"; +import { mcpExtension } from "../index"; + +class MockTransport implements Transport { + onclose?: () => void; + onerror?: (error: Error) => void; + onmessage?: (message: JSONRPCMessage) => void; + + mockResources = [ + { name: "test1", uri: "test://1", type: "text" }, + { name: "test2", uri: "test://2", type: "text" }, + ]; + + async start(): Promise {} + + async send(message: JSONRPCMessage): Promise { + if (!("method" in message)) return; + const req = message as JSONRPCRequest; + + if (req.method === "initialize") { + setTimeout(() => { + const res: InitializeResult = { + jsonrpc: "2.0", + id: req.id, + result: { + capabilities: {}, + }, + protocolVersion: "2024-11-05", + serverInfo: { + name: "codemirror-mcp", + version: "0.1.0", + }, + capabilities: {}, + }; + this.onmessage?.({ + jsonrpc: "2.0", + id: req.id, + result: res, + }); + }, 0); + } + + if (req.method === "resources/list") { + setTimeout(() => { + this.onmessage?.({ + jsonrpc: "2.0", + id: req.id, + result: { + resources: this.mockResources, + }, + }); + }, 0); + } + } + + async close(): Promise { + this.onclose?.(); + } +} + +describe("mcpExtension", () => { + let transport: MockTransport; + let view: EditorView; + let state: EditorState; + let mockLogger: Console; + + function getCompletionHandler(_state: EditorState) { + const ext = mcpExtension({ transport, logger: mockLogger })[2]; + const handler = ext[2].value.override[0]; + return handler; + } + + beforeEach(() => { + transport = new MockTransport(); + mockLogger = { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + } as unknown as Console; + + state = EditorState.create({ + doc: "Hello @", + extensions: [ + markdown(), + mcpExtension({ transport, logger: mockLogger, onClickResource: () => {} }), + tooltips(), + ], + }); + + view = new EditorView({ + state, + parent: document.createElement("div"), + }); + }); + + it("should connect to MCP server on initialization", async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(mockLogger.log).toHaveBeenCalledWith("Connected to MCP server"); + }); + + it("should provide completions when typing @", async () => { + const context = new CompletionContext(state, 7, false); + const handler = getCompletionHandler(state); + const completions = await handler(context); + + expect(completions).toBeTruthy(); + expect(completions?.from).toBe(6); + expect(completions?.options).toHaveLength(2); + expect(completions?.options[0].label).toBe("@test1"); + expect(completions?.options[1].label).toBe("@test2"); + }); + + it("should not provide completions when not typing @", async () => { + const state = EditorState.create({ + doc: "Hello", + extensions: [mcpExtension({ transport, logger: mockLogger })], + }); + const view = new EditorView({ state, parent: document.createElement("div") }); + const context = new CompletionContext(view.state, 5, false); + const handler = getCompletionHandler(view.state); + const completions = await handler(context); + + expect(completions).toBeNull(); + }); + + it.skip("should show tooltip when hovering over @mention", async () => { + const mockTooltip: Tooltip = { + pos: 6, + end: 12, + above: true, + create(_view: EditorView) { + const dom = document.createElement("div"); + dom.className = "cm-tooltip-cursor"; + dom.textContent = "test1 (test://1)"; + return { dom }; + }, + }; + + const state = EditorState.create({ + doc: "Hello @test1", + extensions: [mcpExtension({ transport, logger: mockLogger }), showTooltip.of(mockTooltip)], + }); + const view = new EditorView({ state, parent: document.createElement("div") }); + + // Force resources to be loaded + const context = new CompletionContext(view.state, 7, false); + const handler = getCompletionHandler(view.state); + await handler(context); + + // Get tooltip at @mention position + const [tooltip] = view.state.facet(showTooltip); + expect(tooltip).toBeTruthy(); + expect(tooltip?.pos).toBe(6); + expect(tooltip?.end).toBe(12); + + const dom = tooltip?.create(view).dom; + expect(dom?.textContent).toBe("test1 (test://1)"); + expect(dom?.className).toBe("cm-tooltip-cursor"); + }); + + it("should handle MCP server errors gracefully", async () => { + transport.send = async () => { + throw new Error("Server error"); + }; + + const context = new CompletionContext(view.state, 7, false); + const handler = getCompletionHandler(view.state); + const completions = await handler(context); + + expect(completions).toBeNull(); + expect((mockLogger.error as Mock).mock.calls[0][0]).toBe("Failed to connect to MCP server:"); + }); + + // TODO: test prompt completions +}); diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts new file mode 100644 index 0000000..9e50565 --- /dev/null +++ b/src/__tests__/utils.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, test } from "vitest"; +import { URI_PATTERN, matchAllURIs } from "../utils"; + +describe("URI_PATTERN", () => { + const validURIs = [ + "@github://repo", + "@slack://channel", + "@cursor://workspace", + // Edge cases + "@github://repo/", // trailing slash + "@github://repo/sub", // subpath allowed + "@github://repo?q=1", // query params allowed + "@github://repo#1", // hash allowed + ]; + + const invalidURIs = [ + "", // empty string + "@", // just @ + "github://repo", // missing @ + "@github:/repo", // missing / + "@github:///repo", // too many / + "@git hub://repo", // space in protocol + "plain text", // plain text + "@github:repo", // missing // + "@://repo", // missing protocol + "@github://", // missing resource + ]; + + describe("test()", () => { + test.each(validURIs)("should match valid URI: %s", (uri) => { + expect(URI_PATTERN.test(uri)).toBe(true); + expect(Array.from(matchAllURIs(uri))[0][0]).toBe(uri); + }); + + test.each(invalidURIs)("should not match invalid URI: %s", (uri) => { + expect(URI_PATTERN.test(uri)).toBe(false); + }); + }); + + describe("matchAll()", () => { + test("should find all URIs in text", () => { + const text = `Here are some URIs: + @github://repo1/file.md and @gitlab://project2/file.md + Some text in between + @notion://page3 + More text @teams://channel4/user1`; + + const matches = Array.from(matchAllURIs(text)); + expect(matches).toHaveLength(4); + expect(matches[0][0]).toBe("@github://repo1/file.md"); + expect(matches[1][0]).toBe("@gitlab://project2/file.md"); + expect(matches[2][0]).toBe("@notion://page3"); + expect(matches[3][0]).toBe("@teams://channel4/user1"); + }); + + test("should return empty array for text without URIs", () => { + const text = "Just some plain text without any URIs"; + const matches = Array.from(matchAllURIs(text)); + expect(matches).toHaveLength(0); + }); + + test("should handle multiple URIs on same line", () => { + const text = "@github://repo1/file.md @gitlab://repo2/file.md @notion://page3"; + const matches = Array.from(matchAllURIs(text)); + expect(matches).toHaveLength(3); + expect(matches.map((m) => m[0])).toEqual([ + "@github://repo1/file.md", + "@gitlab://repo2/file.md", + "@notion://page3", + ]); + }); + + test("should handle URIs at start and end of text", () => { + const text = "@github://repo1 some text @gitlab://repo2"; + const matches = Array.from(matchAllURIs(text)); + expect(matches).toHaveLength(2); + expect(matches[0][0]).toBe("@github://repo1"); + expect(matches[1][0]).toBe("@gitlab://repo2"); + }); + }); +}); diff --git a/src/decoration.ts b/src/decoration.ts new file mode 100644 index 0000000..b576d0f --- /dev/null +++ b/src/decoration.ts @@ -0,0 +1,96 @@ +import type { Range } from "@codemirror/state"; +import { + Decoration, + type DecorationSet, + type EditorView, + ViewPlugin, + type ViewUpdate, + WidgetType, +} from "@codemirror/view"; +import type { Resource } from "@modelcontextprotocol/sdk/types.js"; +import { resourceClickHandlerField, resourcesField, updateResources } from "./state.js"; +import { URI_PATTERN, matchAllURIs } from "./utils.js"; + +// Widget for resource decoration +class ResourceWidget extends WidgetType { + constructor( + readonly resource: Resource, + readonly view: EditorView, + ) { + super(); + } + + eq(other: ResourceWidget) { + return other.resource.uri === this.resource.uri; + } + + toDOM() { + const wrap = document.createElement("span"); + wrap.className = "cm-resource-widget"; + wrap.textContent = `@${this.resource.name}`; + wrap.title = this.resource.uri; + + const clickHandler = this.view.state.field(resourceClickHandlerField, false); + if (clickHandler) { + wrap.style.cursor = "pointer"; + wrap.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + clickHandler(this.resource); + }); + } + + return wrap; + } +} + +// Create decorations from resources +function createResourceDecorations(view: EditorView): DecorationSet { + const resources = view.state.field(resourcesField); + const decorations: Range[] = []; + + for (const { from, to } of view.visibleRanges) { + const text = view.state.doc.sliceString(from, to); + const matches = matchAllURIs(text); + + for (const match of matches) { + const start = from + match.index!; + const uri = match[0].slice(1); // Remove @ prefix + const resource = resources.get(uri); + + if (resource) { + decorations.push( + Decoration.replace({ + widget: new ResourceWidget(resource, view), + }).range(start, start + match[0].length), + ); + } + } + } + + return Decoration.set(decorations); +} + +// ViewPlugin for resource decorations +export const resourceDecorations = ViewPlugin.fromClass( + class { + decorations: DecorationSet; + + constructor(view: EditorView) { + this.decorations = createResourceDecorations(view); + } + + update(update: ViewUpdate) { + if ( + update.docChanged || + update.viewportChanged || + update.transactions.some((tr) => tr.effects.some((e) => e.is(updateResources))) + ) { + this.decorations = createResourceDecorations(update.view); + } + } + }, + { + decorations: (v) => v.decorations, + }, +); diff --git a/src/hover.ts b/src/hover.ts new file mode 100644 index 0000000..105ff4d --- /dev/null +++ b/src/hover.ts @@ -0,0 +1,76 @@ +import { type Tooltip, type TooltipView, hoverTooltip } from "@codemirror/view"; +import type { Resource } from "@modelcontextprotocol/sdk/types.js"; +import { resourcesField } from "./state.js"; +import { matchAllURIs } from "./utils.js"; + +export function createTooltip(resource: Resource): TooltipView { + const dom = document.createElement("div"); + dom.className = "cm-tooltip-cursor"; + + const title = document.createElement("div"); + title.className = "cm-tooltip-cursor-title"; + title.textContent = `${resource.name} (${resource.uri})`; + dom.appendChild(title); + + if (resource.description) { + const description = document.createElement("div"); + description.className = "cm-tooltip-cursor-description"; + description.textContent = resource.description; + dom.appendChild(description); + } + + if (resource.mimeType) { + const mimeType = document.createElement("div"); + mimeType.className = "cm-tooltip-cursor-mimetype"; + mimeType.textContent = resource.mimeType; + dom.appendChild(mimeType); + } + + return { dom }; +} + +export interface ResourceMatch { + resource: Resource; + start: number; + end: number; +} + +export function findResourceAtPosition( + text: string, + pos: number, + resources: Map, + lineStart = 0, +): ResourceMatch | null { + for (const match of matchAllURIs(text)) { + const start = lineStart + match.index!; + const end = start + match[0].length; + + if (pos >= start && pos <= end) { + const uri = match[0].slice(1); // Remove @ prefix + const resource = resources.get(uri); + if (resource) { + return { resource, start, end }; + } + } + } + return null; +} + +export function hoverResource() { + return hoverTooltip((view, pos) => { + const { from, text } = view.state.doc.lineAt(pos); + const resources = view.state.field(resourcesField); + + const result = findResourceAtPosition(text, pos - from, resources, from); + if (!result) return null; + + return { + pos: result.start, + end: result.end, + above: true, + create() { + return createTooltip(result.resource); + }, + }; + }); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0deba1f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,229 @@ +import { type Completion, type CompletionContext, autocompletion } from "@codemirror/autocomplete"; +import type { Extension } from "@codemirror/state"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { + GetPromptResultSchema, + type Implementation, + ListPromptsResultSchema, + ListResourcesResultSchema, + type Resource, +} from "@modelcontextprotocol/sdk/types.js"; +import { resourceDecorations } from "./decoration.js"; +import { hoverResource } from "./hover.js"; +import { + type ResourceClickHandler, + promptsField, + resourceClickHandlerField, + resourcesField, + updatePrompts, + updateResources, +} from "./state.js"; +import { resourceTheme } from "./theme.js"; + +export interface MCPOptions { + transport: Transport; + clientOptions?: Implementation; + logger?: typeof console; + onClickResource?: ResourceClickHandler; +} + +interface CompletionHandlerContext { + word: { from: number; to: number } | null; + connected: boolean; + client: Client; + logger?: typeof console; + context: CompletionContext; +} + +async function handleResourceCompletion({ + word, + connected, + client, + logger, + context, +}: CompletionHandlerContext) { + if (!word) return null; + if (word.from === word.to && !context.explicit) return null; + if (!connected) { + logger?.error("MCP client is not connected"); + return null; + } + + logger?.log("Fetching resources from MCP server"); + + try { + // Fetch resources from MCP server + const response = await client.request({ method: "resources/list" }, ListResourcesResultSchema); + const resources = response.resources; + if (resources.length === 0) { + return null; + } + const effects = updateResources.of( + new Map(resources.map((resource) => [resource.uri, resource])), + ); + + if (context.view) { + context.view.dispatch({ effects }); + } + + logger?.log(`Got ${resources.length} resources from MCP server`); + + // Convert resources to completion items + const options = resources.map( + (resource: Resource): Completion => ({ + label: `@${resource.name}`, + displayLabel: resource.name, + detail: resource.uri, + info: resource.description || undefined, + type: resource.mimeType ? "constant" : "variable", + boost: resource.description ? 100 : 0, + apply: (view, _completion, from, to) => { + view.dispatch({ + changes: { from, to, insert: `@${resource.uri}` }, + }); + }, + }), + ); + + return { + from: word.from, + options, + }; + } catch (error) { + logger?.error("Failed to fetch MCP resources:", error); + return null; + } +} + +async function handlePromptCompletion({ + word, + connected, + client, + logger, + context, +}: CompletionHandlerContext) { + if (!word) return null; + if (word.from === word.to && !context.explicit) return null; + if (!connected) { + logger?.error("MCP client is not connected"); + return null; + } + + logger?.log("Fetching prompts from MCP server"); + + try { + // Fetch prompts from MCP server + const response = await client.request({ method: "prompts/list" }, ListPromptsResultSchema); + const prompts = response.prompts; + // TODO: Implement prompt with args + // Not implemented yet, ideally looks a bit like slack completions + // /read_table [table_name] + // /read_table [table_name] [column_name] + // /read_table [table_name] [column_name] [row_id] + const promptWithoutArgs = prompts.filter( + (prompt) => !prompt.args || Object.keys(prompt.args).length === 0, + ); + if (promptWithoutArgs.length === 0) { + return null; + } + + const effects = updatePrompts.of( + new Map(promptWithoutArgs.map((prompt) => [prompt.name, prompt])), + ); + + if (context.view) { + context.view.dispatch({ effects }); + } + + logger?.log(`Got ${prompts.length} prompts from MCP server`); + + // // Convert prompts to completion items + const options = prompts.map( + (prompt): Completion => ({ + label: `/${prompt.name}`, + displayLabel: prompt.name, + detail: prompt.description, + type: "keyword", + boost: prompt.description ? 100 : 0, + apply: async (_view, _completion, _from, _to) => { + // Load the prompt template + const _promptResult = await client.request( + { method: "prompts/get", params: { name: prompt.name } }, + GetPromptResultSchema, + ); + }, + }), + ); + + return { + from: word.from, + options, + }; + } catch (error) { + logger?.error("Failed to fetch MCP prompts:", error); + return null; + } +} + +export function mcpExtension(options: MCPOptions): Extension { + const logger = options.logger; + const client = new Client( + { + name: options.clientOptions?.name ?? "codemirror-mcp", + version: options.clientOptions?.version ?? "0.1.0", + }, + { + capabilities: {}, + }, + ); + + const connectedPromise = client + .connect(options.transport) + .then(() => { + logger?.log("Connected to MCP server"); + return true; + }) + .catch((error) => { + logger?.error("Failed to connect to MCP server:", error); + return false; + }); + + const completion = autocompletion({ + override: [ + async (context: CompletionContext) => { + const connected = await connectedPromise; + const handlerContext: Omit = { + connected, + client, + logger, + context, + }; + + // Handle resource completions (@) + const resourceWord = context.matchBefore(/@(\w+)?/); + if (resourceWord) { + return handleResourceCompletion({ ...handlerContext, word: resourceWord }); + } + + // Handle prompt completions (/) + const promptWord = context.matchBefore(/\/(\w+)?/); + if (promptWord) { + return handlePromptCompletion({ ...handlerContext, word: promptWord }); + } + + return null; + }, + ], + }); + + return [ + resourcesField, + promptsField, + completion, + resourceTheme, + hoverResource(), + resourceDecorations, + resourceClickHandlerField.init(() => options.onClickResource || null), + ]; +} diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..2ab25c2 --- /dev/null +++ b/src/state.ts @@ -0,0 +1,64 @@ +import { StateEffect, type StateEffectType, StateField } from "@codemirror/state"; +import type { Prompt, Resource } from "@modelcontextprotocol/sdk/types.js"; + +type ResourceURI = string; +type ResourceMap = Map; + +// Effect to update resources +export const updateResources: StateEffectType = StateEffect.define(); + +// StateField to track resources +export const resourcesField: StateField = StateField.define({ + create() { + return new Map(); + }, + update(oldResources, tr) { + for (const e of tr.effects) { + if (e.is(updateResources)) { + // merge resources + const newResources = new Map(oldResources); + for (const [resourceURI, resource] of e.value) { + newResources.set(resourceURI, resource); + } + return newResources; + } + } + return oldResources; + }, +}); + +export type ResourceClickHandler = (resource: Resource) => void; + +export const resourceClickHandlerField = StateField.define({ + create() { + return null; + }, + update(value) { + return value; + }, +}); + +type PromptMap = Map; + +// Effect to update prompts +export const updatePrompts = StateEffect.define(); + +// StateField to track prompts +export const promptsField = StateField.define({ + create() { + return new Map(); + }, + update(oldPrompts, tr) { + for (const e of tr.effects) { + if (e.is(updatePrompts)) { + // merge prompts + const newPrompts = new Map(oldPrompts); + for (const [name, prompt] of e.value) { + newPrompts.set(name, prompt); + } + return newPrompts; + } + } + return oldPrompts; + }, +}); diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..f08fde5 --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,15 @@ +import { EditorView } from "@codemirror/view"; + +// Theme for resource decorations +export const resourceTheme = EditorView.baseTheme({ + ".cm-resource-widget": { + background: "rgba(86, 156, 214, 0.1)", + borderRadius: "4px", + padding: "2px 4px", + color: "#569cd6", + fontWeight: "500", + }, + ".cm-resource-widget:hover": { + background: "rgba(86, 156, 214, 0.2)", + }, +}); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..651c30a --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,5 @@ +export const URI_PATTERN = /@[\w-]+:\/\/(?!\/)[^\s]+/; + +export function matchAllURIs(text: string) { + return text.matchAll(new RegExp(URI_PATTERN.source, "g")); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..53438cc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": true, + "jsx": "react", + "module": "nodenext", + "moduleResolution": "nodenext", + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "es2022", + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"], + "exclude": ["./demo", "./src/**/*.test.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..262b0c1 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + root: process.env.VITEST ? "." : "demo", + build: { + outDir: "demo/dist", + }, + test: { + environment: "jsdom", + coverage: { + enabled: true, + include: ["src/**"], + exclude: ["demo/**", "scripts/**"], + reportOnFailure: true, + reporter: ["text", "html", "json-summary", "json"], + }, + }, + base: "/codemirror-mcp/", +});