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
+
+
+
+
+
+
+
+
+
+
+ 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/",
+});