From 110e8aff4dc7bde988202009232a7a23db1de875 Mon Sep 17 00:00:00 2001 From: Ompragash Viswanathan Date: Sat, 21 Feb 2026 01:29:12 +0530 Subject: [PATCH 1/2] feat: implement Chorus.ai MCP server with full API coverage Introduce a complete Model Context Protocol server for the Chorus.ai (ZoomInfo) conversation intelligence platform. The server exposes 39 tools, 6 URI-based resources, and 6 workflow prompts that enable LLMs to interact with sales call data, transcripts, scorecards, and analytics through natural language. Architecture decisions: - JSON:API normalization layer in the API client auto-flattens Chorus responses (data/attributes/id) into plain objects, keeping all 12 tool domain files decoupled from the wire format. - Annotation presets (READ_ONLY, CREATE, DELETE) defined as typed constants eliminate repetition across 39 tool registrations while preserving full MCP spec compliance. - Dual transport support: stdio (default) for CLI/desktop clients, Streamable HTTP for networked deployments. Transport selected via TRANSPORT env var at startup. Tools by domain (39 total): Conversations (5), Users (3), Teams (3), Scorecards (4), Playlists (3), Moments (4 incl. create/delete), Emails (2), Engagements (2), Reports (3), Saved Searches (3), Video Conferences (4 incl. upload/delete), Integrations/Session (3) Workflow prompts: chorus_call_analysis, chorus_deal_risk_assessment, chorus_competitive_intelligence, chorus_meeting_summary, chorus_rep_performance_review, chorus_customer_feedback_synthesis Infrastructure: - Shared services: Axios-based API client with auth injection, HTTP error-to-actionable-message mapper, markdown/JSON formatters with CHARACTER_LIMIT truncation - Zod schemas with .strict() validation on all tool inputs - Pagination via page[size] and cursor on every list endpoint - Jest test suite: 125 tests across 9 suites (unit, integration) covering services, all tool domains, and tool registration integrity - CI: GitHub Actions matrix testing on Node 20/22/24 - Release: tag-triggered npm publish with provenance to @opensourceops/chorus-mcp --- .github/workflows/ci.yml | 32 + .github/workflows/release.yml | 73 + .gitignore | 8 + LICENSE | 2 +- README.md | 305 +- jest.config.js | 32 + package-lock.json | 7420 +++++++++++++++++++ package.json | 63 + src/constants.ts | 41 + src/index.ts | 95 + src/prompts/index.ts | 359 + src/resources/index.ts | 325 + src/schemas/common.ts | 36 + src/schemas/conversations.ts | 72 + src/schemas/emails.ts | 31 + src/schemas/engagements.ts | 33 + src/schemas/moments.ts | 68 + src/schemas/playlists.ts | 30 + src/schemas/reports.ts | 54 + src/schemas/saved-searches.ts | 30 + src/schemas/scorecards.ts | 51 + src/schemas/teams.ts | 30 + src/schemas/users.ts | 39 + src/schemas/video-conferences.ts | 67 + src/services/api-client.ts | 129 + src/services/error-handler.ts | 39 + src/services/formatters.ts | 100 + src/tools/conversations.ts | 418 ++ src/tools/emails.ts | 122 + src/tools/engagements.ts | 117 + src/tools/integrations.ts | 140 + src/tools/moments.ts | 184 + src/tools/playlists.ts | 159 + src/tools/reports.ts | 166 + src/tools/saved-searches.ts | 139 + src/tools/scorecards.ts | 222 + src/tools/teams.ts | 151 + src/tools/users.ts | 162 + src/tools/video-conferences.ts | 173 + src/types.ts | 201 + tests/fixtures/chorus-responses.ts | 279 + tests/integration/tool-registration.test.ts | 154 + tests/unit/services/api-client.test.ts | 93 + tests/unit/services/error-handler.test.ts | 88 + tests/unit/services/formatters.test.ts | 144 + tests/unit/tools/conversations.test.ts | 211 + tests/unit/tools/moments.test.ts | 96 + tests/unit/tools/remaining-tools.test.ts | 295 + tests/unit/tools/scorecards.test.ts | 101 + tests/unit/tools/users.test.ts | 83 + tsconfig.json | 20 + tsconfig.test.json | 9 + 52 files changed, 13489 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 jest.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/constants.ts create mode 100644 src/index.ts create mode 100644 src/prompts/index.ts create mode 100644 src/resources/index.ts create mode 100644 src/schemas/common.ts create mode 100644 src/schemas/conversations.ts create mode 100644 src/schemas/emails.ts create mode 100644 src/schemas/engagements.ts create mode 100644 src/schemas/moments.ts create mode 100644 src/schemas/playlists.ts create mode 100644 src/schemas/reports.ts create mode 100644 src/schemas/saved-searches.ts create mode 100644 src/schemas/scorecards.ts create mode 100644 src/schemas/teams.ts create mode 100644 src/schemas/users.ts create mode 100644 src/schemas/video-conferences.ts create mode 100644 src/services/api-client.ts create mode 100644 src/services/error-handler.ts create mode 100644 src/services/formatters.ts create mode 100644 src/tools/conversations.ts create mode 100644 src/tools/emails.ts create mode 100644 src/tools/engagements.ts create mode 100644 src/tools/integrations.ts create mode 100644 src/tools/moments.ts create mode 100644 src/tools/playlists.ts create mode 100644 src/tools/reports.ts create mode 100644 src/tools/saved-searches.ts create mode 100644 src/tools/scorecards.ts create mode 100644 src/tools/teams.ts create mode 100644 src/tools/users.ts create mode 100644 src/tools/video-conferences.ts create mode 100644 src/types.ts create mode 100644 tests/fixtures/chorus-responses.ts create mode 100644 tests/integration/tool-registration.test.ts create mode 100644 tests/unit/services/api-client.test.ts create mode 100644 tests/unit/services/error-handler.test.ts create mode 100644 tests/unit/services/formatters.test.ts create mode 100644 tests/unit/tools/conversations.test.ts create mode 100644 tests/unit/tools/moments.test.ts create mode 100644 tests/unit/tools/remaining-tools.test.ts create mode 100644 tests/unit/tools/scorecards.test.ts create mode 100644 tests/unit/tools/users.test.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.test.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f13cd52 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x, 22.x, 24.x] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Run tests + run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..00b6907 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,73 @@ +name: Release & Publish to npm + +on: + release: + types: [published] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x, 22.x, 24.x] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Run tests + run: npm test + + publish: + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + registry-url: https://registry.npmjs.org + cache: npm + + - name: Set version from tag + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + npm version "$TAG_VERSION" --no-git-tag-version + echo "Publishing version: $TAG_VERSION" + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Publish to npm + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Commit version update back to main + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add package.json package-lock.json + git commit -m "chore: bump version to ${TAG_VERSION} [skip ci]" + git push origin HEAD:main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01d8eaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.claude +node_modules/ +dist/ +*.tsbuildinfo +.env +.env.* +coverage/ +dist-test/ diff --git a/LICENSE b/LICENSE index 261eeb9..91aa951 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Ompragash Viswanathan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 7e9973c..1f02dad 100644 --- a/README.md +++ b/README.md @@ -1 +1,304 @@ -# chorus-mcp-server \ No newline at end of file +# Chorus MCP Server + +A Model Context Protocol (MCP) server for the Chorus.ai conversation intelligence platform. Connect Chorus to any MCP-compatible AI client to access sales call recordings, transcripts, scorecards, and analytics through natural language. + +## Features + +- **39 Tools** -- Full Chorus API coverage: conversations, transcripts, scorecards, playlists, moments, emails, engagements, reports, and video conferences +- **6 Resources** -- URI-based data access for users, teams, templates, searches, conversations, and playlists +- **6 Workflow Prompts** -- Call analysis, deal risk scoring, competitive intel, meeting summaries, rep reviews, and customer feedback synthesis +- **Token-Optimized** -- Compact markdown and JSON responses reduce token consumption +- **Smart Pagination** -- Automatic handling on all list endpoints +- **Annotation Presets** -- READ_ONLY, CREATE, and DELETE access levels + +## Prerequisites + +- **Node.js** v20.9+, v22+, or v24+ (LTS versions) +- **npm** v9+ +- **MCP Client** -- Claude Code, Continue.dev, or any MCP-compatible client +- **Chorus API Key** -- Generate one in your Chorus Personal Settings ([API docs](https://api-docs.chorus.ai/)) + +## Quick Start with Claude Code + +Run `npx` directly through Claude Code. No clone, no build. + +### Step 1: Get Your Chorus API Key + +1. Log in to Chorus.ai +2. Navigate to **Personal Settings** +3. Generate an API token + +### Step 2: Add the MCP Server + +```bash +claude mcp add --transport stdio chorus \ + --scope user \ + --env CHORUS_API_KEY= \ + -- npx -y @opensourceops/chorus-mcp +``` + +Replace `` with your Chorus API token. + +### Step 3: Restart Claude Code + +Quit and reopen Claude Code for the new server to load. + +### Step 4: Verify + +Ask Claude: + +``` +List the available Chorus tools. +``` + +You should see 39 tools, including `chorus_list_conversations`, `chorus_get_transcript`, and `chorus_search_conversations`. + +## Global Install + +Install the package globally: + +```bash +npm install -g @opensourceops/chorus-mcp +``` + +Then configure your MCP client to run `chorus-mcp-server` instead of `npx`: + +```json +{ + "mcpServers": { + "chorus": { + "command": "chorus-mcp-server", + "env": { + "CHORUS_API_KEY": "your_api_key" + } + } + } +} +``` + +## Available Tools + +### Conversations (5 read-only) +- `chorus_list_conversations` -- List calls and meetings with filters +- `chorus_get_conversation` -- Get conversation metadata +- `chorus_get_transcript` -- Get speaker-attributed transcript +- `chorus_get_conversation_trackers` -- Get tracker hits (competitors, keywords) +- `chorus_search_conversations` -- Search by keyword, participant, or date + +### Users (3 read-only) +- `chorus_list_users` -- List all users +- `chorus_get_user` -- Get user details +- `chorus_search_users` -- Search users by name or email + +### Teams (3 read-only) +- `chorus_list_teams` -- List all teams +- `chorus_get_team` -- Get team details +- `chorus_get_team_members` -- List members of a team + +### Scorecards (4 read-only) +- `chorus_list_scorecards` -- List scorecards +- `chorus_get_scorecard` -- Get scorecard details +- `chorus_list_scorecard_templates` -- List scoring templates +- `chorus_get_scorecard_template` -- Get template details + +### Playlists & Moments (7 tools: 5 read-only, 2 write) +- `chorus_list_playlists` -- List playlists +- `chorus_get_playlist` -- Get playlist details +- `chorus_list_playlist_moments` -- List moments in a playlist +- `chorus_list_moments` -- List all moments +- `chorus_get_moment` -- Get moment details +- `chorus_create_moment` -- Create a moment (write) +- `chorus_delete_moment` -- Delete a moment (write, destructive) + +### Emails (2 read-only) +- `chorus_list_emails` -- List tracked emails +- `chorus_get_email` -- Get email details + +### Engagements (2 read-only) +- `chorus_filter_engagements` -- Filter engagements with criteria +- `chorus_get_engagement` -- Get engagement details + +### Reports (3 read-only) +- `chorus_list_reports` -- List available reports +- `chorus_get_report` -- Get report data +- `chorus_get_activity_metrics` -- Get activity metrics + +### Saved Searches (3 read-only) +- `chorus_list_saved_searches` -- List saved searches +- `chorus_get_saved_search` -- Get saved search details +- `chorus_execute_saved_search` -- Run a saved search and return results + +### Video Conferences (4 tools: 2 read-only, 2 write) +- `chorus_list_video_conferences` -- List video conferences +- `chorus_get_video_conference` -- Get video conference details +- `chorus_upload_recording` -- Upload a recording (write) +- `chorus_delete_recording` -- Delete a recording (write, destructive) + +### Integrations & Session (3 read-only) +- `chorus_list_integrations` -- List connected integrations +- `chorus_get_integration` -- Get integration details +- `chorus_get_session` -- Get current session info + +## Resources + +Access data through MCP resource URIs: + +| URI | Description | +|-----|-------------| +| `chorus://users/{user_id}` | User profile | +| `chorus://teams/{team_id}` | Team details | +| `chorus://scorecard-templates/{template_id}` | Scorecard template | +| `chorus://saved-searches/{search_id}` | Saved search definition | +| `chorus://conversations/{conversation_id}/summary` | Conversation summary | +| `chorus://playlists/{playlist_id}` | Playlist with moments | + +## Prompts + +Built-in workflow prompts for common sales intelligence tasks: + +| Prompt | Purpose | +|--------|---------| +| `chorus_call_analysis` | Generate coaching feedback from a sales call | +| `chorus_deal_risk_assessment` | Score deal risk for a prospect | +| `chorus_competitive_intelligence` | Report competitor mentions across calls | +| `chorus_meeting_summary` | Produce structured meeting summary with action items | +| `chorus_rep_performance_review` | Evaluate rep performance from scorecards and metrics | +| `chorus_customer_feedback_synthesis` | Synthesize product feedback from conversations | + +## Configuration + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `CHORUS_API_KEY` | -- | Chorus API token (required) | +| `TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` | +| `PORT` | `3000` | HTTP server port (when `TRANSPORT=http`) | + +### Transport Modes + +The server supports two transport modes: + +**stdio (default)** -- Standard input/output. Use with Claude Code and most MCP clients. + +**Streamable HTTP** -- HTTP-based transport for network deployments: + +```bash +TRANSPORT=http PORT=3000 CHORUS_API_KEY=your_key npx @opensourceops/chorus-mcp +``` + +## Local Development + +Clone the repository to modify the server, run tests, or contribute. + +### Step 1: Clone and Build + +```bash +git clone https://github.com/opensourceops/chorus-mcp-server.git +cd chorus-mcp-server +npm install +npm run build +``` + +### Step 2: Configure Your MCP Client + +#### Option A: Claude Code (via CLI) + +```bash +claude mcp add --transport stdio chorus \ + --env CHORUS_API_KEY=your_api_key \ + -- $(which node) $(pwd)/dist/index.js +``` + +#### Option B: Manual JSON Configuration + +Add to your MCP client's config file: + +```json +{ + "mcpServers": { + "chorus": { + "command": "node", + "args": ["/absolute/path/to/chorus-mcp-server/dist/index.js"], + "env": { + "CHORUS_API_KEY": "your_api_key" + } + } + } +} +``` + +### Step 3: Restart and Verify + +Restart your MCP client, then ask: + +``` +Show me recent sales calls from Chorus. +``` + +## Project Structure + +``` +chorus-mcp-server/ +├── src/ +│ ├── index.ts # Entry point +│ ├── constants.ts # Shared constants +│ ├── types.ts # Type definitions +│ ├── services/ # API client, error handler, formatters +│ ├── schemas/ # Zod validation schemas +│ ├── tools/ # 12 tool domain files +│ ├── resources/ # MCP resource handlers +│ └── prompts/ # Workflow prompts +├── tests/ +│ ├── unit/ +│ ├── integration/ +│ └── fixtures/ +├── dist/ # Compiled output (generated) +├── package.json +└── tsconfig.json +``` + +## Troubleshooting + +### API Key Issues + +Test your key directly: + +```bash +curl -H "Authorization: Bearer YOUR_API_KEY" https://api.chorus.ai/v1/conversations +``` + +A valid key returns conversation data. + +### Tools Not Appearing + +1. Rebuild after code changes: `npm run build` +2. Restart your MCP client (quit and reopen) +3. Use absolute paths in manual JSON configuration + +### Node.js Version Warnings + +Use Node.js LTS versions (20.9+, 22+, or 24+). Odd-numbered releases (23, 25) are non-LTS and unsupported. Switch with `nvm use 22`. + +## Security + +- Never commit `.env` files or API keys +- Store `CHORUS_API_KEY` in environment variables, not in code +- Destructive tools (`chorus_delete_moment`, `chorus_delete_recording`) require explicit confirmation + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests +5. Submit a pull request + +## License + +Apache 2.0 -- See [LICENSE](LICENSE). + +## Support + +- **Issues** -- [GitHub Issues](https://github.com/opensourceops/chorus-mcp-server/issues) +- **API Reference** -- [Chorus API Documentation](https://api-docs.chorus.ai/) diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..ecc96b4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,32 @@ +export default { + testEnvironment: 'node', + roots: ['/tests'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.ts$': ['ts-jest', { + useESM: false, + tsconfig: 'tsconfig.test.json', + diagnostics: { + ignoreDiagnostics: [151002], + }, + }], + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts', + '!src/index.ts', + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + coverageThreshold: { + global: { + branches: 60, + functions: 60, + lines: 60, + statements: 60, + }, + }, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..abb5de2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7420 @@ +{ + "name": "@anthropic-chorus/chorus-mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@anthropic-chorus/chorus-mcp-server", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "axios": "^1.7.9", + "express": "^4.21.0", + "zod": "^3.23.8" + }, + "bin": { + "chorus-mcp-server": "dist/index.js" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + }, + "engines": { + "node": "^20.9.0 || ^22.0.0 || ^24.0.0", + "npm": ">=9.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/expect/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", + "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4e3ed43 --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "@opensourceops/chorus-mcp", + "version": "1.0.0", + "description": "MCP server for Chorus.ai conversation intelligence API", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "chorus-mcp-server": "dist/index.js" + }, + "files": [ + "dist" + ], + "engines": { + "node": "^20.9.0 || ^22.0.0 || ^24.0.0", + "npm": ">=9.0.0" + }, + "scripts": { + "start": "node dist/index.js", + "dev": "tsx watch src/index.ts", + "build": "tsc", + "clean": "rm -rf dist", + "test": "jest", + "test:watch": "jest --watch", + "test:unit": "jest --testPathPattern=tests/unit", + "test:integration": "jest --testPathPattern=tests/integration", + "test:e2e": "jest --testPathPattern=tests/e2e --testTimeout=60000", + "prepare": "npm run build" + }, + "keywords": [ + "mcp", + "chorus", + "zoominfo", + "conversation-intelligence", + "sales", + "sales-coaching", + "deal-intelligence" + ], + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/opensourceops/chorus-mcp-server.git" + }, + "homepage": "https://github.com/opensourceops/chorus-mcp-server#readme", + "bugs": { + "url": "https://github.com/opensourceops/chorus-mcp-server/issues" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "axios": "^1.7.9", + "express": "^4.21.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..2b23de6 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,41 @@ +export const API_BASE_URL = "https://chorus.ai/api/v1"; +export const CHARACTER_LIMIT = 25000; +export const DEFAULT_PAGE_SIZE = 20; +export const MAX_PAGE_SIZE = 100; + +export enum ResponseFormat { + MARKDOWN = "markdown", + JSON = "json", +} + +/** + * Reusable tool annotation presets. + * + * The MCP spec defines exactly 4 annotation hints per tool: + * readOnlyHint, destructiveHint, idempotentHint, openWorldHint + * + * These presets avoid repeating identical objects across 35+ tools. + */ +export const ANNOTATIONS = { + /** Read-only, safe to call repeatedly (most tools). */ + READ_ONLY: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + /** Creates a new resource (not idempotent, not destructive). */ + CREATE: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + /** Permanently deletes a resource (destructive, not idempotent). */ + DELETE: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, +} as const; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..c8d32db --- /dev/null +++ b/src/index.ts @@ -0,0 +1,95 @@ +#!/usr/bin/env node + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import express from "express"; + +import { registerConversationTools } from "./tools/conversations.js"; +import { registerUserTools } from "./tools/users.js"; +import { registerTeamTools } from "./tools/teams.js"; +import { registerScorecardTools } from "./tools/scorecards.js"; +import { registerPlaylistTools } from "./tools/playlists.js"; +import { registerMomentTools } from "./tools/moments.js"; +import { registerEmailTools } from "./tools/emails.js"; +import { registerEngagementTools } from "./tools/engagements.js"; +import { registerReportTools } from "./tools/reports.js"; +import { registerSavedSearchTools } from "./tools/saved-searches.js"; +import { registerVideoConferenceTools } from "./tools/video-conferences.js"; +import { registerIntegrationTools } from "./tools/integrations.js"; +import { registerResources } from "./resources/index.js"; +import { registerPrompts } from "./prompts/index.js"; + +const server = new McpServer({ + name: "chorus-mcp-server", + version: "1.0.0", +}); + +// Register all tools +registerConversationTools(server); +registerUserTools(server); +registerTeamTools(server); +registerScorecardTools(server); +registerPlaylistTools(server); +registerMomentTools(server); +registerEmailTools(server); +registerEngagementTools(server); +registerReportTools(server); +registerSavedSearchTools(server); +registerVideoConferenceTools(server); +registerIntegrationTools(server); + +// Register resources and prompts +registerResources(server); +registerPrompts(server); + +async function runStdio(): Promise { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Chorus MCP server running via stdio"); +} + +async function runHTTP(): Promise { + const app = express(); + app.use(express.json()); + + app.post("/mcp", async (req, res) => { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + res.on("close", () => { + transport.close(); + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + }); + + const port = parseInt(process.env.PORT || "3000"); + app.listen(port, () => { + console.error(`Chorus MCP server running on http://localhost:${port}/mcp`); + }); +} + +// Validate API key +if (!process.env.CHORUS_API_KEY) { + console.error( + "WARNING: CHORUS_API_KEY environment variable is not set. " + + "API calls will fail until a valid token is provided. " + + "Generate one from your Chorus Personal Settings page." + ); +} + +// Select transport +const transportMode = process.env.TRANSPORT || "stdio"; +if (transportMode === "http") { + runHTTP().catch((error) => { + console.error("Server error:", error); + process.exit(1); + }); +} else { + runStdio().catch((error) => { + console.error("Server error:", error); + process.exit(1); + }); +} diff --git a/src/prompts/index.ts b/src/prompts/index.ts new file mode 100644 index 0000000..e0a975d --- /dev/null +++ b/src/prompts/index.ts @@ -0,0 +1,359 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +export function registerPrompts(server: McpServer): void { + server.registerPrompt( + "chorus_call_analysis", + { + title: "Sales Call Analysis", + description: + "Analyze a recorded sales call for coaching feedback. Examines transcript, talk-to-listen ratio, trackers, and provides actionable coaching recommendations.", + argsSchema: { + conversation_id: z + .string() + .describe("The conversation/call ID to analyze"), + focus_areas: z + .string() + .default("overall") + .describe( + "Comma-separated coaching areas: discovery, objection_handling, closing, rapport, product_knowledge, next_steps, overall" + ), + }, + }, + async ({ conversation_id, focus_areas }) => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `Analyze the sales call with conversation ID "${conversation_id}". + +Use the following tools in sequence: +1. chorus_get_conversation to get call metadata (participants, duration, date) +2. chorus_get_transcript to get the full transcript +3. chorus_get_conversation_trackers to identify tracker hits (competitors, objections, topics) + +Then provide a coaching analysis covering these focus areas: ${focus_areas} + +Structure your analysis as: +## Call Overview +- Date, duration, participants, call type + +## Talk-to-Listen Ratio Analysis +- Estimate from transcript who spoke more, identify longest monologues + +## Key Moments +- Important points in the conversation (objections raised, competitors mentioned, pricing discussed) + +## Coaching Feedback +For each focus area, provide: +- What went well (with transcript quotes) +- Areas for improvement (with specific suggestions) +- Recommended techniques or frameworks + +## Action Items +- Specific next steps for the rep based on this call`, + }, + }, + ], + }) + ); + + server.registerPrompt( + "chorus_deal_risk_assessment", + { + title: "Deal Risk Assessment", + description: + "Assess deal risk by analyzing recent conversations with a prospect. Examines sentiment, competitor mentions, objections, and engagement patterns.", + argsSchema: { + participant_email: z + .string() + .describe("The prospect/buyer email to analyze conversations for"), + lookback_days: z + .string() + .default("30") + .describe("Number of days back to analyze (default: 30)"), + }, + }, + async ({ participant_email, lookback_days }) => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `Assess deal risk for the prospect with email "${participant_email}" over the last ${lookback_days} days. + +Use these tools: +1. chorus_list_conversations filtered by participant_email and date range +2. For each recent conversation, use chorus_get_conversation_trackers to find competitor mentions and objections +3. Use chorus_get_transcript for the most recent 2-3 calls to analyze sentiment and tone + +Provide a risk assessment structured as: + +## Deal Summary +- Number of conversations in period, total engagement time +- Key participants from buyer side + +## Risk Signals +Rate each as Low/Medium/High: +- **Competitor Activity**: Are competitors being mentioned? How frequently? +- **Objection Patterns**: Are the same objections recurring without resolution? +- **Engagement Trend**: Is meeting frequency increasing or decreasing? +- **Stakeholder Breadth**: Are we talking to decision-makers or only champions? +- **Sentiment**: Is the buyer tone positive, neutral, or negative? +- **Next Steps Clarity**: Are clear next steps being set and followed through? + +## Overall Risk Score +Low / Medium / High with justification + +## Recommended Actions +Specific steps to de-risk the deal`, + }, + }, + ], + }) + ); + + server.registerPrompt( + "chorus_competitive_intelligence", + { + title: "Competitive Intelligence Report", + description: + "Generate a competitive intelligence report by analyzing competitor mentions across recent conversations.", + argsSchema: { + competitor_name: z + .string() + .describe("The competitor name to track across conversations"), + start_date: z + .string() + .describe("Start date for analysis (ISO 8601, e.g. 2024-01-01)"), + end_date: z + .string() + .describe("End date for analysis (ISO 8601, e.g. 2024-12-31)"), + }, + }, + async ({ competitor_name, start_date, end_date }) => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `Generate a competitive intelligence report for "${competitor_name}" from ${start_date} to ${end_date}. + +Steps: +1. Use chorus_search_conversations to find calls mentioning "${competitor_name}" +2. For each relevant call, use chorus_get_conversation_trackers to get context +3. Use chorus_get_transcript for the top 5-10 most relevant calls to extract exact quotes + +Report structure: + +## Executive Summary +- Total mentions, trend over time, most common contexts + +## How Prospects Describe ${competitor_name} +- Common praise points (with quotes from transcripts) +- Common complaints (with quotes) + +## Competitive Positioning +- Where ${competitor_name} is perceived as stronger +- Where we are perceived as stronger +- Common switching triggers + +## Objections Related to ${competitor_name} +- Most frequent objections when ${competitor_name} is in the deal +- Successful rebuttals used by our reps (with examples) + +## Win/Loss Patterns +- Patterns in deals where ${competitor_name} was mentioned + +## Recommendations +- Messaging adjustments +- Battlecard suggestions +- Training priorities`, + }, + }, + ], + }) + ); + + server.registerPrompt( + "chorus_meeting_summary", + { + title: "Meeting Summary & Action Items", + description: + "Generate a structured meeting summary with key discussion points, decisions, and action items from a conversation transcript.", + argsSchema: { + conversation_id: z + .string() + .describe("The conversation/call ID to summarize"), + }, + }, + async ({ conversation_id }) => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `Create a comprehensive meeting summary for conversation "${conversation_id}". + +Use: +1. chorus_get_conversation for metadata +2. chorus_get_transcript for the full transcript +3. chorus_get_conversation_trackers for topic/keyword detection + +Structure the summary as: + +## Meeting Details +- Date, time, duration +- Participants (with roles if identifiable) +- Meeting type (discovery, demo, negotiation, check-in, etc.) + +## Executive Summary +2-3 sentence overview of the meeting + +## Key Discussion Points +Numbered list of main topics discussed with brief descriptions + +## Decisions Made +Bullet list of any decisions reached during the meeting + +## Action Items +For each action item: +- [ ] Description of the task +- **Owner**: Who is responsible (from transcript context) +- **Due**: Any mentioned deadline + +## Open Questions +Items that were raised but not resolved + +## Follow-Up +Suggested next steps based on the conversation`, + }, + }, + ], + }) + ); + + server.registerPrompt( + "chorus_rep_performance_review", + { + title: "Rep Performance Review", + description: + "Generate a performance review for a sales rep by analyzing their scorecards, call activity metrics, and conversation patterns.", + argsSchema: { + user_id: z.string().describe("The rep's Chorus user ID"), + start_date: z + .string() + .describe("Review period start (ISO 8601, e.g. 2024-01-01)"), + end_date: z + .string() + .describe("Review period end (ISO 8601, e.g. 2024-12-31)"), + }, + }, + async ({ user_id, start_date, end_date }) => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `Create a performance review for rep with user ID "${user_id}" from ${start_date} to ${end_date}. + +Steps: +1. chorus_get_user to get rep profile and team +2. chorus_list_scorecards filtered by user_id and date range +3. chorus_get_activity_metrics for the rep over the period +4. chorus_list_conversations filtered by the rep and date range +5. For the 2-3 lowest-scored calls, use chorus_get_scorecard for details + +Report: + +## Rep Profile +- Name, team, role, tenure + +## Activity Summary +- Total calls, total duration, average call length +- Week-over-week trends + +## Scorecard Analysis +- Average score across all scorecards +- Scores by criteria category +- Trend over time (improving, declining, stable) + +## Strengths +- Top-scoring criteria with examples from calls + +## Areas for Improvement +- Lowest-scoring criteria with specific examples +- Suggested training resources or playlist moments + +## Recommendations +- Specific coaching actions +- Suggested playlists or calls to review +- Goals for next review period`, + }, + }, + ], + }) + ); + + server.registerPrompt( + "chorus_customer_feedback_synthesis", + { + title: "Customer Feedback Synthesis", + description: + "Aggregate and synthesize customer feedback from conversations for product teams. Identifies feature requests, pain points, and trending topics.", + argsSchema: { + topic: z + .string() + .describe( + "Product area or topic to focus on (e.g., 'reporting', 'integrations', 'onboarding')" + ), + start_date: z.string().describe("Start date (ISO 8601)"), + end_date: z.string().describe("End date (ISO 8601)"), + }, + }, + async ({ topic, start_date, end_date }) => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `Synthesize customer feedback about "${topic}" from conversations between ${start_date} and ${end_date}. + +Steps: +1. chorus_search_conversations with keyword "${topic}" and date filters +2. For the top 10-15 most relevant conversations, use chorus_get_transcript to extract customer quotes +3. Use chorus_get_conversation_trackers to identify related topics + +Report: + +## Overview +- Number of conversations mentioning "${topic}" +- Types of customers raising this topic + +## Feature Requests +Ranked by frequency: +1. Request description - mentioned N times + - Representative quotes from customers + +## Pain Points +Current frustrations related to ${topic}: +1. Pain point - mentioned N times + - Customer quotes showing impact + +## Positive Feedback +What customers appreciate about current ${topic} capabilities + +## Competitive Context +How customers compare our ${topic} to competitors + +## Recommendations for Product Team +- Priority 1 items (high frequency, high impact) +- Priority 2 items (moderate frequency or impact) +- Quick wins vs. larger investments`, + }, + }, + ], + }) + ); +} diff --git a/src/resources/index.ts b/src/resources/index.ts new file mode 100644 index 0000000..d4b4e1a --- /dev/null +++ b/src/resources/index.ts @@ -0,0 +1,325 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import type { + ChorusUser, + ChorusTeam, + ChorusScorecardTemplate, + ChorusSavedSearch, + ChorusConversation, + ChorusPlaylist, +} from "../types.js"; + +function extractParam(uri: URL, pattern: RegExp): string | null { + const match = uri.pathname.match(pattern); + return match ? match[1] : null; +} + +export function registerResources(server: McpServer): void { + // User Profile Resource + server.registerResource( + "chorus_user_profile", + "chorus://users/{user_id}", + { + description: + "Access a Chorus user profile by user ID. Returns name, email, role, team membership, and activity summary.", + mimeType: "application/json", + }, + async (uri: URL) => { + const userId = extractParam(uri, /\/users\/([^/]+)/); + if (!userId) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: "Error: Could not extract user_id from URI.", + }, + ], + }; + } + try { + const user = await makeApiRequest(`users/${userId}`); + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(user, null, 2), + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: handleApiError(error), + }, + ], + }; + } + } + ); + + // Team Structure Resource + server.registerResource( + "chorus_team", + "chorus://teams/{team_id}", + { + description: + "Access team structure including members, manager, and team hierarchy.", + mimeType: "application/json", + }, + async (uri: URL) => { + const teamId = extractParam(uri, /\/teams\/([^/]+)/); + if (!teamId) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: "Error: Could not extract team_id from URI.", + }, + ], + }; + } + try { + const team = await makeApiRequest(`teams/${teamId}`); + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(team, null, 2), + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: handleApiError(error), + }, + ], + }; + } + } + ); + + // Scorecard Template Resource + server.registerResource( + "chorus_scorecard_template", + "chorus://scorecard-templates/{template_id}", + { + description: + "Access a scorecard evaluation template with criteria definitions, scoring rubric, and expected behaviors.", + mimeType: "application/json", + }, + async (uri: URL) => { + const templateId = extractParam( + uri, + /\/scorecard-templates\/([^/]+)/ + ); + if (!templateId) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: "Error: Could not extract template_id from URI.", + }, + ], + }; + } + try { + const template = + await makeApiRequest( + `scorecards/templates/${templateId}` + ); + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(template, null, 2), + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: handleApiError(error), + }, + ], + }; + } + } + ); + + // Saved Search Resource + server.registerResource( + "chorus_saved_search", + "chorus://saved-searches/{search_id}", + { + description: + "Access a saved search query definition including filters, criteria, and configuration.", + mimeType: "application/json", + }, + async (uri: URL) => { + const searchId = extractParam(uri, /\/saved-searches\/([^/]+)/); + if (!searchId) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: "Error: Could not extract search_id from URI.", + }, + ], + }; + } + try { + const search = await makeApiRequest( + `saved-searches/${searchId}` + ); + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(search, null, 2), + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: handleApiError(error), + }, + ], + }; + } + } + ); + + // Conversation Summary Resource + server.registerResource( + "chorus_conversation_summary", + "chorus://conversations/{conversation_id}/summary", + { + description: + "Quick summary of a conversation including participants, duration, date, and key topics. Lighter weight than the full conversation tool.", + mimeType: "application/json", + }, + async (uri: URL) => { + const conversationId = extractParam( + uri, + /\/conversations\/([^/]+)\/summary/ + ); + if (!conversationId) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: "Error: Could not extract conversation_id from URI.", + }, + ], + }; + } + try { + const conversation = await makeApiRequest( + `conversations/${conversationId}` + ); + const summary = { + id: conversation.id, + title: conversation.title, + date: conversation.date, + duration: conversation.duration, + participantCount: conversation.participants.length, + participants: conversation.participants.map((p) => p.name), + type: conversation.type, + status: conversation.status, + }; + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(summary, null, 2), + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: handleApiError(error), + }, + ], + }; + } + } + ); + + // Playlist Resource + server.registerResource( + "chorus_playlist", + "chorus://playlists/{playlist_id}", + { + description: + "Access a coaching playlist with its moments list, descriptions, and associated calls.", + mimeType: "application/json", + }, + async (uri: URL) => { + const playlistId = extractParam(uri, /\/playlists\/([^/]+)/); + if (!playlistId) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: "Error: Could not extract playlist_id from URI.", + }, + ], + }; + } + try { + const playlist = await makeApiRequest( + `playlists/${playlistId}` + ); + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(playlist, null, 2), + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: uri.href, + mimeType: "text/plain", + text: handleApiError(error), + }, + ], + }; + } + } + ); +} diff --git a/src/schemas/common.ts b/src/schemas/common.ts new file mode 100644 index 0000000..acbce79 --- /dev/null +++ b/src/schemas/common.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; +import { ResponseFormat } from "../constants.js"; + +export const PaginationSchema = z.object({ + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe("Maximum results to return (1-100, default: 20)"), + offset: z + .number() + .int() + .min(0) + .default(0) + .describe("Number of results to skip for pagination (default: 0)"), +}); + +export const ResponseFormatSchema = z + .nativeEnum(ResponseFormat) + .default(ResponseFormat.MARKDOWN) + .describe( + "Output format: 'markdown' for human-readable or 'json' for machine-readable" + ); + +export const DateRangeSchema = z.object({ + start_date: z + .string() + .optional() + .describe("Start date in ISO 8601 format (e.g., 2024-01-01)"), + end_date: z + .string() + .optional() + .describe("End date in ISO 8601 format (e.g., 2024-12-31)"), +}); diff --git a/src/schemas/conversations.ts b/src/schemas/conversations.ts new file mode 100644 index 0000000..6335fb5 --- /dev/null +++ b/src/schemas/conversations.ts @@ -0,0 +1,72 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema, DateRangeSchema } from "./common.js"; + +export const ListConversationsSchema = z + .object({ + participant_email: z + .string() + .email() + .optional() + .describe("Filter by participant email address"), + team_id: z.string().optional().describe("Filter by team ID"), + response_format: ResponseFormatSchema, + }) + .merge(DateRangeSchema) + .merge(PaginationSchema) + .strict(); + +export type ListConversationsInput = z.infer; + +export const GetConversationSchema = z + .object({ + conversation_id: z.string().describe("The conversation/call ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetConversationInput = z.infer; + +export const GetTranscriptSchema = z + .object({ + conversation_id: z.string().describe("The conversation/call ID"), + speaker_filter: z + .string() + .optional() + .describe("Filter transcript to a specific speaker name"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetTranscriptInput = z.infer; + +export const GetConversationTrackersSchema = z + .object({ + conversation_id: z.string().describe("The conversation/call ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetConversationTrackersInput = z.infer; + +export const SearchConversationsSchema = z + .object({ + query: z + .string() + .min(1) + .describe("Search keyword or phrase to match in conversations"), + participant_email: z + .string() + .email() + .optional() + .describe("Filter by participant email address"), + tracker_name: z + .string() + .optional() + .describe("Filter by tracker name (e.g., competitor name)"), + response_format: ResponseFormatSchema, + }) + .merge(DateRangeSchema) + .merge(PaginationSchema) + .strict(); + +export type SearchConversationsInput = z.infer; diff --git a/src/schemas/emails.ts b/src/schemas/emails.ts new file mode 100644 index 0000000..8f6d9ce --- /dev/null +++ b/src/schemas/emails.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema, DateRangeSchema } from "./common.js"; + +export const ListEmailsSchema = z + .object({ + sender_email: z + .string() + .email() + .optional() + .describe("Filter by sender email address"), + recipient_email: z + .string() + .email() + .optional() + .describe("Filter by recipient email address"), + response_format: ResponseFormatSchema, + }) + .merge(DateRangeSchema) + .merge(PaginationSchema) + .strict(); + +export type ListEmailsInput = z.infer; + +export const GetEmailSchema = z + .object({ + email_id: z.string().describe("The email engagement ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetEmailInput = z.infer; diff --git a/src/schemas/engagements.ts b/src/schemas/engagements.ts new file mode 100644 index 0000000..2decb2c --- /dev/null +++ b/src/schemas/engagements.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema, DateRangeSchema } from "./common.js"; + +export const FilterEngagementsSchema = z + .object({ + engagement_type: z + .enum(["meeting", "dialer", "all"]) + .default("all") + .describe("Type of engagement to filter"), + participant_emails: z + .array(z.string().email()) + .optional() + .describe("Filter by participant email addresses"), + outcome: z + .string() + .optional() + .describe("Filter by engagement outcome"), + response_format: ResponseFormatSchema, + }) + .merge(DateRangeSchema) + .merge(PaginationSchema) + .strict(); + +export type FilterEngagementsInput = z.infer; + +export const GetEngagementSchema = z + .object({ + engagement_id: z.string().describe("The engagement ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetEngagementInput = z.infer; diff --git a/src/schemas/moments.ts b/src/schemas/moments.ts new file mode 100644 index 0000000..0453034 --- /dev/null +++ b/src/schemas/moments.ts @@ -0,0 +1,68 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema } from "./common.js"; + +export const ListMomentsSchema = z + .object({ + conversation_id: z + .string() + .optional() + .describe("Filter moments by conversation ID"), + start_date: z + .string() + .optional() + .describe("Start date for shared_on filter (ISO 8601, e.g. 2024-01-01T00:00:00.000Z). Defaults to 90 days ago."), + end_date: z + .string() + .optional() + .describe("End date for shared_on filter (ISO 8601, e.g. 2024-12-31T23:59:59.000Z). Defaults to now."), + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListMomentsInput = z.infer; + +export const GetMomentSchema = z + .object({ + moment_id: z.string().describe("The moment ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetMomentInput = z.infer; + +export const CreateMomentSchema = z + .object({ + conversation_id: z + .string() + .describe("The conversation ID to add the moment to"), + timestamp_ms: z + .number() + .int() + .min(0) + .describe("Timestamp in milliseconds from the start of the call"), + duration_ms: z + .number() + .int() + .min(0) + .optional() + .describe("Duration of the moment in milliseconds"), + title: z.string().max(200).describe("Title/label for the moment"), + description: z + .string() + .max(2000) + .optional() + .describe("Description of the moment"), + type: z.string().optional().describe("Moment type/category"), + }) + .strict(); + +export type CreateMomentInput = z.infer; + +export const DeleteMomentSchema = z + .object({ + moment_id: z.string().describe("The moment ID to delete"), + }) + .strict(); + +export type DeleteMomentInput = z.infer; diff --git a/src/schemas/playlists.ts b/src/schemas/playlists.ts new file mode 100644 index 0000000..54adf91 --- /dev/null +++ b/src/schemas/playlists.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema } from "./common.js"; + +export const ListPlaylistsSchema = z + .object({ + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListPlaylistsInput = z.infer; + +export const GetPlaylistSchema = z + .object({ + playlist_id: z.string().describe("The playlist ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetPlaylistInput = z.infer; + +export const ListPlaylistMomentsSchema = z + .object({ + playlist_id: z.string().describe("The playlist ID"), + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListPlaylistMomentsInput = z.infer; diff --git a/src/schemas/reports.ts b/src/schemas/reports.ts new file mode 100644 index 0000000..4293b38 --- /dev/null +++ b/src/schemas/reports.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema, DateRangeSchema } from "./common.js"; + +export const ListReportsSchema = z + .object({ + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListReportsInput = z.infer; + +export const GetReportSchema = z + .object({ + report_id: z.string().describe("The report ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetReportInput = z.infer; + +export const GetActivityMetricsSchema = z + .object({ + team_id: z.string().optional().describe("Filter metrics by team ID"), + user_id: z + .string() + .optional() + .describe("Filter metrics for a specific user"), + start_date: z + .string() + .describe("Start date for the metrics period (ISO 8601)"), + end_date: z + .string() + .describe("End date for the metrics period (ISO 8601)"), + metrics: z + .array( + z.enum([ + "total_calls", + "total_duration", + "avg_duration", + "talk_ratio", + "longest_monologue", + "interactivity", + "patience", + "question_rate", + ]) + ) + .optional() + .describe("Specific metrics to include; omit for all"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetActivityMetricsInput = z.infer; diff --git a/src/schemas/saved-searches.ts b/src/schemas/saved-searches.ts new file mode 100644 index 0000000..0f73287 --- /dev/null +++ b/src/schemas/saved-searches.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema } from "./common.js"; + +export const ListSavedSearchesSchema = z + .object({ + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListSavedSearchesInput = z.infer; + +export const GetSavedSearchSchema = z + .object({ + search_id: z.string().describe("The saved search ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetSavedSearchInput = z.infer; + +export const ExecuteSavedSearchSchema = z + .object({ + search_id: z.string().describe("The saved search ID to execute"), + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ExecuteSavedSearchInput = z.infer; diff --git a/src/schemas/scorecards.ts b/src/schemas/scorecards.ts new file mode 100644 index 0000000..a46ef71 --- /dev/null +++ b/src/schemas/scorecards.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema, DateRangeSchema } from "./common.js"; + +export const ListScorecardsSchema = z + .object({ + user_id: z + .string() + .optional() + .describe("Filter scorecards by the evaluated rep's user ID"), + conversation_id: z + .string() + .optional() + .describe("Filter scorecards for a specific conversation"), + template_id: z + .string() + .optional() + .describe("Filter by scorecard template ID"), + response_format: ResponseFormatSchema, + }) + .merge(DateRangeSchema) + .merge(PaginationSchema) + .strict(); + +export type ListScorecardsInput = z.infer; + +export const GetScorecardSchema = z + .object({ + scorecard_id: z.string().describe("The scorecard ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetScorecardInput = z.infer; + +export const ListScorecardTemplatesSchema = z + .object({ + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListScorecardTemplatesInput = z.infer; + +export const GetScorecardTemplateSchema = z + .object({ + template_id: z.string().describe("The scorecard template ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetScorecardTemplateInput = z.infer; diff --git a/src/schemas/teams.ts b/src/schemas/teams.ts new file mode 100644 index 0000000..672b536 --- /dev/null +++ b/src/schemas/teams.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema } from "./common.js"; + +export const ListTeamsSchema = z + .object({ + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListTeamsInput = z.infer; + +export const GetTeamSchema = z + .object({ + team_id: z.string().describe("The team ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetTeamInput = z.infer; + +export const GetTeamMembersSchema = z + .object({ + team_id: z.string().describe("The team ID"), + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type GetTeamMembersInput = z.infer; diff --git a/src/schemas/users.ts b/src/schemas/users.ts new file mode 100644 index 0000000..de654a7 --- /dev/null +++ b/src/schemas/users.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema } from "./common.js"; + +export const ListUsersSchema = z + .object({ + team_id: z.string().optional().describe("Filter users by team ID"), + role: z + .string() + .optional() + .describe("Filter by role (e.g., 'admin', 'user', 'manager')"), + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type ListUsersInput = z.infer; + +export const GetUserSchema = z + .object({ + user_id: z.string().describe("The Chorus user ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetUserInput = z.infer; + +export const SearchUsersSchema = z + .object({ + query: z + .string() + .min(1) + .max(200) + .describe("Search string to match against user names or emails"), + response_format: ResponseFormatSchema, + }) + .merge(PaginationSchema) + .strict(); + +export type SearchUsersInput = z.infer; diff --git a/src/schemas/video-conferences.ts b/src/schemas/video-conferences.ts new file mode 100644 index 0000000..f201db6 --- /dev/null +++ b/src/schemas/video-conferences.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; +import { PaginationSchema, ResponseFormatSchema, DateRangeSchema } from "./common.js"; + +export const ListVideoConferencesSchema = z + .object({ + response_format: ResponseFormatSchema, + }) + .merge(DateRangeSchema) + .merge(PaginationSchema) + .strict(); + +export type ListVideoConferencesInput = z.infer; + +export const GetVideoConferenceSchema = z + .object({ + conference_id: z.string().describe("The video conference ID"), + response_format: ResponseFormatSchema, + }) + .strict(); + +export type GetVideoConferenceInput = z.infer; + +export const UploadRecordingSchema = z + .object({ + title: z.string().max(500).describe("Title for the recording"), + recording_url: z + .string() + .url() + .describe("URL of the recording file to upload"), + participants: z + .array( + z.object({ + name: z.string().describe("Participant name"), + email: z + .string() + .email() + .optional() + .describe("Participant email"), + }) + ) + .min(1) + .describe("List of participants in the recording"), + date: z.string().describe("Date of the recording (ISO 8601)"), + duration_seconds: z + .number() + .int() + .min(0) + .optional() + .describe("Duration in seconds"), + external_id: z + .string() + .optional() + .describe("External reference ID from your system"), + }) + .strict(); + +export type UploadRecordingInput = z.infer; + +export const DeleteRecordingSchema = z + .object({ + conference_id: z + .string() + .describe("The video conference/recording ID to delete"), + }) + .strict(); + +export type DeleteRecordingInput = z.infer; diff --git a/src/services/api-client.ts b/src/services/api-client.ts new file mode 100644 index 0000000..ea6c6b3 --- /dev/null +++ b/src/services/api-client.ts @@ -0,0 +1,129 @@ +import axios, { type AxiosRequestConfig } from "axios"; +import { API_BASE_URL } from "../constants.js"; + +function getApiKey(): string { + const key = process.env.CHORUS_API_KEY; + if (!key) { + throw new Error( + "CHORUS_API_KEY environment variable is not set. " + + "Generate an API token from your Chorus Personal Settings page." + ); + } + return key; +} + +/** + * JSON:API record returned by the Chorus API. + */ +interface JsonApiRecord { + id: string; + type: string; + attributes: Record; +} + +interface JsonApiListResponse { + data: JsonApiRecord[]; + meta?: { page?: { cursor?: string; total?: number } }; +} + +interface JsonApiSingleResponse { + data: JsonApiRecord; +} + +/** + * Detect whether a response body is JSON:API format. + */ +function isJsonApiList(body: unknown): body is JsonApiListResponse { + return ( + typeof body === "object" && + body !== null && + "data" in body && + Array.isArray((body as JsonApiListResponse).data) && + (body as JsonApiListResponse).data.length > 0 && + "attributes" in (body as JsonApiListResponse).data[0] + ); +} + +function isJsonApiSingle(body: unknown): body is JsonApiSingleResponse { + return ( + typeof body === "object" && + body !== null && + "data" in body && + !Array.isArray((body as JsonApiSingleResponse).data) && + typeof (body as JsonApiSingleResponse).data === "object" && + (body as JsonApiSingleResponse).data !== null && + "attributes" in (body as JsonApiSingleResponse).data + ); +} + +/** + * Flatten a single JSON:API record into a plain object with id at the top. + */ +function flattenRecord(record: JsonApiRecord): Record { + return { id: record.id, ...record.attributes }; +} + +/** + * Auto-normalize a Chorus API response: + * - JSON:API list -> { items: [...flattened], total, cursor } + * - JSON:API single -> flattened record + * - Already plain -> return as-is + */ +function normalizeResponse(body: unknown): unknown { + if (isJsonApiList(body)) { + const items = body.data.map(flattenRecord); + return { + items, + total: body.meta?.page?.total ?? items.length, + cursor: body.meta?.page?.cursor, + }; + } + + if (isJsonApiSingle(body)) { + return flattenRecord(body.data); + } + + // Handle empty JSON:API response: { data: [] } + if ( + typeof body === "object" && + body !== null && + "data" in body && + Array.isArray((body as { data: unknown[] }).data) && + (body as { data: unknown[] }).data.length === 0 + ) { + return { items: [], total: 0 }; + } + + return body; +} + +export async function makeApiRequest( + endpoint: string, + method: "GET" | "POST" | "PUT" | "DELETE" = "GET", + data?: unknown, + params?: Record +): Promise { + const apiKey = getApiKey(); + + const config: AxiosRequestConfig = { + method, + url: `${API_BASE_URL}/${endpoint}`, + headers: { + Authorization: apiKey, + "Content-Type": "application/json", + Accept: "application/json", + }, + timeout: 30000, + }; + + if (data !== undefined) { + config.data = data; + } + + if (params !== undefined) { + config.params = params; + } + + const response = await axios(config); + return normalizeResponse(response.data) as T; +} diff --git a/src/services/error-handler.ts b/src/services/error-handler.ts new file mode 100644 index 0000000..d2e9f71 --- /dev/null +++ b/src/services/error-handler.ts @@ -0,0 +1,39 @@ +import axios from "axios"; + +export function handleApiError(error: unknown): string { + if (axios.isAxiosError(error)) { + if (error.response) { + const status = error.response.status; + const message = + typeof error.response.data === "object" && error.response.data !== null + ? (error.response.data as Record).message ?? + (error.response.data as Record).error ?? + "" + : ""; + + switch (status) { + case 400: + return `Error: Bad request. ${message ? String(message) : "Check your parameters and try again."}`; + case 401: + return "Error: Authentication failed. Check your CHORUS_API_KEY environment variable. Generate a token from Chorus Personal Settings."; + case 403: + return "Error: Access denied. Your API key may lack permissions for this resource, or the recording is marked as private."; + case 404: + return "Error: Resource not found. Verify the ID is correct and that you have access to this resource."; + case 429: + return "Error: Rate limit exceeded. Wait a moment before making more requests."; + default: + if (status >= 500) { + return `Error: Chorus API server error (${status}). Try again later.`; + } + return `Error: API request failed with status ${status}. ${message ? String(message) : ""}`; + } + } else if (error.code === "ECONNABORTED") { + return "Error: Request timed out. The Chorus API did not respond within 30 seconds. Try again."; + } else if (error.code === "ECONNREFUSED") { + return "Error: Could not connect to the Chorus API. Check your network connection."; + } + } + + return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`; +} diff --git a/src/services/formatters.ts b/src/services/formatters.ts new file mode 100644 index 0000000..fee547f --- /dev/null +++ b/src/services/formatters.ts @@ -0,0 +1,100 @@ +import { CHARACTER_LIMIT, ResponseFormat } from "../constants.js"; +import type { ChorusParticipant, PaginatedResponse } from "../types.js"; + +export function truncateResponse(text: string): string { + if (text.length <= CHARACTER_LIMIT) { + return text; + } + const truncated = text.slice(0, CHARACTER_LIMIT); + return ( + truncated + + "\n\n---\n*Response truncated. Use `limit` and `offset` parameters or add filters to see more results.*" + ); +} + +export function formatDate(isoString: string): string { + try { + const date = new Date(isoString); + if (isNaN(date.getTime())) { + return isoString; + } + return date.toLocaleString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + } catch { + return isoString; + } +} + +export function formatDuration(seconds: number): string { + if (seconds < 60) { + return `${seconds}s`; + } + const minutes = Math.floor(seconds / 60); + const remaining = seconds % 60; + if (minutes < 60) { + return remaining > 0 ? `${minutes}m ${remaining}s` : `${minutes}m`; + } + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + return remainingMinutes > 0 + ? `${hours}h ${remainingMinutes}m` + : `${hours}h`; +} + +export function formatParticipants(participants: ChorusParticipant[]): string { + if (participants.length === 0) return "None"; + if (participants.length <= 3) { + return participants.map((p) => p.name).join(", "); + } + const shown = participants.slice(0, 3).map((p) => p.name); + return `${shown.join(", ")} (+${participants.length - 3} others)`; +} + +export function formatPaginationInfo( + response: PaginatedResponse +): string { + const lines: string[] = []; + lines.push( + `Showing ${response.count} of ${response.total} results (offset: ${response.offset})` + ); + if (response.has_more && response.next_offset !== undefined) { + lines.push(`More results available. Use offset=${response.next_offset}`); + } + return lines.join("\n"); +} + +export function buildPaginatedResponse( + items: T[], + total: number, + offset: number +): PaginatedResponse { + return { + total, + count: items.length, + offset, + items, + has_more: total > offset + items.length, + ...(total > offset + items.length + ? { next_offset: offset + items.length } + : {}), + }; +} + +export function formatOutput( + data: unknown, + markdownFormatter: () => string, + responseFormat: ResponseFormat +): string { + let text: string; + if (responseFormat === ResponseFormat.MARKDOWN) { + text = markdownFormatter(); + } else { + text = JSON.stringify(data, null, 2); + } + return truncateResponse(text); +} diff --git a/src/tools/conversations.ts b/src/tools/conversations.ts new file mode 100644 index 0000000..9f3acb9 --- /dev/null +++ b/src/tools/conversations.ts @@ -0,0 +1,418 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { + formatOutput, + formatDate, + formatDuration, + formatParticipants, + formatPaginationInfo, + buildPaginatedResponse, +} from "../services/formatters.js"; +import type { ListResponse, ChorusParticipant } from "../types.js"; +import { + ListConversationsSchema, + GetConversationSchema, + GetTranscriptSchema, + GetConversationTrackersSchema, + SearchConversationsSchema, + type ListConversationsInput, + type GetConversationInput, + type GetTranscriptInput, + type GetConversationTrackersInput, + type SearchConversationsInput, +} from "../schemas/conversations.js"; + +export function registerConversationTools(server: McpServer): void { + server.registerTool( + "chorus_list_conversations", + { + title: "List Chorus Conversations", + description: `List conversations (calls and meetings) in Chorus with optional filters. + +Args: + - start_date (string, optional): Filter conversations after this date (ISO 8601) + - end_date (string, optional): Filter conversations before this date (ISO 8601) + - participant_email (string, optional): Filter by participant email + - team_id (string, optional): Filter by team ID + - limit (number): Max results 1-100 (default: 20) + - offset (number): Pagination offset (default: 0) + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Paginated list of conversations with title, date, duration, and participants.`, + inputSchema: ListConversationsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListConversationsInput) => { + try { + const queryParams: Record = { + "page[size]": params.limit, + }; + if (params.start_date) queryParams["filter[start_date]"] = params.start_date; + if (params.end_date) queryParams["filter[end_date]"] = params.end_date; + if (params.participant_email) + queryParams["filter[participant_email]"] = params.participant_email; + if (params.team_id) queryParams["filter[team_id]"] = params.team_id; + + const data = await makeApiRequest("conversations", "GET", undefined, queryParams); + + const conversations = data.items || []; + const total = data.total || conversations.length; + const response = buildPaginatedResponse( + conversations, + total, + params.offset + ); + + if (conversations.length === 0) { + return { + content: [ + { type: "text" as const, text: "No conversations found matching the specified filters." }, + ], + }; + } + + const text = formatOutput( + response, + () => { + const lines: string[] = ["# Conversations", ""]; + lines.push(formatPaginationInfo(response)); + lines.push(""); + for (const c of conversations) { + const title = (c.title as string) || "Untitled"; + const id = c.id as string; + const date = c.date as string; + const duration = c.duration as number; + const participants = (c.participants || []) as unknown as ChorusParticipant[]; + const status = c.status as string | undefined; + lines.push(`## ${title} (${id})`); + lines.push(`- **Date**: ${formatDate(date)}`); + lines.push(`- **Duration**: ${formatDuration(duration)}`); + lines.push( + `- **Participants**: ${formatParticipants(participants)}` + ); + if (status) lines.push(`- **Status**: ${status}`); + lines.push(""); + } + return lines.join("\n"); + }, + params.response_format + ); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { + content: [{ type: "text" as const, text: handleApiError(error) }], + }; + } + } + ); + + server.registerTool( + "chorus_get_conversation", + { + title: "Get Chorus Conversation", + description: `Get full details of a single conversation including metadata, participants, duration, and summary. + +Args: + - conversation_id (string): The conversation/call ID + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Conversation details with title, date, duration, participants, summary, and recording info.`, + inputSchema: GetConversationSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetConversationInput) => { + try { + const conversation = await makeApiRequest>( + `conversations/${params.conversation_id}` + ); + + const text = formatOutput( + conversation, + () => { + const title = (conversation.title as string) || "Untitled Conversation"; + const id = conversation.id as string; + const date = conversation.date as string; + const duration = conversation.duration as number; + const participants = (conversation.participants || []) as unknown as ChorusParticipant[]; + const type = conversation.type as string | undefined; + const status = conversation.status as string | undefined; + const summary = conversation.summary as string | undefined; + const lines: string[] = [ + `# ${title}`, + "", + `- **ID**: ${id}`, + `- **Date**: ${formatDate(date)}`, + `- **Duration**: ${formatDuration(duration)}`, + `- **Participants**: ${formatParticipants(participants)}`, + ]; + if (type) lines.push(`- **Type**: ${type}`); + if (status) + lines.push(`- **Status**: ${status}`); + if (summary) { + lines.push("", "## Summary", summary); + } + if (participants.length > 0) { + lines.push("", "## Participants"); + for (const p of participants) { + const pName = (p.name as string) || "Unknown"; + const pEmail = p.email as string | undefined; + const pRole = p.role as string | undefined; + lines.push( + `- **${pName}**${pEmail ? ` (${pEmail})` : ""}${pRole ? ` - ${pRole}` : ""}` + ); + } + } + return lines.join("\n"); + }, + params.response_format + ); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { + content: [{ type: "text" as const, text: handleApiError(error) }], + }; + } + } + ); + + server.registerTool( + "chorus_get_transcript", + { + title: "Get Conversation Transcript", + description: `Retrieve the full transcript of a conversation with speaker attribution and timestamps. + +Args: + - conversation_id (string): The conversation/call ID + - speaker_filter (string, optional): Filter transcript to a specific speaker name + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Speaker-attributed transcript with timestamps. If speaker_filter is set, only that speaker's segments are returned.`, + inputSchema: GetTranscriptSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetTranscriptInput) => { + try { + const data = await makeApiRequest( + `conversations/${params.conversation_id}/transcript` + ); + + let segments = data.items || []; + + if (params.speaker_filter) { + const filter = params.speaker_filter.toLowerCase(); + segments = segments.filter((s) => + ((s.speaker as string) || "").toLowerCase().includes(filter) + ); + } + + if (segments.length === 0) { + return { + content: [ + { + type: "text" as const, + text: params.speaker_filter + ? `No transcript segments found for speaker "${params.speaker_filter}".` + : "No transcript available for this conversation.", + }, + ], + }; + } + + const text = formatOutput( + { segments }, + () => { + const lines: string[] = ["# Transcript", ""]; + if (params.speaker_filter) { + lines.push( + `*Filtered to speaker: ${params.speaker_filter}*`, + "" + ); + } + for (const seg of segments) { + const startTime = seg.startTime as number; + const speaker = (seg.speaker as string) || "Unknown"; + const segText = (seg.text as string) || ""; + const time = formatDuration(Math.floor(startTime / 1000)); + lines.push(`**[${time}] ${speaker}**: ${segText}`, ""); + } + return lines.join("\n"); + }, + params.response_format + ); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { + content: [{ type: "text" as const, text: handleApiError(error) }], + }; + } + } + ); + + server.registerTool( + "chorus_get_conversation_trackers", + { + title: "Get Conversation Trackers", + description: `Get tracker hits (competitor mentions, keywords, topics) detected in a conversation. + +Args: + - conversation_id (string): The conversation/call ID + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: List of trackers with name, category, hit count, and occurrences with timestamps and text.`, + inputSchema: GetConversationTrackersSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetConversationTrackersInput) => { + try { + const data = await makeApiRequest( + `conversations/${params.conversation_id}/trackers` + ); + + const trackers = data.items || []; + + if (trackers.length === 0) { + return { + content: [ + { + type: "text" as const, + text: "No trackers detected in this conversation.", + }, + ], + }; + } + + const text = formatOutput( + { trackers }, + () => { + const lines: string[] = ["# Conversation Trackers", ""]; + for (const t of trackers) { + const name = (t.name as string) || "Unknown"; + const category = t.category as string | undefined; + const count = t.count as number; + const occurrences = (t.occurrences || []) as Array>; + lines.push( + `## ${name}${category ? ` (${category})` : ""}` + ); + lines.push(`- **Occurrences**: ${count}`); + if (occurrences.length > 0) { + lines.push("- **Instances**:"); + for (const occ of occurrences) { + const timestamp = occ.timestamp as number; + const occText = (occ.text as string) || ""; + const time = formatDuration( + Math.floor(timestamp / 1000) + ); + lines.push(` - [${time}]: "${occText}"`); + } + } + lines.push(""); + } + return lines.join("\n"); + }, + params.response_format + ); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { + content: [{ type: "text" as const, text: handleApiError(error) }], + }; + } + } + ); + + server.registerTool( + "chorus_search_conversations", + { + title: "Search Chorus Conversations", + description: `Search conversations by keyword, tracker, participant, or date range. + +Args: + - query (string): Search keyword or phrase + - participant_email (string, optional): Filter by participant email + - tracker_name (string, optional): Filter by tracker name + - start_date (string, optional): Filter after this date (ISO 8601) + - end_date (string, optional): Filter before this date (ISO 8601) + - limit (number): Max results 1-100 (default: 20) + - offset (number): Pagination offset (default: 0) + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Paginated list of matching conversations with relevance ranking.`, + inputSchema: SearchConversationsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: SearchConversationsInput) => { + try { + const queryParams: Record = { + q: params.query, + "page[size]": params.limit, + }; + if (params.start_date) queryParams["filter[start_date]"] = params.start_date; + if (params.end_date) queryParams["filter[end_date]"] = params.end_date; + if (params.participant_email) + queryParams["filter[participant_email]"] = params.participant_email; + if (params.tracker_name) queryParams["filter[tracker_name]"] = params.tracker_name; + + const data = await makeApiRequest("conversations/search", "GET", undefined, queryParams); + + const conversations = data.items || []; + const total = data.total || conversations.length; + const response = buildPaginatedResponse( + conversations, + total, + params.offset + ); + + if (conversations.length === 0) { + return { + content: [ + { + type: "text" as const, + text: `No conversations found matching "${params.query}".`, + }, + ], + }; + } + + const text = formatOutput( + response, + () => { + const lines: string[] = [ + `# Search Results: "${params.query}"`, + "", + ]; + lines.push(formatPaginationInfo(response)); + lines.push(""); + for (const c of conversations) { + const title = (c.title as string) || "Untitled"; + const id = c.id as string; + const date = c.date as string; + const duration = c.duration as number; + const participants = (c.participants || []) as unknown as ChorusParticipant[]; + lines.push(`## ${title} (${id})`); + lines.push(`- **Date**: ${formatDate(date)}`); + lines.push(`- **Duration**: ${formatDuration(duration)}`); + lines.push( + `- **Participants**: ${formatParticipants(participants)}` + ); + lines.push(""); + } + return lines.join("\n"); + }, + params.response_format + ); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { + content: [{ type: "text" as const, text: handleApiError(error) }], + }; + } + } + ); +} diff --git a/src/tools/emails.ts b/src/tools/emails.ts new file mode 100644 index 0000000..ce468d3 --- /dev/null +++ b/src/tools/emails.ts @@ -0,0 +1,122 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDate, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListEmailsSchema, GetEmailSchema, + type ListEmailsInput, type GetEmailInput, +} from "../schemas/emails.js"; + +export function registerEmailTools(server: McpServer): void { + server.registerTool( + "chorus_list_emails", + { + title: "List Email Engagements", + description: `List email engagements tracked in Chorus with optional filters. + +Args: + - sender_email (string, optional): Filter by sender email + - recipient_email (string, optional): Filter by recipient email + - start_date/end_date (string, optional): Date range (ISO 8601) + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of email engagements with subject, date, and metrics.`, + inputSchema: ListEmailsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListEmailsInput) => { + try { + const qp: Record = { "page[size]": params.limit }; + if (params.sender_email) qp["filter[sender_email]"] = params.sender_email; + if (params.recipient_email) qp["filter[recipient_email]"] = params.recipient_email; + if (params.start_date) qp["filter[start_date]"] = params.start_date; + if (params.end_date) qp["filter[end_date]"] = params.end_date; + + const data = await makeApiRequest("emails", "GET", undefined, qp); + const emails = data.items || []; + const response = buildPaginatedResponse(emails, data.total || emails.length, params.offset); + + if (emails.length === 0) { + return { content: [{ type: "text" as const, text: "No email engagements found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Email Engagements", "", formatPaginationInfo(response), ""]; + for (const e of emails) { + const subject = (e.name as string) || "No Subject"; + const id = e.id as string; + const emailData = e.email as Record | undefined; + const initiator = emailData?.initiator as Record | undefined; + const sender = (initiator?.name as string) || (initiator?.email as string) || "Unknown"; + const sentTime = (emailData?.sent_time as string) || ""; + const company = (e.company_name as string) || ""; + lines.push(`- **${subject}** (${id}) - ${sentTime ? formatDate(sentTime) : "N/A"} from ${sender}${company ? ` | ${company}` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_email", + { + title: "Get Email Engagement", + description: `Get details of a specific email engagement including metrics. + +Args: + - email_id (string): The email engagement ID + - response_format: Output format + +Returns: Email details with subject, sender, recipients, date, and engagement metrics.`, + inputSchema: GetEmailSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetEmailInput) => { + try { + const email = await makeApiRequest>(`emails/${params.email_id}`); + + const text = formatOutput(email, () => { + const subject = (email.name as string) || "No Subject"; + const id = email.id as string; + const emailData = email.email as Record | undefined; + const initiator = emailData?.initiator as Record | undefined; + const sender = (initiator?.name as string) || (initiator?.email as string) || "Unknown"; + const sentTime = (emailData?.sent_time as string) || ""; + const owner = email.owner as Record | undefined; + const participants = (email.participants || []) as Array>; + const company = (email.company_name as string) || ""; + const lines: string[] = [ + `# ${subject}`, "", + `- **ID**: ${id}`, + `- **Date**: ${sentTime ? formatDate(sentTime) : "N/A"}`, + `- **From**: ${sender}`, + `- **Company**: ${company}`, + ]; + if (owner) { + lines.push(`- **Owner**: ${owner.name} (${owner.email})`); + } + if (participants.length > 0) { + lines.push("", "## Participants"); + for (const p of participants) { + const role = (p.type as string) || ""; + lines.push(`- **${p.name}** (${p.email})${role ? ` [${role}]` : ""}${p.company_name ? ` | ${p.company_name}` : ""}`); + } + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/engagements.ts b/src/tools/engagements.ts new file mode 100644 index 0000000..63465b0 --- /dev/null +++ b/src/tools/engagements.ts @@ -0,0 +1,117 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDate, formatDuration, formatParticipants, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse, ChorusParticipant } from "../types.js"; +import { + FilterEngagementsSchema, GetEngagementSchema, + type FilterEngagementsInput, type GetEngagementInput, +} from "../schemas/engagements.js"; + +export function registerEngagementTools(server: McpServer): void { + server.registerTool( + "chorus_filter_engagements", + { + title: "Filter Engagements", + description: `Filter and search engagements (meetings and dialer calls) using advanced criteria. + +Args: + - engagement_type ('meeting'|'dialer'|'all'): Type filter (default: 'all') + - participant_emails (string[], optional): Filter by participant emails + - outcome (string, optional): Filter by outcome + - start_date/end_date (string, optional): Date range (ISO 8601) + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of matching engagements with type, date, participants, and outcome.`, + inputSchema: FilterEngagementsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: FilterEngagementsInput) => { + try { + const body: Record = { "page[size]": params.limit }; + if (params.engagement_type !== "all") body["filter[type]"] = params.engagement_type; + if (params.participant_emails) body["filter[participant_emails]"] = params.participant_emails; + if (params.outcome) body["filter[outcome]"] = params.outcome; + if (params.start_date) body["filter[start_date]"] = params.start_date; + if (params.end_date) body["filter[end_date]"] = params.end_date; + + const data = await makeApiRequest("engagements/filter", "POST", body); + const engagements = data.items || []; + const response = buildPaginatedResponse(engagements, data.total || engagements.length, params.offset); + + if (engagements.length === 0) { + return { content: [{ type: "text" as const, text: "No engagements found matching the specified filters." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Engagements", "", formatPaginationInfo(response), ""]; + for (const e of engagements) { + const type = (e.type as string) || "Unknown"; + const date = e.date as string; + const id = e.id as string; + const participants = (e.participants || []) as unknown as ChorusParticipant[]; + const duration = e.duration as number | undefined; + const outcome = e.outcome as string | undefined; + lines.push(`## ${type} - ${formatDate(date)} (${id})`); + lines.push(`- **Participants**: ${formatParticipants(participants)}`); + if (duration) lines.push(`- **Duration**: ${formatDuration(duration)}`); + if (outcome) lines.push(`- **Outcome**: ${outcome}`); + lines.push(""); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_engagement", + { + title: "Get Engagement", + description: `Get details of a specific engagement. + +Args: + - engagement_id (string): The engagement ID + - response_format: Output format + +Returns: Engagement details with type, date, participants, duration, and outcome.`, + inputSchema: GetEngagementSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetEngagementInput) => { + try { + const eng = await makeApiRequest>(`engagements/${params.engagement_id}`); + + const text = formatOutput(eng, () => { + const type = (eng.type as string) || "Unknown"; + const id = eng.id as string; + const date = eng.date as string; + const participants = (eng.participants || []) as unknown as ChorusParticipant[]; + const duration = eng.duration as number | undefined; + const outcome = eng.outcome as string | undefined; + const conversationId = eng.conversationId as string | undefined; + const lines: string[] = [ + `# ${type} Engagement`, "", + `- **ID**: ${id}`, + `- **Date**: ${formatDate(date)}`, + `- **Participants**: ${formatParticipants(participants)}`, + ]; + if (duration) lines.push(`- **Duration**: ${formatDuration(duration)}`); + if (outcome) lines.push(`- **Outcome**: ${outcome}`); + if (conversationId) lines.push(`- **Conversation ID**: ${conversationId}`); + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/integrations.ts b/src/tools/integrations.ts new file mode 100644 index 0000000..f5809fd --- /dev/null +++ b/src/tools/integrations.ts @@ -0,0 +1,140 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import { ResponseFormatSchema, PaginationSchema } from "../schemas/common.js"; +import type { ListResponse } from "../types.js"; + +const ListIntegrationsSchema = z.object({ + response_format: ResponseFormatSchema, +}).merge(PaginationSchema).strict(); + +const GetIntegrationSchema = z.object({ + integration_id: z.string().describe("The integration ID"), + response_format: ResponseFormatSchema, +}).strict(); + +const GetSessionSchema = z.object({ + response_format: ResponseFormatSchema, +}).strict(); + +export function registerIntegrationTools(server: McpServer): void { + server.registerTool( + "chorus_list_integrations", + { + title: "List Integrations", + description: `List configured integrations (Salesforce, HubSpot, etc.) in Chorus. + +Args: + - limit/offset: Pagination + - response_format: Output format + +Returns: List of integrations with type, name, and status.`, + inputSchema: ListIntegrationsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: z.infer) => { + try { + const data = await makeApiRequest( + "integrations", "GET", undefined, { "page[size]": params.limit } + ); + const integrations = data.items || []; + const response = buildPaginatedResponse(integrations, data.total || integrations.length, params.offset); + + if (integrations.length === 0) { + return { content: [{ type: "text" as const, text: "No integrations configured." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Integrations", "", formatPaginationInfo(response), ""]; + for (const i of integrations) { + const name = (i.name as string) || "Untitled"; + const id = i.id as string; + const type = (i.type as string) || "Unknown"; + const status = (i.status as string) || "Unknown"; + lines.push(`- **${name}** (${id}) [${type}] - Status: ${status}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_integration", + { + title: "Get Integration", + description: `Get details and status of a specific integration. + +Args: + - integration_id (string): The integration ID + - response_format: Output format + +Returns: Integration details with type, name, status, and configuration.`, + inputSchema: GetIntegrationSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: z.infer) => { + try { + const integ = await makeApiRequest>(`integrations/${params.integration_id}`); + + const text = formatOutput(integ, () => { + const name = (integ.name as string) || "Untitled"; + const id = integ.id as string; + const type = (integ.type as string) || "Unknown"; + const status = (integ.status as string) || "Unknown"; + const config = integ.config as Record | undefined; + const lines: string[] = [ + `# ${name}`, "", + `- **ID**: ${id}`, + `- **Type**: ${type}`, + `- **Status**: ${status}`, + ]; + if (config) { + lines.push("", "## Configuration", "```json", JSON.stringify(config, null, 2), "```"); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_session", + { + title: "Get Current Session", + description: `Get current API session details and permissions. Useful for verifying authentication and understanding access scope. + +Args: + - response_format: Output format + +Returns: Session info with user details and permission scope.`, + inputSchema: GetSessionSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: z.infer) => { + try { + const session = await makeApiRequest>("sessions/current"); + + const text = formatOutput(session, () => { + const lines: string[] = ["# Current Session", "", "```json", JSON.stringify(session, null, 2), "```"]; + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/moments.ts b/src/tools/moments.ts new file mode 100644 index 0000000..a8b4d30 --- /dev/null +++ b/src/tools/moments.ts @@ -0,0 +1,184 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDate, formatDuration, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListMomentsSchema, GetMomentSchema, CreateMomentSchema, DeleteMomentSchema, + type ListMomentsInput, type GetMomentInput, type CreateMomentInput, type DeleteMomentInput, +} from "../schemas/moments.js"; + +export function registerMomentTools(server: McpServer): void { + server.registerTool( + "chorus_list_moments", + { + title: "List External Moments", + description: `List external moments across conversations. + +Args: + - conversation_id (string, optional): Filter by conversation ID + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of moments with timestamps and descriptions.`, + inputSchema: ListMomentsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListMomentsInput) => { + try { + const qp: Record = { "page[size]": params.limit }; + if (params.conversation_id) qp["filter[conversation_id]"] = params.conversation_id; + + // Chorus API requires filter[shared_on] as a date range "start:end" + const endDate = params.end_date || new Date().toISOString().replace(/Z$/, "").split(".")[0] + ".000Z"; + const startDate = params.start_date || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().replace(/Z$/, "").split(".")[0] + ".000Z"; + qp["filter[shared_on]"] = `${startDate}:${endDate}`; + + const data = await makeApiRequest("moments", "GET", undefined, qp); + const moments = data.items || []; + const response = buildPaginatedResponse(moments, data.total || moments.length, params.offset); + + if (moments.length === 0) { + return { content: [{ type: "text" as const, text: "No moments found in the specified date range." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Shared Moments", "", formatPaginationInfo(response), ""]; + for (const m of moments) { + const subject = (m.subject as string) || "Untitled"; + const id = m.id as string; + const conversation = (m.conversation as string) || ""; + const sharedOn = (m.shared_on as string) || ""; + const duration = m.duration as number | undefined; + const creator = m.creator as Record | undefined; + const creatorName = (creator?.name as string) || ""; + lines.push(`- **${subject}** (${id})`); + if (sharedOn) lines.push(` Shared: ${formatDate(sharedOn)}${creatorName ? ` by ${creatorName}` : ""}`); + if (conversation) lines.push(` Conversation: ${conversation}`); + if (duration) lines.push(` Duration: ${formatDuration(Math.floor(duration))}`); + lines.push(""); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_moment", + { + title: "Get Moment", + description: `Get details of a specific moment including transcript snippet and context. + +Args: + - moment_id (string): The moment ID + - response_format: Output format + +Returns: Moment with timestamp, title, description, type, and transcript text.`, + inputSchema: GetMomentSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetMomentInput) => { + try { + const moment = await makeApiRequest>(`moments/${params.moment_id}`); + + const text = formatOutput(moment, () => { + const timestamp = moment.timestamp as number; + const title = (moment.title as string) || "Untitled"; + const id = moment.id as string; + const conversationId = moment.conversationId as string; + const duration = moment.duration as number | undefined; + const type = moment.type as string | undefined; + const description = moment.description as string | undefined; + const mText = moment.text as string | undefined; + const time = formatDuration(Math.floor(timestamp / 1000)); + const lines: string[] = [ + `# ${title}`, "", + `- **ID**: ${id}`, + `- **Conversation**: ${conversationId}`, + `- **Timestamp**: ${time}`, + ]; + if (duration) lines.push(`- **Duration**: ${formatDuration(Math.floor(duration / 1000))}`); + if (type) lines.push(`- **Type**: ${type}`); + if (description) lines.push("", "## Description", description); + if (mText) lines.push("", "## Transcript Snippet", `> ${mText}`); + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_create_moment", + { + title: "Create External Moment", + description: `Create a new external moment on a conversation at a specific timestamp. + +Args: + - conversation_id (string): The conversation ID + - timestamp_ms (number): Timestamp in ms from start of call + - duration_ms (number, optional): Duration in ms + - title (string): Title/label for the moment + - description (string, optional): Description + - type (string, optional): Moment type/category + +Returns: The created moment with its ID.`, + inputSchema: CreateMomentSchema, + annotations: ANNOTATIONS.CREATE, + }, + async (params: CreateMomentInput) => { + try { + const body: Record = { + conversationId: params.conversation_id, + timestamp: params.timestamp_ms, + title: params.title, + }; + if (params.duration_ms !== undefined) body.duration = params.duration_ms; + if (params.description) body.description = params.description; + if (params.type) body.type = params.type; + + const moment = await makeApiRequest>("moments", "POST", body); + + return { + content: [{ type: "text" as const, text: `Moment created successfully.\n- **ID**: ${moment.id as string}\n- **Title**: ${(moment.title as string) || (moment.subject as string) || "N/A"}\n- **Conversation**: ${(moment.conversation as string) || (moment.conversationId as string) || "N/A"}` }], + }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_delete_moment", + { + title: "Delete External Moment", + description: `Delete an external moment. This action is irreversible. + +Args: + - moment_id (string): The moment ID to delete + +Returns: Confirmation of deletion.`, + inputSchema: DeleteMomentSchema, + annotations: ANNOTATIONS.DELETE, + }, + async (params: DeleteMomentInput) => { + try { + await makeApiRequest(`moments/${params.moment_id}`, "DELETE"); + return { + content: [{ type: "text" as const, text: `Moment ${params.moment_id} deleted successfully.` }], + }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/playlists.ts b/src/tools/playlists.ts new file mode 100644 index 0000000..524b184 --- /dev/null +++ b/src/tools/playlists.ts @@ -0,0 +1,159 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDuration, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListPlaylistsSchema, GetPlaylistSchema, ListPlaylistMomentsSchema, + type ListPlaylistsInput, type GetPlaylistInput, type ListPlaylistMomentsInput, +} from "../schemas/playlists.js"; + +export function registerPlaylistTools(server: McpServer): void { + server.registerTool( + "chorus_list_playlists", + { + title: "List Playlists", + description: `List coaching playlists in Chorus. + +Args: + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of playlists with names, descriptions, and moment counts.`, + inputSchema: ListPlaylistsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListPlaylistsInput) => { + try { + const data = await makeApiRequest( + "playlists", "GET", undefined, { "page[size]": params.limit } + ); + const playlists = data.items || []; + const response = buildPaginatedResponse(playlists, data.total || playlists.length, params.offset); + + if (playlists.length === 0) { + return { content: [{ type: "text" as const, text: "No playlists found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Playlists", "", formatPaginationInfo(response), ""]; + for (const p of playlists) { + const name = (p.name as string) || "Untitled"; + const id = p.id as string; + const momentCount = p.momentCount as number | undefined; + const description = p.description as string | undefined; + lines.push(`- **${name}** (${id})${momentCount !== undefined ? ` - ${momentCount} moments` : ""}${description ? `: ${description}` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_playlist", + { + title: "Get Playlist", + description: `Get playlist details including its moments. + +Args: + - playlist_id (string): The playlist ID + - response_format: Output format + +Returns: Playlist with name, description, and list of moments.`, + inputSchema: GetPlaylistSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetPlaylistInput) => { + try { + const playlist = await makeApiRequest>(`playlists/${params.playlist_id}`); + + const text = formatOutput(playlist, () => { + const name = (playlist.name as string) || "Untitled"; + const id = playlist.id as string; + const description = playlist.description as string | undefined; + const momentCount = playlist.momentCount as number | undefined; + const moments = (playlist.moments || []) as Array>; + const lines: string[] = [`# ${name}`, ""]; + if (description) lines.push(description, ""); + lines.push(`- **ID**: ${id}`); + if (momentCount !== undefined) lines.push(`- **Moments**: ${momentCount}`); + if (moments.length > 0) { + lines.push("", "## Moments"); + for (const m of moments) { + const timestamp = m.timestamp as number; + const mTitle = (m.title as string) || "Untitled"; + const mDesc = m.description as string | undefined; + const time = formatDuration(Math.floor(timestamp / 1000)); + lines.push(`- [${time}] **${mTitle}**${mDesc ? `: ${mDesc}` : ""}`); + } + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_list_playlist_moments", + { + title: "List Playlist Moments", + description: `List all moments in a playlist with timestamps and descriptions. + +Args: + - playlist_id (string): The playlist ID + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of moments with timestamps, titles, and transcript snippets.`, + inputSchema: ListPlaylistMomentsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListPlaylistMomentsInput) => { + try { + const data = await makeApiRequest( + `playlists/${params.playlist_id}/moments`, "GET", undefined, { "page[size]": params.limit } + ); + const moments = data.items || []; + const response = buildPaginatedResponse(moments, data.total || moments.length, params.offset); + + if (moments.length === 0) { + return { content: [{ type: "text" as const, text: "No moments found in this playlist." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Playlist Moments", "", formatPaginationInfo(response), ""]; + for (const m of moments) { + const timestamp = m.timestamp as number; + const title = (m.title as string) || "Untitled"; + const id = m.id as string; + const conversationId = m.conversationId as string; + const type = m.type as string | undefined; + const mText = m.text as string | undefined; + const time = formatDuration(Math.floor(timestamp / 1000)); + lines.push(`## ${title} (${id})`); + lines.push(`- **Timestamp**: ${time}`); + lines.push(`- **Conversation**: ${conversationId}`); + if (type) lines.push(`- **Type**: ${type}`); + if (mText) lines.push(`- **Snippet**: "${mText}"`); + lines.push(""); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/reports.ts b/src/tools/reports.ts new file mode 100644 index 0000000..c3478ce --- /dev/null +++ b/src/tools/reports.ts @@ -0,0 +1,166 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDuration, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListReportsSchema, GetReportSchema, GetActivityMetricsSchema, + type ListReportsInput, type GetReportInput, type GetActivityMetricsInput, +} from "../schemas/reports.js"; + +export function registerReportTools(server: McpServer): void { + server.registerTool( + "chorus_list_reports", + { + title: "List Reports", + description: `List available reports in Chorus. + +Args: + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of reports with name and type.`, + inputSchema: ListReportsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListReportsInput) => { + try { + const data = await makeApiRequest( + "reports", "GET", undefined, { "page[size]": params.limit } + ); + const reports = data.items || []; + const response = buildPaginatedResponse(reports, data.total || reports.length, params.offset); + + if (reports.length === 0) { + return { content: [{ type: "text" as const, text: "No reports found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Reports", "", formatPaginationInfo(response), ""]; + for (const r of reports) { + const name = (r.name as string) || "Untitled"; + const id = r.id as string; + const type = (r.type as string) || "Unknown"; + lines.push(`- **${name}** (${id}) [${type}]`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_report", + { + title: "Get Report", + description: `Get report data with analytics results. + +Args: + - report_id (string): The report ID + - response_format: Output format + +Returns: Report data with analytics.`, + inputSchema: GetReportSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetReportInput) => { + try { + const report = await makeApiRequest>(`reports/${params.report_id}`); + + const text = formatOutput(report, () => { + const name = (report.name as string) || "Untitled"; + const id = report.id as string; + const type = (report.type as string) || "Unknown"; + const dateRange = report.dateRange as Record | undefined; + const reportData = report.data as Record | undefined; + const lines: string[] = [ + `# ${name}`, "", + `- **ID**: ${id}`, + `- **Type**: ${type}`, + ]; + if (dateRange) { + lines.push(`- **Period**: ${dateRange.start} to ${dateRange.end}`); + } + if (reportData) { + lines.push("", "## Data", "```json", JSON.stringify(reportData, null, 2), "```"); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_activity_metrics", + { + title: "Get Activity Metrics", + description: `Get activity metrics (calls per rep, talk time, ratios) for a date range and team/user. + +Args: + - team_id (string, optional): Filter by team ID + - user_id (string, optional): Filter for a specific user + - start_date (string): Start date (ISO 8601) + - end_date (string): End date (ISO 8601) + - metrics (string[], optional): Specific metrics to include + - response_format: Output format + +Returns: Activity metrics including total calls, duration, talk ratio, question rate, etc.`, + inputSchema: GetActivityMetricsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetActivityMetricsInput) => { + try { + const qp: Record = { + "filter[start_date]": params.start_date, + "filter[end_date]": params.end_date, + }; + if (params.team_id) qp["filter[team_id]"] = params.team_id; + if (params.user_id) qp["filter[user_id]"] = params.user_id; + if (params.metrics) qp["filter[metrics]"] = params.metrics.join(","); + + const data = await makeApiRequest>("reports/activity", "GET", undefined, qp); + + const text = formatOutput(data, () => { + const userName = data.userName as string | undefined; + const totalCalls = data.totalCalls as number; + const totalDuration = data.totalDuration as number; + const avgDuration = data.avgDuration as number; + const talkRatio = data.talkRatio as number | undefined; + const longestMonologue = data.longestMonologue as number | undefined; + const interactivity = data.interactivity as number | undefined; + const patience = data.patience as number | undefined; + const questionRate = data.questionRate as number | undefined; + const lines: string[] = [ + "# Activity Metrics", "", + `- **Period**: ${params.start_date} to ${params.end_date}`, + ]; + if (userName) lines.push(`- **User**: ${userName}`); + lines.push(""); + lines.push("## Metrics"); + lines.push(`- **Total Calls**: ${totalCalls}`); + lines.push(`- **Total Duration**: ${formatDuration(totalDuration)}`); + lines.push(`- **Avg Duration**: ${formatDuration(avgDuration)}`); + if (talkRatio !== undefined) lines.push(`- **Talk Ratio**: ${Math.round(talkRatio * 100)}%`); + if (longestMonologue !== undefined) lines.push(`- **Longest Monologue**: ${formatDuration(longestMonologue)}`); + if (interactivity !== undefined) lines.push(`- **Interactivity**: ${interactivity}`); + if (patience !== undefined) lines.push(`- **Patience**: ${patience}`); + if (questionRate !== undefined) lines.push(`- **Question Rate**: ${questionRate}`); + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/saved-searches.ts b/src/tools/saved-searches.ts new file mode 100644 index 0000000..988b2f2 --- /dev/null +++ b/src/tools/saved-searches.ts @@ -0,0 +1,139 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListSavedSearchesSchema, GetSavedSearchSchema, ExecuteSavedSearchSchema, + type ListSavedSearchesInput, type GetSavedSearchInput, type ExecuteSavedSearchInput, +} from "../schemas/saved-searches.js"; + +export function registerSavedSearchTools(server: McpServer): void { + server.registerTool( + "chorus_list_saved_searches", + { + title: "List Saved Searches", + description: `List saved search queries in Chorus. + +Args: + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of saved searches with names and query info.`, + inputSchema: ListSavedSearchesSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListSavedSearchesInput) => { + try { + const data = await makeApiRequest( + "saved-searches", "GET", undefined, { "page[size]": params.limit } + ); + const searches = data.items || []; + const response = buildPaginatedResponse(searches, data.total || searches.length, params.offset); + + if (searches.length === 0) { + return { content: [{ type: "text" as const, text: "No saved searches found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Saved Searches", "", formatPaginationInfo(response), ""]; + for (const s of searches) { + const name = (s.name as string) || "Untitled"; + const id = s.id as string; + const query = s.query as string | undefined; + lines.push(`- **${name}** (${id})${query ? ` - Query: "${query}"` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_saved_search", + { + title: "Get Saved Search", + description: `Get a saved search definition including its filters and query. + +Args: + - search_id (string): The saved search ID + - response_format: Output format + +Returns: Saved search with name, query, and filter configuration.`, + inputSchema: GetSavedSearchSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetSavedSearchInput) => { + try { + const search = await makeApiRequest>(`saved-searches/${params.search_id}`); + + const text = formatOutput(search, () => { + const name = (search.name as string) || "Untitled"; + const id = search.id as string; + const query = search.query as string | undefined; + const filters = search.filters as Record | undefined; + const lines: string[] = [`# ${name}`, "", `- **ID**: ${id}`]; + if (query) lines.push(`- **Query**: ${query}`); + if (filters) { + lines.push("", "## Filters", "```json", JSON.stringify(filters, null, 2), "```"); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_execute_saved_search", + { + title: "Execute Saved Search", + description: `Execute a saved search and return matching conversations. + +Args: + - search_id (string): The saved search ID to execute + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of conversations matching the saved search criteria.`, + inputSchema: ExecuteSavedSearchSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ExecuteSavedSearchInput) => { + try { + const data = await makeApiRequest( + `saved-searches/${params.search_id}/execute`, "POST", { "page[size]": params.limit } + ); + const conversations = data.items || []; + const response = buildPaginatedResponse(conversations, data.total || conversations.length, params.offset); + + if (conversations.length === 0) { + return { content: [{ type: "text" as const, text: "No conversations matched this saved search." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Saved Search Results", "", formatPaginationInfo(response), ""]; + for (const c of conversations) { + const title = (c.title as string) || "Untitled"; + const id = c.id as string; + const date = (c.date as string) || ""; + lines.push(`- **${title}** (${id}) - ${date}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/scorecards.ts b/src/tools/scorecards.ts new file mode 100644 index 0000000..e0c56c2 --- /dev/null +++ b/src/tools/scorecards.ts @@ -0,0 +1,222 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDate, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListScorecardsSchema, GetScorecardSchema, ListScorecardTemplatesSchema, GetScorecardTemplateSchema, + type ListScorecardsInput, type GetScorecardInput, type ListScorecardTemplatesInput, type GetScorecardTemplateInput, +} from "../schemas/scorecards.js"; + +export function registerScorecardTools(server: McpServer): void { + server.registerTool( + "chorus_list_scorecards", + { + title: "List Scorecards", + description: `List scorecards with optional filters for user, conversation, template, and date range. + +Args: + - user_id (string, optional): Filter by evaluated rep's user ID + - conversation_id (string, optional): Filter for a specific conversation + - template_id (string, optional): Filter by scorecard template ID + - start_date/end_date (string, optional): Date range (ISO 8601) + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of scorecards with scores and evaluator info.`, + inputSchema: ListScorecardsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListScorecardsInput) => { + try { + const qp: Record = { "page[size]": params.limit }; + if (params.user_id) qp["filter[user_id]"] = params.user_id; + if (params.conversation_id) qp["filter[conversation_id]"] = params.conversation_id; + if (params.template_id) qp["filter[template_id]"] = params.template_id; + if (params.start_date) qp["filter[start_date]"] = params.start_date; + if (params.end_date) qp["filter[end_date]"] = params.end_date; + + const data = await makeApiRequest("scorecards", "GET", undefined, qp); + const scorecards = data.items || []; + const response = buildPaginatedResponse(scorecards, data.total || scorecards.length, params.offset); + + if (scorecards.length === 0) { + return { content: [{ type: "text" as const, text: "No scorecards found matching the specified filters." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Scorecards", "", formatPaginationInfo(response), ""]; + for (const s of scorecards) { + const userName = (s.userName as string) || (s.userId as string) || "Unknown"; + const overallScore = s.overallScore as number; + const maxScore = s.maxScore as number; + const id = s.id as string; + const conversationId = s.conversationId as string; + const templateName = s.templateName as string | undefined; + const evaluatorName = s.evaluatorName as string | undefined; + const createdAt = s.createdAt as string; + lines.push(`## ${userName} - ${overallScore}/${maxScore}`); + lines.push(`- **ID**: ${id}`); + lines.push(`- **Conversation**: ${conversationId}`); + if (templateName) lines.push(`- **Template**: ${templateName}`); + if (evaluatorName) lines.push(`- **Evaluator**: ${evaluatorName}`); + lines.push(`- **Date**: ${formatDate(createdAt)}`); + lines.push(""); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_scorecard", + { + title: "Get Scorecard", + description: `Get detailed scorecard with all criteria scores for a specific call evaluation. + +Args: + - scorecard_id (string): The scorecard ID + - response_format: Output format + +Returns: Full scorecard with criteria-level scores, comments, and overall rating.`, + inputSchema: GetScorecardSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetScorecardInput) => { + try { + const sc = await makeApiRequest>(`scorecards/${params.scorecard_id}`); + + const text = formatOutput(sc, () => { + const userName = (sc.userName as string) || (sc.userId as string) || "Unknown"; + const overallScore = sc.overallScore as number; + const maxScore = sc.maxScore as number; + const conversationId = sc.conversationId as string; + const createdAt = sc.createdAt as string; + const evaluatorName = sc.evaluatorName as string | undefined; + const templateName = sc.templateName as string | undefined; + const criteria = (sc.criteria || []) as Array>; + const lines: string[] = [ + `# Scorecard: ${userName}`, + "", + `- **Overall Score**: ${overallScore}/${maxScore} (${Math.round((overallScore / maxScore) * 100)}%)`, + `- **Conversation**: ${conversationId}`, + `- **Date**: ${formatDate(createdAt)}`, + ]; + if (evaluatorName) lines.push(`- **Evaluator**: ${evaluatorName}`); + if (templateName) lines.push(`- **Template**: ${templateName}`); + if (criteria.length > 0) { + lines.push("", "## Criteria Scores", ""); + lines.push("| Criteria | Score | Max |"); + lines.push("|----------|-------|-----|"); + for (const c of criteria) { + lines.push(`| ${c.name as string} | ${c.score as number} | ${c.maxScore as number} |`); + } + const withComments = criteria.filter((c) => c.comments); + if (withComments.length > 0) { + lines.push("", "## Comments"); + for (const c of withComments) { + lines.push(`- **${c.name as string}**: ${c.comments as string}`); + } + } + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_list_scorecard_templates", + { + title: "List Scorecard Templates", + description: `List available scorecard evaluation templates/rubrics. + +Args: + - limit/offset: Pagination + - response_format: Output format + +Returns: List of scorecard templates with names and criteria counts.`, + inputSchema: ListScorecardTemplatesSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListScorecardTemplatesInput) => { + try { + const data = await makeApiRequest( + "scorecards/templates", "GET", undefined, { "page[size]": params.limit } + ); + const templates = data.items || []; + const response = buildPaginatedResponse(templates, data.total || templates.length, params.offset); + + if (templates.length === 0) { + return { content: [{ type: "text" as const, text: "No scorecard templates found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Scorecard Templates", "", formatPaginationInfo(response), ""]; + for (const t of templates) { + const name = (t.name as string) || "Untitled"; + const id = t.id as string; + const criteria = (t.criteria || []) as Array>; + const description = t.description as string | undefined; + lines.push(`- **${name}** (${id}) - ${criteria.length} criteria${description ? `: ${description}` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_scorecard_template", + { + title: "Get Scorecard Template", + description: `Get a scorecard template with its criteria definitions and scoring rubric. + +Args: + - template_id (string): The scorecard template ID + - response_format: Output format + +Returns: Template with criteria names, descriptions, max scores, and weights.`, + inputSchema: GetScorecardTemplateSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetScorecardTemplateInput) => { + try { + const tmpl = await makeApiRequest>(`scorecards/templates/${params.template_id}`); + + const text = formatOutput(tmpl, () => { + const name = (tmpl.name as string) || "Untitled"; + const description = tmpl.description as string | undefined; + const criteria = (tmpl.criteria || []) as Array>; + const lines: string[] = [`# ${name}`, ""]; + if (description) lines.push(description, ""); + lines.push("## Criteria", ""); + lines.push("| Criteria | Max Score | Weight | Description |"); + lines.push("|----------|-----------|--------|-------------|"); + for (const c of criteria) { + lines.push(`| ${c.name as string} | ${c.maxScore as number} | ${(c.weight as number) ?? "N/A"} | ${(c.description as string) || ""} |`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/teams.ts b/src/tools/teams.ts new file mode 100644 index 0000000..98c6aa2 --- /dev/null +++ b/src/tools/teams.ts @@ -0,0 +1,151 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListTeamsSchema, GetTeamSchema, GetTeamMembersSchema, + type ListTeamsInput, type GetTeamInput, type GetTeamMembersInput, +} from "../schemas/teams.js"; + +export function registerTeamTools(server: McpServer): void { + server.registerTool( + "chorus_list_teams", + { + title: "List Chorus Teams", + description: `List all teams in the Chorus organization. + +Args: + - limit (number): Max results 1-100 (default: 20) + - offset (number): Pagination offset (default: 0) + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Paginated list of teams with name, manager, and member count.`, + inputSchema: ListTeamsSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListTeamsInput) => { + try { + const data = await makeApiRequest( + "teams", "GET", undefined, { "page[size]": params.limit } + ); + const teams = data.items || []; + const response = buildPaginatedResponse(teams, data.total || teams.length, params.offset); + + if (teams.length === 0) { + return { content: [{ type: "text" as const, text: "No teams found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Teams", "", formatPaginationInfo(response), ""]; + for (const t of teams) { + const name = (t.name as string) || "Untitled"; + const id = t.id as string; + const managerName = t.managerName as string | undefined; + const memberCount = t.memberCount as number | undefined; + lines.push(`- **${name}** (${id})${managerName ? ` | Manager: ${managerName}` : ""}${memberCount !== undefined ? ` | ${memberCount} members` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_team", + { + title: "Get Chorus Team", + description: `Get team details including members and manager. + +Args: + - team_id (string): The team ID + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Team details with name, manager, and member list.`, + inputSchema: GetTeamSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetTeamInput) => { + try { + const team = await makeApiRequest>(`teams/${params.team_id}`); + + const text = formatOutput(team, () => { + const name = (team.name as string) || "Untitled"; + const id = team.id as string; + const managerName = team.managerName as string | undefined; + const memberCount = team.memberCount as number | undefined; + const members = team.members as Array> | undefined; + const lines: string[] = [`# ${name}`, "", `- **ID**: ${id}`]; + if (managerName) lines.push(`- **Manager**: ${managerName}`); + if (memberCount !== undefined) lines.push(`- **Members**: ${memberCount}`); + if (members && members.length > 0) { + lines.push("", "## Team Members"); + for (const m of members) { + const mName = (m.name as string) || "Unknown"; + const mEmail = (m.email as string) || ""; + const mRole = m.role as string | undefined; + lines.push(`- **${mName}** (${mEmail})${mRole ? ` [${mRole}]` : ""}`); + } + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_team_members", + { + title: "Get Team Members", + description: `List members of a specific Chorus team. + +Args: + - team_id (string): The team ID + - limit (number): Max results 1-100 (default: 20) + - offset (number): Pagination offset (default: 0) + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Paginated list of team members with profile details.`, + inputSchema: GetTeamMembersSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetTeamMembersInput) => { + try { + const data = await makeApiRequest( + `teams/${params.team_id}/members`, "GET", undefined, { "page[size]": params.limit } + ); + const members = data.items || []; + const response = buildPaginatedResponse(members, data.total || members.length, params.offset); + + if (members.length === 0) { + return { content: [{ type: "text" as const, text: "No members found for this team." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Team Members", "", formatPaginationInfo(response), ""]; + for (const m of members) { + const name = (m.name as string) || "Unknown"; + const id = m.id as string; + const email = (m.email as string) || ""; + const role = m.role as string | undefined; + lines.push(`- **${name}** (${id}) - ${email}${role ? ` [${role}]` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/users.ts b/src/tools/users.ts new file mode 100644 index 0000000..69ea90d --- /dev/null +++ b/src/tools/users.ts @@ -0,0 +1,162 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { + formatOutput, + formatPaginationInfo, + buildPaginatedResponse, +} from "../services/formatters.js"; +import type { ListResponse } from "../types.js"; +import { + ListUsersSchema, + GetUserSchema, + SearchUsersSchema, + type ListUsersInput, + type GetUserInput, + type SearchUsersInput, +} from "../schemas/users.js"; + +export function registerUserTools(server: McpServer): void { + server.registerTool( + "chorus_list_users", + { + title: "List Chorus Users", + description: `List all users in the Chorus organization with optional filters. + +Args: + - team_id (string, optional): Filter users by team ID + - role (string, optional): Filter by role (e.g., 'admin', 'user', 'manager') + - limit (number): Max results 1-100 (default: 20) + - offset (number): Pagination offset (default: 0) + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Paginated list of users with name, email, role, and team.`, + inputSchema: ListUsersSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListUsersInput) => { + try { + const queryParams: Record = { "page[size]": params.limit }; + if (params.team_id) queryParams["filter[team_id]"] = params.team_id; + if (params.role) queryParams["filter[role]"] = params.role; + + const data = await makeApiRequest("users", "GET", undefined, queryParams); + const users = data.items || []; + const response = buildPaginatedResponse(users, data.total || users.length, params.offset); + + if (users.length === 0) { + return { content: [{ type: "text" as const, text: "No users found matching the specified filters." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Chorus Users", "", formatPaginationInfo(response), ""]; + for (const u of users) { + const name = (u.name as string) || "Unknown"; + const id = u.id as string; + const email = (u.email as string) || ""; + const role = u.role as string | undefined; + const teamName = u.teamName as string | undefined; + lines.push(`- **${name}** (${id}) - ${email}${role ? ` [${role}]` : ""}${teamName ? ` | Team: ${teamName}` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_user", + { + title: "Get Chorus User", + description: `Get detailed profile for a specific Chorus user. + +Args: + - user_id (string): The Chorus user ID + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: User profile with name, email, role, team membership, and activity stats.`, + inputSchema: GetUserSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetUserInput) => { + try { + const user = await makeApiRequest>(`users/${params.user_id}`); + + const text = formatOutput(user, () => { + const name = (user.name as string) || "Unknown"; + const id = user.id as string; + const email = (user.email as string) || ""; + const role = user.role as string | undefined; + const teamName = user.teamName as string | undefined; + const active = user.active as boolean | undefined; + const lines: string[] = [ + `# ${name}`, + "", + `- **ID**: ${id}`, + `- **Email**: ${email}`, + ]; + if (role) lines.push(`- **Role**: ${role}`); + if (teamName) lines.push(`- **Team**: ${teamName}`); + if (active !== undefined) lines.push(`- **Active**: ${active ? "Yes" : "No"}`); + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_search_users", + { + title: "Search Chorus Users", + description: `Search users by name or email. + +Args: + - query (string): Search string to match against names/emails + - limit (number): Max results 1-100 (default: 20) + - offset (number): Pagination offset (default: 0) + - response_format ('markdown'|'json'): Output format (default: 'markdown') + +Returns: Paginated list of matching users.`, + inputSchema: SearchUsersSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: SearchUsersInput) => { + try { + const data = await makeApiRequest( + "users/search", "GET", undefined, { q: params.query, "page[size]": params.limit } + ); + const users = data.items || []; + const response = buildPaginatedResponse(users, data.total || users.length, params.offset); + + if (users.length === 0) { + return { content: [{ type: "text" as const, text: `No users found matching "${params.query}".` }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = [`# User Search: "${params.query}"`, "", formatPaginationInfo(response), ""]; + for (const u of users) { + const name = (u.name as string) || "Unknown"; + const id = u.id as string; + const email = (u.email as string) || ""; + const role = u.role as string | undefined; + lines.push(`- **${name}** (${id}) - ${email}${role ? ` [${role}]` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/tools/video-conferences.ts b/src/tools/video-conferences.ts new file mode 100644 index 0000000..8a29e09 --- /dev/null +++ b/src/tools/video-conferences.ts @@ -0,0 +1,173 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { makeApiRequest } from "../services/api-client.js"; +import { handleApiError } from "../services/error-handler.js"; +import { ANNOTATIONS } from "../constants.js"; +import { formatOutput, formatDate, formatDuration, formatParticipants, formatPaginationInfo, buildPaginatedResponse } from "../services/formatters.js"; +import type { ListResponse, ChorusParticipant } from "../types.js"; +import { + ListVideoConferencesSchema, GetVideoConferenceSchema, UploadRecordingSchema, DeleteRecordingSchema, + type ListVideoConferencesInput, type GetVideoConferenceInput, type UploadRecordingInput, type DeleteRecordingInput, +} from "../schemas/video-conferences.js"; + +export function registerVideoConferenceTools(server: McpServer): void { + server.registerTool( + "chorus_list_video_conferences", + { + title: "List Video Conferences", + description: `List video conference recordings in Chorus. + +Args: + - start_date/end_date (string, optional): Date range (ISO 8601) + - limit/offset: Pagination + - response_format: Output format + +Returns: Paginated list of video conferences with title, date, and status.`, + inputSchema: ListVideoConferencesSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: ListVideoConferencesInput) => { + try { + const qp: Record = { "page[size]": params.limit }; + if (params.start_date) qp["filter[start_date]"] = params.start_date; + if (params.end_date) qp["filter[end_date]"] = params.end_date; + + const data = await makeApiRequest("video-conferences", "GET", undefined, qp); + const conferences = data.items || []; + const response = buildPaginatedResponse(conferences, data.total || conferences.length, params.offset); + + if (conferences.length === 0) { + return { content: [{ type: "text" as const, text: "No video conferences found." }] }; + } + + const text = formatOutput(response, () => { + const lines: string[] = ["# Video Conferences", "", formatPaginationInfo(response), ""]; + for (const c of conferences) { + const title = (c.title as string) || "Untitled"; + const id = c.id as string; + const date = c.date as string; + const status = c.status as string | undefined; + lines.push(`- **${title}** (${id}) - ${formatDate(date)}${status ? ` [${status}]` : ""}`); + } + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_get_video_conference", + { + title: "Get Video Conference", + description: `Get details and recording URL for a video conference. + +Args: + - conference_id (string): The video conference ID + - response_format: Output format + +Returns: Conference details with title, date, participants, recording URL, and duration.`, + inputSchema: GetVideoConferenceSchema, + annotations: ANNOTATIONS.READ_ONLY, + }, + async (params: GetVideoConferenceInput) => { + try { + const conf = await makeApiRequest>(`video-conferences/${params.conference_id}`); + + const text = formatOutput(conf, () => { + const title = (conf.title as string) || "Untitled"; + const id = conf.id as string; + const date = conf.date as string; + const participants = (conf.participants || []) as unknown as ChorusParticipant[]; + const duration = conf.duration as number | undefined; + const status = conf.status as string | undefined; + const recordingUrl = conf.recordingUrl as string | undefined; + const lines: string[] = [ + `# ${title}`, "", + `- **ID**: ${id}`, + `- **Date**: ${formatDate(date)}`, + `- **Participants**: ${formatParticipants(participants)}`, + ]; + if (duration) lines.push(`- **Duration**: ${formatDuration(duration)}`); + if (status) lines.push(`- **Status**: ${status}`); + if (recordingUrl) lines.push(`- **Recording**: ${recordingUrl}`); + return lines.join("\n"); + }, params.response_format); + + return { content: [{ type: "text" as const, text }] }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_upload_recording", + { + title: "Upload Recording", + description: `Upload a new recording to Chorus from an external dialer or source. + +Args: + - title (string): Title for the recording + - recording_url (string): URL of the recording file + - participants (array): List of {name, email?} objects + - date (string): Recording date (ISO 8601) + - duration_seconds (number, optional): Duration in seconds + - external_id (string, optional): External reference ID + +Returns: The created video conference record with its Chorus ID.`, + inputSchema: UploadRecordingSchema, + annotations: ANNOTATIONS.CREATE, + }, + async (params: UploadRecordingInput) => { + try { + const body: Record = { + title: params.title, + recordingUrl: params.recording_url, + participants: params.participants, + date: params.date, + }; + if (params.duration_seconds !== undefined) body.durationSeconds = params.duration_seconds; + if (params.external_id) body.externalId = params.external_id; + + const conf = await makeApiRequest>("video-conferences", "POST", body); + + return { + content: [{ + type: "text" as const, + text: `Recording uploaded successfully.\n- **ID**: ${conf.id as string}\n- **Title**: ${conf.title as string}\n- **Date**: ${formatDate(conf.date as string)}`, + }], + }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); + + server.registerTool( + "chorus_delete_recording", + { + title: "Delete Recording", + description: `Delete a recording from Chorus. This action is irreversible. Use for GDPR compliance or data retention policies. + +Args: + - conference_id (string): The video conference/recording ID to delete + +Returns: Confirmation of deletion.`, + inputSchema: DeleteRecordingSchema, + annotations: ANNOTATIONS.DELETE, + }, + async (params: DeleteRecordingInput) => { + try { + await makeApiRequest(`video-conferences/${params.conference_id}`, "DELETE"); + return { + content: [{ type: "text" as const, text: `Recording ${params.conference_id} deleted successfully.` }], + }; + } catch (error) { + return { content: [{ type: "text" as const, text: handleApiError(error) }] }; + } + } + ); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..0a2b90d --- /dev/null +++ b/src/types.ts @@ -0,0 +1,201 @@ +export interface ChorusParticipant { + name: string; + email?: string; + role?: string; + speakerId?: string; +} + +export interface ChorusConversation { + id: string; + title: string; + date: string; + duration: number; + participants: ChorusParticipant[]; + status?: string; + type?: string; + summary?: string; + meetingUrl?: string; + recordingUrl?: string; +} + +export interface ChorusTranscriptSegment { + speaker: string; + text: string; + startTime: number; + endTime: number; +} + +export interface ChorusTracker { + id: string; + name: string; + category?: string; + count: number; + occurrences?: Array<{ + timestamp: number; + text: string; + }>; +} + +export interface ChorusUser { + id: string; + name: string; + email: string; + role?: string; + teamId?: string; + teamName?: string; + active?: boolean; + createdAt?: string; +} + +export interface ChorusTeam { + id: string; + name: string; + managerId?: string; + managerName?: string; + memberCount?: number; + members?: ChorusUser[]; +} + +export interface ChorusScorecardCriteria { + name: string; + score: number; + maxScore: number; + comments?: string; +} + +export interface ChorusScorecard { + id: string; + conversationId: string; + userId: string; + userName?: string; + templateId?: string; + templateName?: string; + overallScore: number; + maxScore: number; + criteria: ChorusScorecardCriteria[]; + createdAt: string; + evaluatorName?: string; +} + +export interface ChorusScorecardTemplateCriteria { + name: string; + description?: string; + maxScore: number; + weight?: number; +} + +export interface ChorusScorecardTemplate { + id: string; + name: string; + description?: string; + criteria: ChorusScorecardTemplateCriteria[]; +} + +export interface ChorusMoment { + id: string; + conversationId: string; + timestamp: number; + duration?: number; + title: string; + description?: string; + type?: string; + text?: string; +} + +export interface ChorusPlaylist { + id: string; + name: string; + description?: string; + momentCount?: number; + createdAt?: string; + moments?: ChorusMoment[]; +} + +export interface ChorusEmail { + id: string; + subject: string; + date: string; + sender: string; + recipients: string[]; + engagement?: { + opens?: number; + clicks?: number; + replies?: number; + }; +} + +export interface ChorusEngagement { + id: string; + type: string; + date: string; + participants: ChorusParticipant[]; + duration?: number; + outcome?: string; + conversationId?: string; +} + +export interface ChorusReport { + id: string; + name: string; + type: string; + data?: Record; + dateRange?: { + start: string; + end: string; + }; +} + +export interface ChorusActivityMetrics { + userId?: string; + userName?: string; + teamId?: string; + totalCalls: number; + totalDuration: number; + avgDuration: number; + talkRatio?: number; + longestMonologue?: number; + interactivity?: number; + patience?: number; + questionRate?: number; +} + +export interface ChorusSavedSearch { + id: string; + name: string; + query?: string; + filters?: Record; +} + +export interface ChorusVideoConference { + id: string; + title: string; + date: string; + participants: ChorusParticipant[]; + recordingUrl?: string; + status?: string; + duration?: number; +} + +export interface ChorusIntegration { + id: string; + type: string; + name: string; + status: string; + config?: Record; +} + +export interface PaginatedResponse { + total: number; + count: number; + offset: number; + items: T[]; + has_more: boolean; + next_offset?: number; +} + +/** Normalized response from JSON:API list endpoints (after api-client auto-normalization). */ +export interface ListResponse { + items: Record[]; + total: number; + cursor?: string; +} diff --git a/tests/fixtures/chorus-responses.ts b/tests/fixtures/chorus-responses.ts new file mode 100644 index 0000000..c842232 --- /dev/null +++ b/tests/fixtures/chorus-responses.ts @@ -0,0 +1,279 @@ +import type { + ChorusConversation, + ChorusUser, + ChorusTeam, + ChorusScorecard, + ChorusScorecardTemplate, + ChorusPlaylist, + ChorusMoment, + ChorusEmail, + ChorusEngagement, + ChorusReport, + ChorusSavedSearch, + ChorusVideoConference, + ChorusIntegration, + ChorusTranscriptSegment, + ChorusTracker, + ChorusActivityMetrics, +} from '../../src/types.js'; + +export function makeConversation(overrides: Partial = {}): ChorusConversation { + return { + id: 'conv-001', + title: 'Discovery Call with Acme Corp', + date: '2024-06-15T14:00:00Z', + duration: 1800, + participants: [ + { name: 'Jane Smith', email: 'jane@ourco.com', role: 'AE' }, + { name: 'Bob Johnson', email: 'bob@acme.com', role: 'Buyer' }, + ], + status: 'completed', + type: 'meeting', + summary: 'Discussed product fit and pricing. Competitor X mentioned twice.', + ...overrides, + }; +} + +export function makeUser(overrides: Partial = {}): ChorusUser { + return { + id: 'user-001', + name: 'Jane Smith', + email: 'jane@ourco.com', + role: 'manager', + teamId: 'team-001', + teamName: 'Enterprise Sales', + active: true, + createdAt: '2023-01-15T00:00:00Z', + ...overrides, + }; +} + +export function makeTeam(overrides: Partial = {}): ChorusTeam { + return { + id: 'team-001', + name: 'Enterprise Sales', + managerId: 'user-001', + managerName: 'Jane Smith', + memberCount: 8, + ...overrides, + }; +} + +export function makeScorecard(overrides: Partial = {}): ChorusScorecard { + return { + id: 'sc-001', + conversationId: 'conv-001', + userId: 'user-002', + userName: 'Alex Rep', + templateId: 'tmpl-001', + templateName: 'MEDDIC Scorecard', + overallScore: 35, + maxScore: 50, + criteria: [ + { name: 'Metrics', score: 8, maxScore: 10 }, + { name: 'Economic Buyer', score: 7, maxScore: 10 }, + { name: 'Decision Criteria', score: 6, maxScore: 10 }, + { name: 'Decision Process', score: 7, maxScore: 10 }, + { name: 'Identify Pain', score: 7, maxScore: 10, comments: 'Good probing questions' }, + ], + createdAt: '2024-06-16T09:00:00Z', + evaluatorName: 'Jane Smith', + ...overrides, + }; +} + +export function makeScorecardTemplate(overrides: Partial = {}): ChorusScorecardTemplate { + return { + id: 'tmpl-001', + name: 'MEDDIC Scorecard', + description: 'Standard MEDDIC evaluation framework', + criteria: [ + { name: 'Metrics', maxScore: 10, weight: 1, description: 'Quantifiable measures of value' }, + { name: 'Economic Buyer', maxScore: 10, weight: 1, description: 'Access to decision maker' }, + { name: 'Decision Criteria', maxScore: 10, weight: 1 }, + { name: 'Decision Process', maxScore: 10, weight: 1 }, + { name: 'Identify Pain', maxScore: 10, weight: 1 }, + ], + ...overrides, + }; +} + +export function makePlaylist(overrides: Partial = {}): ChorusPlaylist { + return { + id: 'pl-001', + name: 'Best Discovery Calls Q2', + description: 'Top-rated discovery calls for new hire onboarding', + momentCount: 5, + createdAt: '2024-04-01T00:00:00Z', + ...overrides, + }; +} + +export function makeMoment(overrides: Partial = {}): ChorusMoment { + return { + id: 'mom-001', + conversationId: 'conv-001', + timestamp: 120000, + duration: 30000, + title: 'Great objection handling', + description: 'Rep addressed pricing concern effectively', + type: 'highlight', + text: 'I understand budget is a concern. Let me walk you through the ROI...', + ...overrides, + }; +} + +export function makeEmail(overrides: Partial = {}): ChorusEmail { + return { + id: 'email-001', + subject: 'Follow-up: Discovery Call', + date: '2024-06-15T16:00:00Z', + sender: 'jane@ourco.com', + recipients: ['bob@acme.com'], + engagement: { opens: 3, clicks: 1, replies: 1 }, + ...overrides, + }; +} + +export function makeEngagement(overrides: Partial = {}): ChorusEngagement { + return { + id: 'eng-001', + type: 'meeting', + date: '2024-06-15T14:00:00Z', + participants: [ + { name: 'Jane Smith', email: 'jane@ourco.com' }, + { name: 'Bob Johnson', email: 'bob@acme.com' }, + ], + duration: 1800, + outcome: 'completed', + conversationId: 'conv-001', + ...overrides, + }; +} + +export function makeReport(overrides: Partial = {}): ChorusReport { + return { + id: 'rpt-001', + name: 'Weekly Activity Report', + type: 'activity', + dateRange: { start: '2024-06-10', end: '2024-06-16' }, + ...overrides, + }; +} + +export function makeActivityMetrics(overrides: Partial = {}): ChorusActivityMetrics { + return { + userId: 'user-002', + userName: 'Alex Rep', + totalCalls: 24, + totalDuration: 43200, + avgDuration: 1800, + talkRatio: 0.45, + longestMonologue: 120, + interactivity: 0.72, + patience: 0.8, + questionRate: 12, + ...overrides, + }; +} + +export function makeSavedSearch(overrides: Partial = {}): ChorusSavedSearch { + return { + id: 'ss-001', + name: 'Competitor X Mentions', + query: 'competitor x', + filters: { dateRange: { start: '2024-01-01', end: '2024-12-31' } }, + ...overrides, + }; +} + +export function makeVideoConference(overrides: Partial = {}): ChorusVideoConference { + return { + id: 'vc-001', + title: 'Weekly Pipeline Review', + date: '2024-06-14T10:00:00Z', + participants: [ + { name: 'Jane Smith', email: 'jane@ourco.com' }, + { name: 'Alex Rep', email: 'alex@ourco.com' }, + ], + recordingUrl: 'https://chorus.ai/recordings/vc-001', + status: 'processed', + duration: 3600, + ...overrides, + }; +} + +export function makeIntegration(overrides: Partial = {}): ChorusIntegration { + return { + id: 'int-001', + type: 'salesforce', + name: 'Salesforce CRM', + status: 'active', + ...overrides, + }; +} + +/** + * Wrap a fixture record into JSON:API format as the Chorus API returns it. + * The API client's normalizeResponse will flatten this back. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function wrapJsonApi(record: any, typeName: string = 'record') { + const { id, ...attributes } = record; + return { id: id as string, type: typeName, attributes }; +} + +/** + * Wrap an array of records into a JSON:API list response. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function wrapJsonApiList(records: any[], typeName: string = 'record') { + return { + data: records.map((r: any) => wrapJsonApi(r, typeName)), + meta: { page: { total: records.length } }, + }; +} + +/** + * Wrap a single record into a JSON:API single response. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function wrapJsonApiSingle(record: any, typeName: string = 'record') { + return { + data: wrapJsonApi(record, typeName), + }; +} + +export function makeTranscriptSegments(): ChorusTranscriptSegment[] { + return [ + { speaker: 'Jane Smith', text: 'Thanks for joining today, Bob.', startTime: 0, endTime: 3000 }, + { speaker: 'Bob Johnson', text: 'Happy to be here. We\'re excited about this.', startTime: 3000, endTime: 6000 }, + { speaker: 'Jane Smith', text: 'Let me start by understanding your current challenges.', startTime: 6000, endTime: 10000 }, + { speaker: 'Bob Johnson', text: 'Our biggest pain point is manual reporting.', startTime: 10000, endTime: 14000 }, + { speaker: 'Jane Smith', text: 'How much time does your team spend on that weekly?', startTime: 14000, endTime: 18000 }, + ]; +} + +export function makeTrackers(): ChorusTracker[] { + return [ + { + id: 'trk-001', + name: 'Competitor X', + category: 'competitor', + count: 2, + occurrences: [ + { timestamp: 300000, text: 'We looked at Competitor X but their pricing...' }, + { timestamp: 900000, text: 'Competitor X doesn\'t have this feature.' }, + ], + }, + { + id: 'trk-002', + name: 'Pricing Discussion', + category: 'topic', + count: 1, + occurrences: [ + { timestamp: 600000, text: 'What does your pricing look like?' }, + ], + }, + ]; +} diff --git a/tests/integration/tool-registration.test.ts b/tests/integration/tool-registration.test.ts new file mode 100644 index 0000000..888a3b1 --- /dev/null +++ b/tests/integration/tool-registration.test.ts @@ -0,0 +1,154 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerConversationTools } from '../../src/tools/conversations.js'; +import { registerUserTools } from '../../src/tools/users.js'; +import { registerTeamTools } from '../../src/tools/teams.js'; +import { registerScorecardTools } from '../../src/tools/scorecards.js'; +import { registerPlaylistTools } from '../../src/tools/playlists.js'; +import { registerMomentTools } from '../../src/tools/moments.js'; +import { registerEmailTools } from '../../src/tools/emails.js'; +import { registerEngagementTools } from '../../src/tools/engagements.js'; +import { registerReportTools } from '../../src/tools/reports.js'; +import { registerSavedSearchTools } from '../../src/tools/saved-searches.js'; +import { registerVideoConferenceTools } from '../../src/tools/video-conferences.js'; +import { registerIntegrationTools } from '../../src/tools/integrations.js'; +import { ANNOTATIONS } from '../../src/constants.js'; + +describe('Tool Registration', () => { + let server: McpServer; + + beforeAll(() => { + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerConversationTools(server); + registerUserTools(server); + registerTeamTools(server); + registerScorecardTools(server); + registerPlaylistTools(server); + registerMomentTools(server); + registerEmailTools(server); + registerEngagementTools(server); + registerReportTools(server); + registerSavedSearchTools(server); + registerVideoConferenceTools(server); + registerIntegrationTools(server); + }); + + function getRegisteredTools(): Record { + return (server as any)._registeredTools || {}; + } + + it('registers all 38 expected tools', () => { + const tools = getRegisteredTools(); + const toolNames = Object.keys(tools); + expect(toolNames.length).toBe(39); + }); + + it('prefixes all tool names with chorus_', () => { + const tools = getRegisteredTools(); + for (const name of Object.keys(tools)) { + expect(name).toMatch(/^chorus_/); + } + }); + + it('uses snake_case for all tool names', () => { + const tools = getRegisteredTools(); + for (const name of Object.keys(tools)) { + expect(name).toMatch(/^[a-z_]+$/); + } + }); + + it('has no duplicate tool names', () => { + const tools = getRegisteredTools(); + const names = Object.keys(tools); + const unique = new Set(names); + expect(unique.size).toBe(names.length); + }); + + describe('read-only tools', () => { + const readOnlyTools = [ + 'chorus_list_conversations', + 'chorus_get_conversation', + 'chorus_get_transcript', + 'chorus_get_conversation_trackers', + 'chorus_search_conversations', + 'chorus_list_users', + 'chorus_get_user', + 'chorus_search_users', + 'chorus_list_teams', + 'chorus_get_team', + 'chorus_get_team_members', + 'chorus_list_scorecards', + 'chorus_get_scorecard', + 'chorus_list_scorecard_templates', + 'chorus_get_scorecard_template', + 'chorus_list_playlists', + 'chorus_get_playlist', + 'chorus_list_playlist_moments', + 'chorus_list_moments', + 'chorus_get_moment', + 'chorus_list_emails', + 'chorus_get_email', + 'chorus_filter_engagements', + 'chorus_get_engagement', + 'chorus_list_reports', + 'chorus_get_report', + 'chorus_get_activity_metrics', + 'chorus_list_video_conferences', + 'chorus_get_video_conference', + 'chorus_list_saved_searches', + 'chorus_get_saved_search', + 'chorus_execute_saved_search', + 'chorus_list_integrations', + 'chorus_get_integration', + 'chorus_get_session', + ]; + + it.each(readOnlyTools)('%s is registered', (toolName) => { + const tools = getRegisteredTools(); + expect(tools[toolName]).toBeDefined(); + }); + }); + + describe('write tools', () => { + const createTools = ['chorus_create_moment', 'chorus_upload_recording']; + const deleteTools = ['chorus_delete_moment', 'chorus_delete_recording']; + + it.each(createTools)('%s is registered', (toolName) => { + const tools = getRegisteredTools(); + expect(tools[toolName]).toBeDefined(); + }); + + it.each(deleteTools)('%s is registered', (toolName) => { + const tools = getRegisteredTools(); + expect(tools[toolName]).toBeDefined(); + }); + }); + + describe('ANNOTATIONS presets', () => { + it('READ_ONLY marks tools as read-only and idempotent', () => { + expect(ANNOTATIONS.READ_ONLY).toEqual({ + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }); + }); + + it('CREATE marks tools as non-read-only and non-idempotent', () => { + expect(ANNOTATIONS.CREATE).toEqual({ + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }); + }); + + it('DELETE marks tools as destructive', () => { + expect(ANNOTATIONS.DELETE).toEqual({ + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }); + }); + }); +}); diff --git a/tests/unit/services/api-client.test.ts b/tests/unit/services/api-client.test.ts new file mode 100644 index 0000000..e333ae9 --- /dev/null +++ b/tests/unit/services/api-client.test.ts @@ -0,0 +1,93 @@ +import axios from 'axios'; +import { makeApiRequest } from '../../../src/services/api-client.js'; + +jest.mock('axios'); +const mockedAxios = axios as jest.MockedFunction; + +describe('API Client', () => { + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-api-key-123' }; + }); + + afterAll(() => { + process.env = originalEnv; + }); + + describe('makeApiRequest', () => { + it('sends GET request with correct headers', async () => { + mockedAxios.mockResolvedValue({ data: { users: [] } }); + + await makeApiRequest('users'); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'GET', + url: expect.stringContaining('/users'), + headers: expect.objectContaining({ + Authorization: 'test-api-key-123', + 'Content-Type': 'application/json', + }), + timeout: 30000, + }) + ); + }); + + it('sends POST request with body', async () => { + const body = { title: 'test', conversationId: 'conv-001' }; + mockedAxios.mockResolvedValue({ data: { id: 'mom-001' } }); + + await makeApiRequest('moments', 'POST', body); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'POST', + data: body, + }) + ); + }); + + it('sends GET request with query params', async () => { + mockedAxios.mockResolvedValue({ data: { conversations: [] } }); + + await makeApiRequest('conversations', 'GET', undefined, { + limit: 10, + offset: 0, + startDate: '2024-01-01', + }); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + params: { limit: 10, offset: 0, startDate: '2024-01-01' }, + }) + ); + }); + + it('returns response data', async () => { + const mockData = { users: [{ id: 'u1', name: 'Jane' }] }; + mockedAxios.mockResolvedValue({ data: mockData }); + + const result = await makeApiRequest('users'); + + expect(result).toEqual(mockData); + }); + + it('throws when CHORUS_API_KEY is not set', async () => { + delete process.env.CHORUS_API_KEY; + + await expect(makeApiRequest('users')).rejects.toThrow('CHORUS_API_KEY'); + }); + + it('sends DELETE request', async () => { + mockedAxios.mockResolvedValue({ data: undefined }); + + await makeApiRequest('moments/mom-001', 'DELETE'); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ method: 'DELETE' }) + ); + }); + }); +}); diff --git a/tests/unit/services/error-handler.test.ts b/tests/unit/services/error-handler.test.ts new file mode 100644 index 0000000..ed3140a --- /dev/null +++ b/tests/unit/services/error-handler.test.ts @@ -0,0 +1,88 @@ +import { AxiosError, AxiosHeaders } from 'axios'; +import { handleApiError } from '../../../src/services/error-handler.js'; + +function makeAxiosError(status: number, data?: unknown): AxiosError { + const headers = new AxiosHeaders(); + const error = new AxiosError( + `Request failed with status code ${status}`, + String(status), + undefined, + undefined, + { + status, + statusText: 'Error', + headers, + config: { headers } as any, + data: data ?? {}, + } + ); + return error; +} + +describe('Error Handler', () => { + describe('handleApiError', () => { + it('maps 401 to authentication error', () => { + const error = makeAxiosError(401); + const message = handleApiError(error); + expect(message).toContain('Authentication failed'); + expect(message).toContain('CHORUS_API_KEY'); + }); + + it('maps 403 to access denied error', () => { + const error = makeAxiosError(403); + const message = handleApiError(error); + expect(message).toContain('Access denied'); + }); + + it('maps 404 to not found error', () => { + const error = makeAxiosError(404); + const message = handleApiError(error); + expect(message).toContain('not found'); + }); + + it('maps 429 to rate limit error', () => { + const error = makeAxiosError(429); + const message = handleApiError(error); + expect(message).toContain('Rate limit'); + }); + + it('maps 400 to bad request with API message', () => { + const error = makeAxiosError(400, { message: 'Invalid date format' }); + const message = handleApiError(error); + expect(message).toContain('Bad request'); + expect(message).toContain('Invalid date format'); + }); + + it('maps 500+ to server error', () => { + const error = makeAxiosError(503); + const message = handleApiError(error); + expect(message).toContain('server error'); + }); + + it('handles connection timeout', () => { + const error = new AxiosError('timeout', 'ECONNABORTED'); + error.code = 'ECONNABORTED'; + const message = handleApiError(error); + expect(message).toContain('timed out'); + }); + + it('handles connection refused', () => { + const error = new AxiosError('refused', 'ECONNREFUSED'); + error.code = 'ECONNREFUSED'; + const message = handleApiError(error); + expect(message).toContain('Could not connect'); + }); + + it('handles non-Axios errors', () => { + const error = new Error('Something unexpected'); + const message = handleApiError(error); + expect(message).toContain('Unexpected error'); + expect(message).toContain('Something unexpected'); + }); + + it('handles non-Error objects', () => { + const message = handleApiError('string error'); + expect(message).toContain('string error'); + }); + }); +}); diff --git a/tests/unit/services/formatters.test.ts b/tests/unit/services/formatters.test.ts new file mode 100644 index 0000000..a042129 --- /dev/null +++ b/tests/unit/services/formatters.test.ts @@ -0,0 +1,144 @@ +import { + truncateResponse, + formatDate, + formatDuration, + formatParticipants, + formatPaginationInfo, + buildPaginatedResponse, + formatOutput, +} from '../../../src/services/formatters.js'; +import { ResponseFormat, CHARACTER_LIMIT } from '../../../src/constants.js'; + +describe('Formatters', () => { + describe('truncateResponse', () => { + it('returns text unchanged when under limit', () => { + const text = 'Short text'; + expect(truncateResponse(text)).toBe(text); + }); + + it('truncates text exceeding CHARACTER_LIMIT', () => { + const text = 'x'.repeat(CHARACTER_LIMIT + 1000); + const result = truncateResponse(text); + expect(result.length).toBeLessThan(text.length); + expect(result).toContain('truncated'); + }); + }); + + describe('formatDate', () => { + it('formats ISO date string', () => { + const result = formatDate('2024-06-15T14:30:00Z'); + expect(result).toContain('2024'); + expect(result).toContain('Jun'); + }); + + it('returns original string for invalid date', () => { + expect(formatDate('not-a-date')).toBe('not-a-date'); + }); + }); + + describe('formatDuration', () => { + it('formats seconds only', () => { + expect(formatDuration(45)).toBe('45s'); + }); + + it('formats minutes and seconds', () => { + expect(formatDuration(90)).toBe('1m 30s'); + }); + + it('formats minutes without seconds', () => { + expect(formatDuration(120)).toBe('2m'); + }); + + it('formats hours and minutes', () => { + expect(formatDuration(3720)).toBe('1h 2m'); + }); + + it('formats hours without minutes', () => { + expect(formatDuration(3600)).toBe('1h'); + }); + }); + + describe('formatParticipants', () => { + it('returns None for empty array', () => { + expect(formatParticipants([])).toBe('None'); + }); + + it('joins up to 3 names', () => { + const participants = [ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Carol' }, + ]; + expect(formatParticipants(participants)).toBe('Alice, Bob, Carol'); + }); + + it('shows count for more than 3', () => { + const participants = [ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Carol' }, + { name: 'Dave' }, + { name: 'Eve' }, + ]; + const result = formatParticipants(participants); + expect(result).toContain('Alice, Bob, Carol'); + expect(result).toContain('+2 others'); + }); + }); + + describe('buildPaginatedResponse', () => { + it('builds response with has_more when more results exist', () => { + const result = buildPaginatedResponse(['a', 'b'], 10, 0); + expect(result).toEqual({ + total: 10, + count: 2, + offset: 0, + items: ['a', 'b'], + has_more: true, + next_offset: 2, + }); + }); + + it('builds response without has_more at end', () => { + const result = buildPaginatedResponse(['a', 'b'], 2, 0); + expect(result).toEqual({ + total: 2, + count: 2, + offset: 0, + items: ['a', 'b'], + has_more: false, + }); + }); + }); + + describe('formatPaginationInfo', () => { + it('includes count and total', () => { + const response = buildPaginatedResponse(['a'], 10, 0); + const result = formatPaginationInfo(response); + expect(result).toContain('1 of 10'); + }); + + it('includes next offset when more results exist', () => { + const response = buildPaginatedResponse(['a'], 10, 0); + const result = formatPaginationInfo(response); + expect(result).toContain('offset=1'); + }); + }); + + describe('formatOutput', () => { + it('returns markdown when format is MARKDOWN', () => { + const result = formatOutput( + { value: 42 }, + () => '# Title\nContent', + ResponseFormat.MARKDOWN + ); + expect(result).toBe('# Title\nContent'); + }); + + it('returns JSON when format is JSON', () => { + const data = { value: 42 }; + const result = formatOutput(data, () => 'ignored', ResponseFormat.JSON); + expect(JSON.parse(result)).toEqual(data); + }); + }); +}); diff --git a/tests/unit/tools/conversations.test.ts b/tests/unit/tools/conversations.test.ts new file mode 100644 index 0000000..f8970c3 --- /dev/null +++ b/tests/unit/tools/conversations.test.ts @@ -0,0 +1,211 @@ +import axios from 'axios'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerConversationTools } from '../../../src/tools/conversations.js'; +import { makeConversation, makeTranscriptSegments, makeTrackers, wrapJsonApiList, wrapJsonApiSingle } from '../../fixtures/chorus-responses.js'; + +jest.mock('axios'); +const mockedAxios = axios as jest.MockedFunction; + +describe('Conversation Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerConversationTools(server); + }); + + afterAll(() => { + process.env = originalEnv; + }); + + function getToolHandler(toolName: string) { + const tools = (server as any)._registeredTools; + if (!tools || !tools[toolName]) { + throw new Error(`Tool ${toolName} not registered`); + } + return tools[toolName]; + } + + describe('chorus_list_conversations', () => { + it('registers the tool', () => { + const tool = getToolHandler('chorus_list_conversations'); + expect(tool).toBeDefined(); + }); + + it('returns conversations in markdown format', async () => { + const conversations = [makeConversation(), makeConversation({ id: 'conv-002', title: 'Demo Call' })]; + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(conversations, 'conversation') }); + + const tool = getToolHandler('chorus_list_conversations'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Discovery Call'); + expect(result.content[0].text).toContain('Demo Call'); + }); + + it('returns conversations in JSON format', async () => { + const conversations = [makeConversation()]; + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(conversations, 'conversation') }); + + const tool = getToolHandler('chorus_list_conversations'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'json' }, {} as any); + + const parsed = JSON.parse(result.content[0].text); + expect(parsed.items).toHaveLength(1); + expect(parsed.items[0].id).toBe('conv-001'); + }); + + it('returns empty message when no conversations found', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_list_conversations'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('No conversations found'); + }); + + it('passes filter parameters to API', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_list_conversations'); + await tool.handler({ + start_date: '2024-01-01', + end_date: '2024-12-31', + participant_email: 'bob@acme.com', + team_id: 'team-001', + limit: 10, + offset: 5, + response_format: 'markdown', + }, {} as any); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ + "filter[start_date]": '2024-01-01', + "filter[end_date]": '2024-12-31', + "filter[participant_email]": 'bob@acme.com', + "filter[team_id]": 'team-001', + "page[size]": 10, + }), + }) + ); + }); + + it('handles API errors gracefully', async () => { + mockedAxios.mockRejectedValue(new Error('Network error')); + + const tool = getToolHandler('chorus_list_conversations'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Error'); + }); + }); + + describe('chorus_get_conversation', () => { + it('returns conversation details', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeConversation(), 'conversation') }); + + const tool = getToolHandler('chorus_get_conversation'); + const result = await tool.handler({ conversation_id: 'conv-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Discovery Call'); + expect(result.content[0].text).toContain('Jane Smith'); + }); + }); + + describe('chorus_get_transcript', () => { + it('returns full transcript', async () => { + // Transcript segments use JSON:API format too + const segments = makeTranscriptSegments().map((s, i) => ({ + id: `seg-${i}`, + ...s, + })); + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(segments, 'transcript_segment') }); + + const tool = getToolHandler('chorus_get_transcript'); + const result = await tool.handler({ conversation_id: 'conv-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Jane Smith'); + expect(result.content[0].text).toContain('Bob Johnson'); + expect(result.content[0].text).toContain('manual reporting'); + }); + + it('filters transcript by speaker', async () => { + const segments = makeTranscriptSegments().map((s, i) => ({ + id: `seg-${i}`, + ...s, + })); + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(segments, 'transcript_segment') }); + + const tool = getToolHandler('chorus_get_transcript'); + const result = await tool.handler({ + conversation_id: 'conv-001', + speaker_filter: 'Bob', + response_format: 'markdown', + }, {} as any); + + expect(result.content[0].text).toContain('Bob Johnson'); + expect(result.content[0].text).not.toContain('[0s] Jane Smith'); + }); + + it('returns message when no segments match speaker filter', async () => { + const segments = makeTranscriptSegments().map((s, i) => ({ + id: `seg-${i}`, + ...s, + })); + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(segments, 'transcript_segment') }); + + const tool = getToolHandler('chorus_get_transcript'); + const result = await tool.handler({ + conversation_id: 'conv-001', + speaker_filter: 'Nobody', + response_format: 'markdown', + }, {} as any); + + expect(result.content[0].text).toContain('No transcript segments found'); + }); + }); + + describe('chorus_get_conversation_trackers', () => { + it('returns tracker data', async () => { + const trackers = makeTrackers(); + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(trackers, 'tracker') }); + + const tool = getToolHandler('chorus_get_conversation_trackers'); + const result = await tool.handler({ conversation_id: 'conv-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Competitor X'); + expect(result.content[0].text).toContain('Pricing Discussion'); + }); + + it('returns message when no trackers found', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_get_conversation_trackers'); + const result = await tool.handler({ conversation_id: 'conv-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('No trackers detected'); + }); + }); + + describe('chorus_search_conversations', () => { + it('searches with query and returns results', async () => { + const conversations = [makeConversation()]; + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(conversations, 'conversation') }); + + const tool = getToolHandler('chorus_search_conversations'); + const result = await tool.handler({ + query: 'pricing', + limit: 20, + offset: 0, + response_format: 'markdown', + }, {} as any); + + expect(result.content[0].text).toContain('pricing'); + expect(result.content[0].text).toContain('Discovery Call'); + }); + }); +}); diff --git a/tests/unit/tools/moments.test.ts b/tests/unit/tools/moments.test.ts new file mode 100644 index 0000000..c669407 --- /dev/null +++ b/tests/unit/tools/moments.test.ts @@ -0,0 +1,96 @@ +import axios from 'axios'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerMomentTools } from '../../../src/tools/moments.js'; +import { makeMoment, wrapJsonApiList, wrapJsonApiSingle } from '../../fixtures/chorus-responses.js'; + +jest.mock('axios'); +const mockedAxios = axios as jest.MockedFunction; + +describe('Moment Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerMomentTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + describe('chorus_list_moments', () => { + it('returns moments list', async () => { + // Moments use `subject` not `title` in Chorus API, and have `shared_on` + const moment = { ...makeMoment(), subject: 'Great objection handling', shared_on: '2025-06-13T00:00:18Z' }; + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([moment], 'moment') }); + + const tool = getToolHandler('chorus_list_moments'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Great objection handling'); + }); + + it('filters by conversation_id', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_list_moments'); + await tool.handler({ conversation_id: 'conv-001', limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ "filter[conversation_id]": 'conv-001' }), + }) + ); + }); + }); + + describe('chorus_get_moment', () => { + it('returns moment details with transcript snippet', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeMoment(), 'moment') }); + + const tool = getToolHandler('chorus_get_moment'); + const result = await tool.handler({ moment_id: 'mom-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Great objection handling'); + expect(result.content[0].text).toContain('ROI'); + }); + }); + + describe('chorus_create_moment', () => { + it('creates a moment and returns confirmation', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle({ ...makeMoment(), id: 'mom-new' }, 'moment') }); + + const tool = getToolHandler('chorus_create_moment'); + const result = await tool.handler({ + conversation_id: 'conv-001', + timestamp_ms: 120000, + title: 'Key insight', + description: 'Customer revealed budget', + }, {} as any); + + expect(result.content[0].text).toContain('created successfully'); + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ method: 'POST' }) + ); + }); + }); + + describe('chorus_delete_moment', () => { + it('deletes a moment and returns confirmation', async () => { + mockedAxios.mockResolvedValue({ data: undefined }); + + const tool = getToolHandler('chorus_delete_moment'); + const result = await tool.handler({ moment_id: 'mom-001' }, {} as any); + + expect(result.content[0].text).toContain('deleted successfully'); + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ method: 'DELETE' }) + ); + }); + }); +}); diff --git a/tests/unit/tools/remaining-tools.test.ts b/tests/unit/tools/remaining-tools.test.ts new file mode 100644 index 0000000..648b10a --- /dev/null +++ b/tests/unit/tools/remaining-tools.test.ts @@ -0,0 +1,295 @@ +import axios from 'axios'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTeamTools } from '../../../src/tools/teams.js'; +import { registerPlaylistTools } from '../../../src/tools/playlists.js'; +import { registerEmailTools } from '../../../src/tools/emails.js'; +import { registerEngagementTools } from '../../../src/tools/engagements.js'; +import { registerReportTools } from '../../../src/tools/reports.js'; +import { registerSavedSearchTools } from '../../../src/tools/saved-searches.js'; +import { registerVideoConferenceTools } from '../../../src/tools/video-conferences.js'; +import { registerIntegrationTools } from '../../../src/tools/integrations.js'; +import { + makeTeam, makeUser, makePlaylist, makeMoment, + makeEmail, makeEngagement, makeReport, makeActivityMetrics, + makeSavedSearch, makeConversation, makeVideoConference, makeIntegration, + wrapJsonApiList, wrapJsonApiSingle, +} from '../../fixtures/chorus-responses.js'; + +jest.mock('axios'); +const mockedAxios = axios as jest.MockedFunction; + +describe('Team Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerTeamTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_teams returns teams', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeTeam()], 'team') }); + const result = await getToolHandler('chorus_list_teams').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Enterprise Sales'); + }); + + it('chorus_get_team returns team details', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeTeam({ members: [makeUser()] } as any), 'team') }); + const result = await getToolHandler('chorus_get_team').handler({ team_id: 'team-001', response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Enterprise Sales'); + }); + + it('chorus_get_team_members returns members', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeUser()], 'user') }); + const result = await getToolHandler('chorus_get_team_members').handler({ team_id: 'team-001', limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Jane Smith'); + }); +}); + +describe('Playlist Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerPlaylistTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_playlists returns playlists', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makePlaylist()], 'playlist') }); + const result = await getToolHandler('chorus_list_playlists').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Best Discovery Calls'); + }); + + it('chorus_get_playlist returns playlist details', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makePlaylist(), 'playlist') }); + const result = await getToolHandler('chorus_get_playlist').handler({ playlist_id: 'pl-001', response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Best Discovery Calls'); + }); +}); + +describe('Email Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerEmailTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_emails returns emails', async () => { + // Email fixture uses `name` for subject and nested `email.initiator` for sender + const emailFixture = { + ...makeEmail(), + name: 'Follow-up: Discovery Call', + email: { initiator: { name: 'Jane Smith', email: 'jane@ourco.com' }, sent_time: '2024-06-15T16:00:00Z' }, + }; + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([emailFixture], 'email') }); + const result = await getToolHandler('chorus_list_emails').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Follow-up: Discovery Call'); + expect(result.content[0].text).toContain('Jane Smith'); + }); + + it('chorus_get_email returns email details', async () => { + const emailFixture = { + ...makeEmail(), + name: 'Follow-up: Discovery Call', + email: { initiator: { name: 'Jane Smith', email: 'jane@ourco.com' }, sent_time: '2024-06-15T16:00:00Z' }, + owner: { name: 'Jane Smith', email: 'jane@ourco.com' }, + participants: [ + { name: 'Jane Smith', email: 'jane@ourco.com', type: 'rep' }, + { name: 'Bob Johnson', email: 'bob@acme.com', type: 'prospect' }, + ], + }; + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(emailFixture, 'email') }); + const result = await getToolHandler('chorus_get_email').handler({ email_id: 'email-001', response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('jane@ourco.com'); + expect(result.content[0].text).toContain('bob@acme.com'); + }); +}); + +describe('Engagement Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerEngagementTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_filter_engagements returns engagements', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeEngagement()], 'engagement') }); + const result = await getToolHandler('chorus_filter_engagements').handler({ + engagement_type: 'all', limit: 20, offset: 0, response_format: 'markdown', + }, {} as any); + expect(result.content[0].text).toContain('meeting'); + }); +}); + +describe('Report Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerReportTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_reports returns reports', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeReport()], 'report') }); + const result = await getToolHandler('chorus_list_reports').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Weekly Activity Report'); + }); + + it('chorus_get_activity_metrics returns metrics', async () => { + // Activity metrics is not JSON:API, it returns a plain object + mockedAxios.mockResolvedValue({ data: makeActivityMetrics() }); + const result = await getToolHandler('chorus_get_activity_metrics').handler({ + start_date: '2024-06-01', end_date: '2024-06-30', response_format: 'markdown', + }, {} as any); + expect(result.content[0].text).toContain('24'); + expect(result.content[0].text).toContain('45%'); + }); +}); + +describe('Saved Search Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerSavedSearchTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_saved_searches returns searches', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeSavedSearch()], 'saved_search') }); + const result = await getToolHandler('chorus_list_saved_searches').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Competitor X Mentions'); + }); + + it('chorus_execute_saved_search returns results', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeConversation()], 'conversation') }); + const result = await getToolHandler('chorus_execute_saved_search').handler({ search_id: 'ss-001', limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Discovery Call'); + }); +}); + +describe('Video Conference Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerVideoConferenceTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_video_conferences returns conferences', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeVideoConference()], 'video_conference') }); + const result = await getToolHandler('chorus_list_video_conferences').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Weekly Pipeline Review'); + }); + + it('chorus_upload_recording creates a recording', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeVideoConference({ id: 'vc-new' }), 'video_conference') }); + const result = await getToolHandler('chorus_upload_recording').handler({ + title: 'External Call', + recording_url: 'https://example.com/recording.mp4', + participants: [{ name: 'Jane', email: 'jane@ourco.com' }], + date: '2024-06-15T14:00:00Z', + }, {} as any); + expect(result.content[0].text).toContain('uploaded successfully'); + }); + + it('chorus_delete_recording deletes a recording', async () => { + mockedAxios.mockResolvedValue({ data: undefined }); + const result = await getToolHandler('chorus_delete_recording').handler({ conference_id: 'vc-001' }, {} as any); + expect(result.content[0].text).toContain('deleted successfully'); + }); +}); + +describe('Integration Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerIntegrationTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + it('chorus_list_integrations returns integrations', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeIntegration()], 'integration') }); + const result = await getToolHandler('chorus_list_integrations').handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('Salesforce CRM'); + }); + + it('chorus_get_session returns session info', async () => { + // Session endpoint returns plain JSON, not JSON:API + mockedAxios.mockResolvedValue({ data: { userId: 'user-001', role: 'admin' } }); + const result = await getToolHandler('chorus_get_session').handler({ response_format: 'markdown' }, {} as any); + expect(result.content[0].text).toContain('user-001'); + }); +}); diff --git a/tests/unit/tools/scorecards.test.ts b/tests/unit/tools/scorecards.test.ts new file mode 100644 index 0000000..53761c2 --- /dev/null +++ b/tests/unit/tools/scorecards.test.ts @@ -0,0 +1,101 @@ +import axios from 'axios'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerScorecardTools } from '../../../src/tools/scorecards.js'; +import { makeScorecard, makeScorecardTemplate, wrapJsonApiList, wrapJsonApiSingle } from '../../fixtures/chorus-responses.js'; + +jest.mock('axios'); +const mockedAxios = axios as jest.MockedFunction; + +describe('Scorecard Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerScorecardTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + describe('chorus_list_scorecards', () => { + it('returns scorecards list', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeScorecard()], 'scorecard') }); + + const tool = getToolHandler('chorus_list_scorecards'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Alex Rep'); + expect(result.content[0].text).toContain('35/50'); + }); + + it('passes all filters to API', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_list_scorecards'); + await tool.handler({ + user_id: 'user-002', + conversation_id: 'conv-001', + template_id: 'tmpl-001', + start_date: '2024-01-01', + end_date: '2024-12-31', + limit: 20, + offset: 0, + response_format: 'markdown', + }, {} as any); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ + "filter[user_id]": 'user-002', + "filter[conversation_id]": 'conv-001', + "filter[template_id]": 'tmpl-001', + }), + }) + ); + }); + }); + + describe('chorus_get_scorecard', () => { + it('returns scorecard with criteria table', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeScorecard(), 'scorecard') }); + + const tool = getToolHandler('chorus_get_scorecard'); + const result = await tool.handler({ scorecard_id: 'sc-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Metrics'); + expect(result.content[0].text).toContain('Economic Buyer'); + expect(result.content[0].text).toContain('70%'); + expect(result.content[0].text).toContain('Good probing questions'); + }); + }); + + describe('chorus_list_scorecard_templates', () => { + it('returns template list', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeScorecardTemplate()], 'scorecard_template') }); + + const tool = getToolHandler('chorus_list_scorecard_templates'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('MEDDIC'); + expect(result.content[0].text).toContain('5 criteria'); + }); + }); + + describe('chorus_get_scorecard_template', () => { + it('returns template with criteria details', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeScorecardTemplate(), 'scorecard_template') }); + + const tool = getToolHandler('chorus_get_scorecard_template'); + const result = await tool.handler({ template_id: 'tmpl-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('MEDDIC'); + expect(result.content[0].text).toContain('Quantifiable measures of value'); + }); + }); +}); diff --git a/tests/unit/tools/users.test.ts b/tests/unit/tools/users.test.ts new file mode 100644 index 0000000..63254e6 --- /dev/null +++ b/tests/unit/tools/users.test.ts @@ -0,0 +1,83 @@ +import axios from 'axios'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerUserTools } from '../../../src/tools/users.js'; +import { makeUser, wrapJsonApiList, wrapJsonApiSingle } from '../../fixtures/chorus-responses.js'; + +jest.mock('axios'); +const mockedAxios = axios as jest.MockedFunction; + +describe('User Tools', () => { + let server: McpServer; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv, CHORUS_API_KEY: 'test-key' }; + server = new McpServer({ name: 'test', version: '1.0.0' }); + registerUserTools(server); + }); + + afterAll(() => { process.env = originalEnv; }); + + function getToolHandler(name: string) { + return (server as any)._registeredTools[name]; + } + + describe('chorus_list_users', () => { + it('returns users list', async () => { + const users = [makeUser(), makeUser({ id: 'user-002', name: 'Alex Rep', email: 'alex@ourco.com' })]; + mockedAxios.mockResolvedValue({ data: wrapJsonApiList(users, 'user') }); + + const tool = getToolHandler('chorus_list_users'); + const result = await tool.handler({ limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Jane Smith'); + expect(result.content[0].text).toContain('Alex Rep'); + }); + + it('filters by team and role', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_list_users'); + await tool.handler({ team_id: 'team-001', role: 'manager', limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(mockedAxios).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ "filter[team_id]": 'team-001', "filter[role]": 'manager' }), + }) + ); + }); + }); + + describe('chorus_get_user', () => { + it('returns user profile', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiSingle(makeUser(), 'user') }); + + const tool = getToolHandler('chorus_get_user'); + const result = await tool.handler({ user_id: 'user-001', response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Jane Smith'); + expect(result.content[0].text).toContain('jane@ourco.com'); + }); + }); + + describe('chorus_search_users', () => { + it('returns matching users', async () => { + mockedAxios.mockResolvedValue({ data: wrapJsonApiList([makeUser()], 'user') }); + + const tool = getToolHandler('chorus_search_users'); + const result = await tool.handler({ query: 'jane', limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('Jane Smith'); + }); + + it('returns message when no users match', async () => { + mockedAxios.mockResolvedValue({ data: { data: [], meta: { page: { total: 0 } } } }); + + const tool = getToolHandler('chorus_search_users'); + const result = await tool.handler({ query: 'nobody', limit: 20, offset: 0, response_format: 'markdown' }, {} as any); + + expect(result.content[0].text).toContain('No users found'); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..654b018 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..4681987 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist-test", + "types": ["node", "jest"] + }, + "include": ["src/**/*", "tests/**/*"] +} From 932605663c60260f3a70c37c994e94e8a5b765cd Mon Sep 17 00:00:00 2001 From: Ompragash Viswanathan Date: Sat, 21 Feb 2026 01:41:07 +0530 Subject: [PATCH 2/2] fix: silence ts-jest TS151002 warnings in test runs Set isolatedModules, module commonjs, and moduleResolution node in tsconfig.test.json to satisfy ts-jest's requirement when the base tsconfig uses module Node16. Production tsconfig unchanged. --- tsconfig.test.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsconfig.test.json b/tsconfig.test.json index 4681987..b7d59fc 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -3,6 +3,9 @@ "compilerOptions": { "rootDir": ".", "outDir": "./dist-test", + "module": "commonjs", + "moduleResolution": "node", + "isolatedModules": true, "types": ["node", "jest"] }, "include": ["src/**/*", "tests/**/*"]