diff --git a/.github/workflows/mcp-server-ci.yml b/.github/workflows/mcp-server-ci.yml
new file mode 100644
index 0000000..e13a556
--- /dev/null
+++ b/.github/workflows/mcp-server-ci.yml
@@ -0,0 +1,209 @@
+name: MCP Server CI/CD
+
+on:
+ push:
+ branches:
+ - main
+ - master
+ - develop
+ paths:
+ - 'packages/mcp-server/**'
+ - '.github/workflows/mcp-server-ci.yml'
+ pull_request:
+ paths:
+ - 'packages/mcp-server/**'
+ - '.github/workflows/mcp-server-ci.yml'
+ release:
+ types: [published]
+
+env:
+ REGISTRY: docker.io
+ IMAGE_NAME: coorchat/mcp-server
+ NODE_VERSION: '18'
+
+jobs:
+ lint-and-test:
+ name: Lint & Test
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: packages/mcp-server
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+ cache-dependency-path: packages/mcp-server/package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run linter
+ run: npm run lint
+
+ - name: Run type check
+ run: npx tsc --noEmit
+
+ - name: Run unit tests
+ run: npm test -- --coverage
+
+ - name: Upload coverage reports
+ uses: codecov/codecov-action@v4
+ with:
+ files: ./packages/mcp-server/coverage/lcov.info
+ flags: mcp-server
+ name: mcp-server-coverage
+ fail_ci_if_error: false
+
+ build:
+ name: Build TypeScript
+ runs-on: ubuntu-latest
+ needs: lint-and-test
+ defaults:
+ run:
+ working-directory: packages/mcp-server
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+ cache-dependency-path: packages/mcp-server/package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build project
+ run: npm run build
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: mcp-server-dist
+ path: packages/mcp-server/dist
+ retention-days: 7
+
+ integration-test:
+ name: Integration Tests
+ runs-on: ubuntu-latest
+ needs: build
+ defaults:
+ run:
+ working-directory: packages/mcp-server
+
+ services:
+ redis:
+ image: redis:7-alpine
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ - 6379:6379
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+ cache-dependency-path: packages/mcp-server/package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run integration tests
+ run: npm run test:integration
+ env:
+ REDIS_URL: redis://localhost:6379
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ docker-build:
+ name: Build Docker Image
+ runs-on: ubuntu-latest
+ needs: [lint-and-test, build]
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=sha,prefix={{branch}}-
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: ./packages/mcp-server
+ file: ./packages/mcp-server/Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ publish-npm:
+ name: Publish to npm
+ runs-on: ubuntu-latest
+ needs: [integration-test, docker-build]
+ if: github.event_name == 'release'
+ defaults:
+ run:
+ working-directory: packages/mcp-server
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build project
+ run: npm run build
+
+ - name: Publish to npm
+ run: npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/relay-server-ci.yml b/.github/workflows/relay-server-ci.yml
new file mode 100644
index 0000000..d6cd869
--- /dev/null
+++ b/.github/workflows/relay-server-ci.yml
@@ -0,0 +1,186 @@
+name: Relay Server CI/CD
+
+on:
+ push:
+ branches:
+ - main
+ - master
+ - develop
+ paths:
+ - 'packages/relay-server/**'
+ - '.github/workflows/relay-server-ci.yml'
+ pull_request:
+ paths:
+ - 'packages/relay-server/**'
+ - '.github/workflows/relay-server-ci.yml'
+ release:
+ types: [published]
+
+env:
+ REGISTRY: docker.io
+ IMAGE_NAME: coorchat/relay-server
+ DOTNET_VERSION: '8.0'
+
+jobs:
+ build-and-test:
+ name: Build & Test
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: packages/relay-server
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build solution
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Run unit tests
+ run: dotnet test tests/CoorChat.RelayServer.Tests.Unit/CoorChat.RelayServer.Tests.Unit.csproj --no-build --configuration Release --logger trx --collect:"XPlat Code Coverage"
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-results
+ path: packages/relay-server/**/*.trx
+ retention-days: 7
+
+ - name: Upload coverage reports
+ uses: codecov/codecov-action@v4
+ with:
+ files: ./packages/relay-server/**/coverage.cobertura.xml
+ flags: relay-server
+ name: relay-server-coverage
+ fail_ci_if_error: false
+
+ integration-test:
+ name: Integration Tests
+ runs-on: ubuntu-latest
+ needs: build-and-test
+ defaults:
+ run:
+ working-directory: packages/relay-server
+
+ services:
+ postgres:
+ image: postgres:16-alpine
+ env:
+ POSTGRES_USER: coorchat
+ POSTGRES_PASSWORD: test_password
+ POSTGRES_DB: coorchat_test
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ - 5432:5432
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build solution
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Run integration tests
+ run: dotnet test tests/CoorChat.RelayServer.Tests.Integration/CoorChat.RelayServer.Tests.Integration.csproj --no-build --configuration Release --logger trx
+ env:
+ ConnectionStrings__DefaultConnection: "Host=localhost;Port=5432;Database=coorchat_test;Username=coorchat;Password=test_password"
+
+ docker-build:
+ name: Build Docker Image
+ runs-on: ubuntu-latest
+ needs: [build-and-test, integration-test]
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=sha,prefix={{branch}}-
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: ./packages/relay-server
+ file: ./packages/relay-server/Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ publish-nuget:
+ name: Publish to NuGet
+ runs-on: ubuntu-latest
+ needs: [integration-test, docker-build]
+ if: github.event_name == 'release'
+ defaults:
+ run:
+ working-directory: packages/relay-server
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build solution
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Pack NuGet packages
+ run: |
+ dotnet pack src/CoorChat.RelayServer.Core/CoorChat.RelayServer.Core.csproj --no-build --configuration Release --output ./nupkgs
+ dotnet pack src/CoorChat.RelayServer.Data/CoorChat.RelayServer.Data.csproj --no-build --configuration Release --output ./nupkgs
+
+ - name: Publish to NuGet
+ run: dotnet nuget push "./nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ad0a823
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# Dependencies
+node_modules/
+packages/*/node_modules/
+
+# Build outputs
+dist/
+build/
+out/
+*.js.map
+*.d.ts.map
+
+# TypeScript
+*.tsbuildinfo
+
+# .NET
+bin/
+obj/
+*.user
+*.suo
+packages/relay-server/packages/
+
+# Logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Environment variables
+.env
+.env.local
+.env.*.local
+*.env
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Testing
+coverage/
+.nyc_output/
+
+# Config
+.coorchat/config.json
+.coorchat/*.json
+!.coorchat/config.template.yaml
+
+# Temporary files
+*.tmp
+*.temp
+.cache/
+
+# Docker
+.dockerignore
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..3f387de
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,29 @@
+# coorchat Development Guidelines
+
+Auto-generated from all feature plans. Last updated: 2026-02-14
+
+## Active Technologies
+
+
+
+## Project Structure
+
+```text
+src/
+tests/
+```
+
+## Commands
+
+# Add commands for
+
+## Code Style
+
+General: Follow standard conventions
+
+## Recent Changes
+
+
+
+
+
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..4448808
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,355 @@
+# CoorChat Local Installation Guide
+
+## Prerequisites
+
+- **Node.js** 18+ and npm
+- **Docker** and Docker Compose (optional, for Redis/Relay Server)
+- **Git** (for cloning)
+- **Claude Desktop** (for MCP integration)
+
+## Quick Start (5 minutes)
+
+### Step 1: Install Dependencies
+
+```bash
+# Navigate to MCP server package
+cd packages/mcp-server
+
+# Install dependencies
+npm install
+
+# Build the project
+npm run build
+```
+
+### Step 2: Generate Secure Token
+
+```bash
+# Generate a secure shared token (on Windows/Git Bash)
+node -e "console.log('cct_' + require('crypto').randomBytes(32).toString('hex'))"
+
+# Save the output - you'll need it for all agents
+# Example output: cct_a3f8d9e2c1b4f7a6e8d2c9b1f4a7e3d2c6b9f1e4a8d3c7b2f5e9a1d4c8b3f6
+```
+
+### Step 3: Choose Your Channel
+
+You have **3 options** - pick the easiest for you:
+
+#### **Option A: Redis (Recommended - Most Reliable)**
+
+```bash
+# Start Redis using Docker
+docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine
+
+# Or install Redis locally on Windows:
+# Download from: https://github.com/microsoftarchive/redis/releases
+```
+
+#### **Option B: Discord (Easiest - No Setup)**
+
+1. Go to https://discord.com/developers/applications
+2. Create a new application
+3. Go to "Bot" β "Add Bot"
+4. Copy the bot token
+5. Enable "Message Content Intent"
+6. Invite bot to your server using OAuth2 URL generator
+7. Create a channel and copy its ID (right-click β Copy ID)
+
+#### **Option C: SignalR Relay Server (Advanced)**
+
+```bash
+# From repo root
+cd packages/relay-server
+
+# Build and run with Docker
+docker build -t coorchat-relay .
+docker run -d -p 5001:5001 -e Authentication__SharedToken=YOUR_TOKEN coorchat-relay
+```
+
+### Step 4: Configure MCP Server for Claude
+
+Create a config file at: `C:\Users\YourUser\.claude\claude_desktop_config.json`
+
+```json
+{
+ "mcpServers": {
+ "coorchat": {
+ "command": "node",
+ "args": [
+ "C:\\projects\\coorchat\\packages\\mcp-server\\dist\\index.js"
+ ],
+ "env": {
+ "CHANNEL_TYPE": "redis",
+ "REDIS_HOST": "localhost",
+ "REDIS_PORT": "6379",
+ "SHARED_TOKEN": "cct_YOUR_TOKEN_FROM_STEP2",
+ "AGENT_ID": "agent-claude-1",
+ "AGENT_ROLE": "developer",
+ "LOG_LEVEL": "info"
+ }
+ }
+ }
+}
+```
+
+**For Discord instead:**
+```json
+{
+ "mcpServers": {
+ "coorchat": {
+ "command": "node",
+ "args": [
+ "C:\\projects\\coorchat\\packages\\mcp-server\\dist\\index.js"
+ ],
+ "env": {
+ "CHANNEL_TYPE": "discord",
+ "DISCORD_BOT_TOKEN": "YOUR_BOT_TOKEN",
+ "DISCORD_CHANNEL_ID": "YOUR_CHANNEL_ID",
+ "SHARED_TOKEN": "cct_YOUR_TOKEN_FROM_STEP2",
+ "AGENT_ID": "agent-claude-1",
+ "AGENT_ROLE": "developer",
+ "LOG_LEVEL": "info"
+ }
+ }
+ }
+}
+```
+
+### Step 5: Configure GitHub Integration (Optional)
+
+```json
+{
+ "mcpServers": {
+ "coorchat": {
+ "command": "node",
+ "args": [
+ "C:\\projects\\coorchat\\packages\\mcp-server\\dist\\index.js"
+ ],
+ "env": {
+ "CHANNEL_TYPE": "redis",
+ "REDIS_HOST": "localhost",
+ "REDIS_PORT": "6379",
+ "SHARED_TOKEN": "cct_YOUR_TOKEN",
+ "AGENT_ID": "agent-claude-1",
+ "AGENT_ROLE": "developer",
+ "GITHUB_TOKEN": "ghp_YOUR_GITHUB_PAT",
+ "GITHUB_OWNER": "your-org",
+ "GITHUB_REPO": "your-repo",
+ "GITHUB_WEBHOOK_SECRET": "your_webhook_secret",
+ "LOG_LEVEL": "info"
+ }
+ }
+ }
+}
+```
+
+**Get GitHub Token:**
+1. Go to https://github.com/settings/tokens
+2. Generate new token (classic)
+3. Select scopes: `repo`, `read:org`
+4. Copy the token
+
+### Step 6: Restart Claude Desktop
+
+Close and reopen Claude Desktop to load the MCP server.
+
+## Verify Installation
+
+### Test 1: Check MCP Server is Running
+
+In Claude Desktop, type:
+```
+Can you check if the coorchat MCP server is connected?
+```
+
+You should see the MCP tools available in Claude's context.
+
+### Test 2: Run Integration Tests
+
+```bash
+cd packages/mcp-server
+npm test
+```
+
+Expected output:
+```
+Test Files 2 passed (2)
+Tests 34 passed (34)
+```
+
+### Test 3: Manual Channel Test
+
+Create a test script `test-connection.js`:
+
+```javascript
+import { ChannelFactory } from './dist/channels/base/ChannelFactory.js';
+
+const config = {
+ type: 'redis',
+ token: 'cct_YOUR_TOKEN',
+ connectionParams: {
+ host: 'localhost',
+ port: 6379,
+ },
+};
+
+const channel = ChannelFactory.create(config);
+await channel.connect();
+console.log('β
Connected to channel!');
+
+channel.onMessage((message) => {
+ console.log('π¨ Received:', message);
+});
+
+await channel.disconnect();
+```
+
+Run it:
+```bash
+node test-connection.js
+```
+
+## Environment Variables Reference
+
+| Variable | Required | Description | Example |
+|----------|----------|-------------|---------|
+| `CHANNEL_TYPE` | Yes | Channel type | `redis`, `discord`, `signalr` |
+| `SHARED_TOKEN` | Yes | Auth token (16+ chars) | `cct_a3f8d9...` |
+| `AGENT_ID` | Yes | Unique agent identifier | `agent-claude-1` |
+| `AGENT_ROLE` | Yes | Agent role | `developer`, `tester`, `architect` |
+| `REDIS_HOST` | If redis | Redis hostname | `localhost` |
+| `REDIS_PORT` | If redis | Redis port | `6379` |
+| `REDIS_PASSWORD` | If redis | Redis password | `your_password` |
+| `REDIS_TLS` | If redis | Enable TLS | `true`, `false` |
+| `DISCORD_BOT_TOKEN` | If discord | Discord bot token | `MTk4...` |
+| `DISCORD_CHANNEL_ID` | If discord | Discord channel ID | `123456789...` |
+| `SIGNALR_HUB_URL` | If signalr | SignalR hub URL | `https://localhost:5001/agentHub` |
+| `GITHUB_TOKEN` | Optional | GitHub PAT | `ghp_...` |
+| `GITHUB_OWNER` | Optional | GitHub org/user | `your-org` |
+| `GITHUB_REPO` | Optional | GitHub repo | `your-repo` |
+| `GITHUB_WEBHOOK_SECRET` | Optional | Webhook secret | `your_secret` |
+| `LOG_LEVEL` | Optional | Log verbosity | `debug`, `info`, `warn`, `error` |
+
+## Multi-Agent Setup (Testing Coordination)
+
+To test multiple agents coordinating:
+
+### Terminal 1: Developer Agent
+```bash
+cd packages/mcp-server
+CHANNEL_TYPE=redis \
+REDIS_HOST=localhost \
+REDIS_PORT=6379 \
+SHARED_TOKEN=cct_YOUR_TOKEN \
+AGENT_ID=agent-dev-1 \
+AGENT_ROLE=developer \
+node dist/index.js
+```
+
+### Terminal 2: Tester Agent
+```bash
+cd packages/mcp-server
+CHANNEL_TYPE=redis \
+REDIS_HOST=localhost \
+REDIS_PORT=6379 \
+SHARED_TOKEN=cct_YOUR_TOKEN \
+AGENT_ID=agent-test-1 \
+AGENT_ROLE=tester \
+node dist/index.js
+```
+
+### Terminal 3: Monitor Redis Messages
+```bash
+docker exec -it coorchat-redis redis-cli
+> SUBSCRIBE coorchat:channel
+```
+
+## Troubleshooting
+
+### "Cannot find module" errors
+```bash
+cd packages/mcp-server
+npm run build
+```
+
+### "ECONNREFUSED" on Redis
+```bash
+# Check Redis is running
+docker ps | grep redis
+
+# Or start it
+docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine
+```
+
+### MCP server not showing in Claude
+1. Check Claude Desktop logs: `%APPDATA%\Claude\logs\`
+2. Verify paths in `claude_desktop_config.json` use absolute paths
+3. Ensure backslashes are escaped: `C:\\projects\\...`
+4. Restart Claude Desktop completely
+
+### "Invalid authentication token" errors
+- Ensure `SHARED_TOKEN` is the same across all agents
+- Token must be 16+ characters
+- Use the `cct_` prefix for channel tokens
+
+### GitHub integration not working
+- Verify `GITHUB_TOKEN` has `repo` scope
+- Check `GITHUB_OWNER` and `GITHUB_REPO` are correct
+- Ensure repo exists and token has access
+
+## Docker Compose Quick Start
+
+From repo root:
+
+```bash
+# Start everything (Redis + Relay Server)
+docker-compose up -d
+
+# View logs
+docker-compose logs -f
+
+# Stop everything
+docker-compose down
+```
+
+Then configure Claude Desktop to use Redis at `localhost:6379`.
+
+## Next Steps
+
+Once installed:
+
+1. **Test basic coordination**: Create a task and assign it to an agent
+2. **Set up GitHub sync**: Connect to a repo and sync issues
+3. **Add more agents**: Create multiple Claude Desktop profiles or standalone agents
+4. **Monitor coordination**: Watch agents communicate and coordinate work
+
+## Development Mode
+
+For active development:
+
+```bash
+cd packages/mcp-server
+
+# Watch mode (auto-rebuild on changes)
+npm run dev
+
+# Run tests on save
+npm test -- --watch
+```
+
+## Uninstall
+
+```bash
+# Remove Docker containers
+docker-compose down -v
+docker rm -f coorchat-redis
+
+# Remove MCP server from Claude config
+# Edit: C:\Users\YourUser\.claude\claude_desktop_config.json
+# Remove the "coorchat" entry
+
+# Remove node_modules
+cd packages/mcp-server
+rm -rf node_modules dist
+```
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ebb97ce
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Stuart Fraser and CoorChat Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..267728b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,571 @@
+# π€ CoorChat
+
+
+
+**Multi-Agent Coordination System for AI-Powered Software Development**
+
+[](./packages/mcp-server/tests)
+[](https://www.typescriptlang.org/)
+[](https://nodejs.org/)
+[](./LICENSE)
+
+*Enable teams of specialized AI agents to coordinate on shared software development tasks through secure, real-time communication channels.*
+
+[Quick Start](#-quick-start) β’ [Documentation](#-documentation) β’ [Examples](#-example-scenarios) β’ [CLI Reference](#-cli-tools)
+
+
+
+---
+
+## π What is CoorChat?
+
+CoorChat is a **multi-agent coordination platform** that allows specialized AI agents (developers, testers, architects, security auditors, etc.) to collaborate on software development tasks just like human teams do.
+
+### The Problem
+
+AI agents working in isolation can't coordinate complex workflows:
+- β No shared context across multiple agents
+- β Duplicated work or conflicting changes
+- β No visibility into what other agents are doing
+- β Manual task assignment and dependency management
+
+### The Solution
+
+CoorChat provides a **secure coordination layer** enabling:
+- β
**Real-time communication** between specialized agents
+- β
**Automatic task distribution** based on agent capabilities
+- β
**Dependency tracking** and conflict resolution
+- β
**GitHub integration** for seamless issue/PR synchronization
+- β
**Human oversight** through monitoring and audit trails
+
+---
+
+## β¨ Key Features
+
+### π Multi-Agent Coordination
+Agents with different specializations (developer, tester, architect, security, infrastructure, documentation) work together on shared tasks with automatic role matching and capability-based assignment.
+
+### π Secure Communication
+- Token-based authentication with timing-safe comparison
+- HMAC message signing for integrity verification
+- TLS/HTTPS enforcement for all channels
+- Cryptographically secure token generation
+
+### π‘ Multiple Channel Types
+Choose the communication channel that fits your infrastructure:
+- **Redis** - Fast, reliable pub/sub (recommended for self-hosted)
+- **Discord** - Zero setup, great for testing and small teams
+- **SignalR** - Enterprise-grade .NET relay server for distributed teams
+
+### π GitHub Integration
+- Automatic synchronization of issues and pull requests
+- Webhook + polling fallback for reliability
+- Task creation from GitHub events
+- Bi-directional updates (agent actions β GitHub comments)
+
+### π― Smart Task Management
+- **Dependency tracking** with cycle detection
+- **Conflict resolution** when multiple agents claim the same task
+- **Priority queuing** (critical β high β medium β low)
+- **Lifecycle events** (assigned β started β blocked β progress β completed)
+
+### π₯ Extensible Role System
+8 predefined roles + custom role support:
+```
+developer, tester, architect, frontend, backend,
+infrastructure, security-auditor, documentation-writer
+```
+
+### π Monitoring & Observability
+- Real-time activity monitoring
+- Structured logging (JSON format)
+- Task lifecycle tracking
+- Agent timeout detection
+
+---
+
+## ποΈ Architecture
+
+```
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β GitHub Issues/PRs β
+ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
+ β
+ βΌ
+ βββββββββββββββββ
+ β GitHub Sync β (Webhook + Polling)
+ β Manager β
+ βββββββββ¬ββββββββ
+ β
+ βΌ
+ βββββββββββββββββ
+ β Task Queue β βββββ Priority, Dependencies
+ βββββββββ¬ββββββββ
+ β
+ ββββββββββββββΌβββββββββββββ
+ β β β
+ βΌ βΌ βΌ
+βββββββββ βββββββββ βββββββββ
+β Agent β β Agent β β Agent β Developer, Tester, etc.
+β 1 β β 2 β β 3 β
+βββββ¬ββββ βββββ¬ββββ βββββ¬ββββ
+ β β β
+ βββββββββββββΌββββββββββββ
+ β
+ βΌ
+ βββββββββββββββββββββββββ
+ β Communication Layer β
+ β (Redis/Discord/ β
+ β SignalR) β
+ βββββββββββββββββββββββββ
+```
+
+### Components
+
+**MCP Server** (`packages/mcp-server/`)
+- TypeScript/Node.js coordination client
+- Integrates with Claude Desktop via MCP protocol
+- Handles agent registration, task management, messaging
+
+**Relay Server** (`packages/relay-server/`)
+- C#/.NET SignalR hub (optional)
+- Enterprise-grade relay for distributed teams
+- Token authentication middleware
+
+---
+
+## π Quick Start
+
+### Prerequisites
+
+- **Node.js** 18+ ([Download](https://nodejs.org/))
+- **Docker** (optional, for Redis) ([Download](https://www.docker.com/))
+- **Git** ([Download](https://git-scm.com/))
+
+### Option 1: Automated Setup β‘ (Recommended)
+
+**Linux/macOS:**
+```bash
+git clone https://github.com/stuartf303/coorchat.git
+cd coorchat
+chmod +x quick-start.sh
+./quick-start.sh
+```
+
+**Windows (PowerShell):**
+```powershell
+git clone https://github.com/stuartf303/coorchat.git
+cd coorchat
+.\quick-start.ps1
+```
+
+This will:
+1. β
Install all dependencies
+2. β
Generate secure authentication token
+3. β
Set up your chosen channel (Redis/Discord/SignalR)
+4. β
Run the test suite (34 tests)
+5. β
Generate Claude Desktop configuration
+
+### Option 2: Manual Installation
+
+```bash
+# 1. Clone and install
+git clone https://github.com/stuartf303/coorchat.git
+cd coorchat/packages/mcp-server
+npm install
+npm run build
+
+# 2. Generate secure token
+npm run cli -- token generate
+# Output: cct_a3f8d9e2c1b4f7a6e8d2c9b1f4a7e3d2...
+
+# 3. Start Redis (or use Discord/SignalR)
+docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine
+
+# 4. Configure environment
+cat > .env << EOF
+CHANNEL_TYPE=redis
+REDIS_HOST=localhost
+REDIS_PORT=6379
+SHARED_TOKEN=cct_YOUR_TOKEN_FROM_STEP2
+AGENT_ID=agent-1
+AGENT_ROLE=developer
+EOF
+
+# 5. Start an agent
+npm run cli -- agent start --role developer
+```
+
+### Verify Installation
+
+```bash
+# Run tests
+npm test
+
+# Expected output:
+# Test Files 2 passed (2)
+# Tests 34 passed (34)
+```
+
+---
+
+## π― Example Scenarios
+
+### Scenario 1: Feature Development Workflow
+
+Coordinate developer, tester, and documentation agents on a new feature:
+
+```bash
+# Terminal 1: Developer Agent
+AGENT_ID=dev-1 AGENT_ROLE=developer npm run cli -- agent start
+
+# Terminal 2: Tester Agent
+AGENT_ID=test-1 AGENT_ROLE=tester npm run cli -- agent start
+
+# Terminal 3: Documentation Agent
+AGENT_ID=doc-1 AGENT_ROLE=documentation-writer npm run cli -- agent start
+
+# Terminal 4: Monitor Activity
+npm run cli -- monitor
+```
+
+**What happens:**
+1. GitHub issue created: "Add user authentication"
+2. Developer agent picks up task, implements feature
+3. Tester agent automatically notified when code is ready
+4. Tester writes and runs tests
+5. Documentation agent updates API docs
+6. All agents report completion β task marked done
+
+### Scenario 2: Bug Fix Coordination
+
+Critical bug workflow with automatic triage and deployment:
+
+```bash
+# Agents automatically coordinate through these stages:
+User reports bug β Triage analysis β Developer fixes β
+Tester validates β Infrastructure deploys β Done
+```
+
+**See [SCENARIOS.md](./SCENARIOS.md) for 5 complete workflow examples** including:
+- Feature development
+- Bug fix coordination
+- Code review pipeline
+- Infrastructure deployment
+- Security audit
+
+---
+
+## π οΈ CLI Tools
+
+CoorChat includes a full-featured CLI for managing agents, tokens, and monitoring:
+
+```bash
+# Token Management
+npm run cli -- token generate # Generate secure token
+npm run cli -- token validate # Validate token format
+npm run cli -- token hash # SHA-256 hash
+
+# Agent Control
+npm run cli -- agent start --role developer
+npm run cli -- agent start --id my-agent --role tester
+npm run cli -- agent list # List active agents
+
+# Role Management
+npm run cli -- role list # Show all available roles
+npm run cli -- role suggest testing security # Suggest roles by capability
+
+# Configuration
+npm run cli -- config show # Show current config
+npm run cli -- config init --channel redis # Initialize config
+
+# Monitoring
+npm run cli -- monitor # Watch real-time coordination
+```
+
+**Full CLI documentation:** [CLI.md](./packages/mcp-server/CLI.md)
+
+---
+
+## π Documentation
+
+### Getting Started
+- **[Installation Guide](./INSTALL.md)** - Complete setup for all platforms and channels
+- **[Quick Start Scripts](./quick-start.sh)** - Automated installation
+- **[CLI Reference](./packages/mcp-server/CLI.md)** - Command-line tool documentation
+
+### Guides & Examples
+- **[Example Scenarios](./SCENARIOS.md)** - Real-world coordination workflows
+- **[Specifications](./specs/001-multi-agent-coordination/)** - Feature specs and design docs
+
+### Configuration
+- **[Environment Variables](./INSTALL.md#environment-variables-reference)** - All config options
+- **[Channel Setup](./INSTALL.md#step-3-choose-your-channel)** - Redis, Discord, SignalR
+
+### Development
+- **[Project Structure](#project-structure)** - Codebase organization
+- **[Contributing](#contributing)** - Development workflow
+- **[Testing](#testing)** - Running tests
+
+---
+
+## ποΈ Project Structure
+
+```
+coorchat/
+βββ packages/
+β βββ mcp-server/ # TypeScript/Node.js MCP Server
+β β βββ src/
+β β β βββ agents/ # Agent registry, roles, capabilities
+β β β βββ channels/ # Discord, SignalR, Redis channels
+β β β βββ cli/ # Command-line interface
+β β β βββ config/ # Configuration, token generation
+β β β βββ github/ # GitHub integration (webhooks, polling)
+β β β βββ logging/ # Structured logging
+β β β βββ protocol/ # Message protocol, validation
+β β β βββ tasks/ # Task queue, dependencies, conflicts
+β β βββ tests/
+β β βββ integration/ # Integration test suites
+β β
+β βββ relay-server/ # C#/.NET SignalR Relay (optional)
+β βββ src/
+β βββ Api/ # SignalR hub, middleware
+β βββ Core/ # Authentication service
+β
+βββ specs/ # Feature specifications
+β βββ 001-multi-agent-coordination/
+β βββ spec.md # Feature specification
+β βββ plan.md # Implementation plan
+β βββ data-model.md # Entity models
+β βββ contracts/ # API contracts
+β βββ tasks.md # Task breakdown
+β
+βββ .github/ # CI/CD workflows
+β βββ workflows/
+β βββ mcp-server-ci.yml # TypeScript tests
+β βββ relay-server-ci.yml # C# tests
+β
+βββ quick-start.sh # Automated setup (Bash)
+βββ quick-start.ps1 # Automated setup (PowerShell)
+βββ docker-compose.yml # Local development stack
+βββ INSTALL.md # Installation guide
+βββ SCENARIOS.md # Example workflows
+βββ README.md # This file
+```
+
+---
+
+## π§ͺ Testing
+
+CoorChat includes comprehensive integration tests:
+
+```bash
+cd packages/mcp-server
+
+# Run all tests
+npm test
+
+# Run with coverage
+npm run test:coverage
+
+# Run integration tests only
+npm run test:integration
+```
+
+### Test Suites
+
+**Agent-Task Coordination** (10 tests)
+- Agent registration and discovery
+- Task assignment with role matching
+- Dependency tracking and automatic unblocking
+- Conflict resolution
+- Lifecycle event handling
+- Complete workflow orchestration
+
+**Secure Communication** (24 tests)
+- Token generation (entropy, uniqueness, formats)
+- Token validation (format, length, characters)
+- Token hashing (consistency, collision resistance)
+- Channel authentication (rejection, verification, timing-safety)
+- Message security (metadata, integrity, tampering detection)
+- Security best practices
+- TLS/encryption support
+- Edge cases
+
+**Current Status:** β
34/34 tests passing
+
+---
+
+## π Security Features
+
+### Authentication
+- **Token-based authentication** with 16+ character minimum
+- **Timing-safe comparison** prevents timing attacks
+- **SHA-256 token hashing** for secure storage
+- **Token prefixes** (cct_, cca_) for type identification
+
+### Message Security
+- **HMAC-SHA256 signatures** for Redis message integrity
+- **Correlation IDs** for request/response tracking
+- **Timestamp validation** for replay attack prevention
+
+### Transport Security
+- **TLS enforcement** for Redis (rediss://)
+- **HTTPS validation** for SignalR
+- **Environment-based policies** (production vs development)
+
+### Best Practices
+- **No plaintext token storage**
+- **Secure random generation** using crypto.randomBytes()
+- **Automatic security warnings** for insecure configurations
+
+---
+
+## π¨ Use Cases
+
+### Software Development Teams
+- Coordinate multiple AI agents on feature development
+- Automated code review pipeline
+- Bug triage and fix coordination
+- Documentation generation
+
+### DevOps & Infrastructure
+- Multi-stage deployment orchestration
+- Infrastructure as code coordination
+- Automated security audits
+- Compliance checking
+
+### Quality Assurance
+- Automated test generation
+- Regression test coordination
+- Coverage analysis
+- Performance testing
+
+### Research & Experimentation
+- Multi-agent AI research
+- Coordination algorithm testing
+- Custom workflow prototyping
+- Agent capability experiments
+
+---
+
+## πΊοΈ Roadmap
+
+### Current Version: MVP (v1.0)
+- β
Multi-agent coordination
+- β
3 channel types (Redis, Discord, SignalR)
+- β
GitHub integration
+- β
Secure authentication
+- β
Task dependencies
+- β
Conflict resolution
+- β
CLI tool
+- β
Comprehensive documentation
+
+### Planned Features
+- π² Web dashboard for monitoring
+- π² Metrics and analytics
+- π² Agent performance tracking
+- π² Advanced conflict resolution strategies
+- π² Multi-repository coordination
+- π² Slack/Teams integration
+- π² Plugin system
+- π² Agent marketplace
+
+---
+
+## π€ Contributing
+
+We welcome contributions! CoorChat follows the **Specify workflow**:
+
+1. **Specification** - Define the feature clearly
+2. **Clarification** - Resolve ambiguities
+3. **Planning** - Create implementation plan
+4. **Task Generation** - Break down into tasks
+5. **Implementation** - Execute the plan
+
+### Development Setup
+
+```bash
+# Clone and install
+git clone https://github.com/stuartf303/coorchat.git
+cd coorchat/packages/mcp-server
+npm install
+
+# Run tests
+npm test
+
+# Run in development mode
+npm run dev
+
+# Lint and format
+npm run lint
+npm run format
+```
+
+### Pull Request Process
+
+1. Create a feature branch from `main`
+2. Make your changes with tests
+3. Ensure all tests pass (`npm test`)
+4. Update documentation as needed
+5. Submit PR with clear description
+6. Wait for CI/CD checks
+7. Address review feedback
+
+**See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines**
+
+---
+
+## π License
+
+This project is licensed under the **MIT License** - see the [LICENSE](./LICENSE) file for details.
+
+```
+MIT License
+
+Copyright (c) 2026 CoorChat Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+[Full MIT License text...]
+```
+
+---
+
+## π Acknowledgments
+
+- **Claude (Anthropic)** - AI pair programming partner
+- **Model Context Protocol (MCP)** - Agent integration framework
+- **Specify** - Specification-driven development workflow
+- **Open Source Community** - For the amazing libraries used in this project
+
+---
+
+## π Support & Community
+
+- **Documentation**: [Read the docs](./INSTALL.md)
+- **Issues**: [Report bugs or request features](https://github.com/stuartf303/coorchat/issues)
+- **Discussions**: [Ask questions or share ideas](https://github.com/stuartf303/coorchat/discussions)
+
+---
+
+## β Star History
+
+If you find CoorChat useful, please consider giving it a star! β
+
+[](https://star-history.com/#stuartf303/coorchat&Date)
+
+---
+
+
+
+**Made with β€οΈ by developers, for developers**
+
+[β¬ Back to Top](#-coorchat)
+
+
diff --git a/SCENARIOS.md b/SCENARIOS.md
new file mode 100644
index 0000000..75549fc
--- /dev/null
+++ b/SCENARIOS.md
@@ -0,0 +1,622 @@
+# CoorChat Example Scenarios
+
+Real-world examples of multi-agent coordination using CoorChat.
+
+## Table of Contents
+
+1. [Scenario 1: Feature Development Workflow](#scenario-1-feature-development-workflow)
+2. [Scenario 2: Bug Fix Coordination](#scenario-2-bug-fix-coordination)
+3. [Scenario 3: Code Review Pipeline](#scenario-3-code-review-pipeline)
+4. [Scenario 4: Infrastructure Deployment](#scenario-4-infrastructure-deployment)
+5. [Scenario 5: Security Audit](#scenario-5-security-audit)
+6. [Running These Scenarios](#running-these-scenarios)
+
+---
+
+## Scenario 1: Feature Development Workflow
+
+**Goal**: Coordinate development, testing, and documentation for a new feature
+
+**Agents Involved**:
+- Developer Agent (implements feature)
+- Tester Agent (writes and runs tests)
+- Documentation Writer (updates docs)
+- Architect (reviews design)
+
+### Setup
+
+```bash
+# Terminal 1: Start Redis
+docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine
+
+# Generate shared token
+TOKEN=$(node -e "console.log('cct_' + require('crypto').randomBytes(32).toString('hex'))")
+echo "Shared Token: $TOKEN"
+```
+
+### Run Agents
+
+```bash
+# Terminal 2: Developer Agent
+cd packages/mcp-server
+CHANNEL_TYPE=redis \
+REDIS_HOST=localhost \
+REDIS_PORT=6379 \
+SHARED_TOKEN=$TOKEN \
+AGENT_ID=dev-agent-1 \
+AGENT_ROLE=developer \
+npm run cli -- agent start --role developer
+
+# Terminal 3: Tester Agent
+CHANNEL_TYPE=redis \
+REDIS_HOST=localhost \
+REDIS_PORT=6379 \
+SHARED_TOKEN=$TOKEN \
+AGENT_ID=test-agent-1 \
+AGENT_ROLE=tester \
+npm run cli -- agent start --role tester
+
+# Terminal 4: Documentation Agent
+CHANNEL_TYPE=redis \
+REDIS_HOST=localhost \
+REDIS_PORT=6379 \
+SHARED_TOKEN=$TOKEN \
+AGENT_ID=doc-agent-1 \
+AGENT_ROLE=documentation-writer \
+npm run cli -- agent start --role documentation-writer
+
+# Terminal 5: Monitor Activity
+npm run cli -- monitor
+```
+
+### Workflow
+
+1. **GitHub Issue Created**: `Add user authentication feature`
+2. **Architect Agent** reviews requirements, creates technical spec
+3. **Developer Agent** picks up task, implements authentication
+4. **Tester Agent** automatically notified when code is ready
+5. **Tester Agent** writes tests, runs them
+6. **Documentation Agent** updates API docs
+7. **All agents** report completion, task marked done
+
+### Expected Message Flow
+
+```
+[09:00:00] TASK_ASSIGNED
+ From: github-sync
+ To: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "description": "Add user authentication",
+ "githubIssue": "https://github.com/org/repo/issues/123"
+ }
+
+[09:15:00] TASK_STARTED
+ From: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "status": "in_progress"
+ }
+
+[10:30:00] TASK_PROGRESS
+ From: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "message": "Authentication endpoints implemented",
+ "completionPercentage": 60
+ }
+
+[11:00:00] TASK_COMPLETED
+ From: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "branch": "feature/user-auth",
+ "pullRequest": "https://github.com/org/repo/pull/456"
+ }
+
+[11:00:01] TASK_ASSIGNED
+ From: task-queue
+ To: test-agent-1
+ Payload: {
+ "taskId": "test-issue-123",
+ "dependsOn": "issue-123",
+ "testTarget": "feature/user-auth"
+ }
+```
+
+---
+
+## Scenario 2: Bug Fix Coordination
+
+**Goal**: Quickly triage, fix, test, and deploy a critical bug
+
+**Agents Involved**:
+- Triage Agent (analyzes bug reports)
+- Developer Agent (fixes bug)
+- Tester Agent (regression testing)
+- Infrastructure Agent (hotfix deployment)
+
+### Setup
+
+```bash
+# Use GitHub integration for automatic bug sync
+GITHUB_TOKEN=ghp_your_token
+GITHUB_OWNER=your-org
+GITHUB_REPO=your-repo
+GITHUB_WEBHOOK_SECRET=$(openssl rand -hex 32)
+```
+
+### Workflow
+
+1. **User reports bug** via GitHub issue with label `bug` and `priority:critical`
+2. **Triage Agent** automatically assigned, analyzes stack trace
+3. **Developer Agent** receives assignment with triage analysis
+4. **Developer Agent** creates hotfix branch, fixes bug
+5. **Tester Agent** runs regression test suite
+6. **Infrastructure Agent** deploys hotfix to production
+7. **All agents** notify completion, GitHub issue auto-closed
+
+### Test Script
+
+Create `scenarios/bug-fix-test.ts`:
+
+```typescript
+import { TaskQueue } from '../src/tasks/TaskQueue.js';
+import { AgentRegistry } from '../src/agents/AgentRegistry.js';
+import { Task } from '../src/tasks/Task.js';
+
+// Simulate critical bug workflow
+const queue = new TaskQueue();
+const registry = new AgentRegistry();
+
+// Register agents
+const triageAgent = registry.registerAgent({
+ id: 'triage-agent-1',
+ role: 'tester',
+ capabilities: ['bug-triage', 'log-analysis'],
+ status: 'active',
+ metadata: { specialization: 'triage' },
+});
+
+const devAgent = registry.registerAgent({
+ id: 'dev-agent-1',
+ role: 'developer',
+ capabilities: ['javascript', 'typescript', 'bugfix'],
+ status: 'active',
+ metadata: {},
+});
+
+// Create critical bug task
+const bugTask: Task = {
+ id: 'bug-critical-001',
+ description: 'Fix: Payment processing timeout',
+ requiredCapabilities: ['bug-triage'],
+ priority: 'critical',
+ status: 'pending',
+ createdAt: new Date(),
+ metadata: {
+ githubIssue: 'https://github.com/org/repo/issues/789',
+ errorMessage: 'Timeout after 30s',
+ affectedUsers: 1523,
+ },
+};
+
+// Add to queue
+await queue.addTask(bugTask);
+
+// Triage agent analyzes
+const assigned = await queue.assignTask(triageAgent.id);
+console.log('Bug assigned to triage:', assigned);
+
+// Create fix task after triage
+const fixTask: Task = {
+ id: 'bug-fix-001',
+ description: 'Implement fix for payment timeout',
+ requiredCapabilities: ['bugfix', 'javascript'],
+ priority: 'critical',
+ status: 'pending',
+ dependencies: ['bug-critical-001'],
+ createdAt: new Date(),
+ metadata: {
+ triageAnalysis: 'Database connection pool exhaustion',
+ suggestedFix: 'Increase pool size and add timeout handling',
+ },
+};
+
+await queue.addTask(fixTask);
+```
+
+---
+
+## Scenario 3: Code Review Pipeline
+
+**Goal**: Automated code review coordination
+
+**Agents Involved**:
+- Security Auditor (checks for vulnerabilities)
+- Code Reviewer (style and best practices)
+- Test Agent (coverage validation)
+- Architect (design review)
+
+### Workflow
+
+1. **Pull Request created** on GitHub
+2. **Security Auditor** scans for common vulnerabilities (SQL injection, XSS, etc.)
+3. **Code Reviewer** checks code style, naming conventions
+4. **Test Agent** validates 80%+ code coverage
+5. **Architect** reviews architectural changes
+6. **All agents** must approve before merge
+
+### Configuration
+
+Create `scenarios/code-review-config.json`:
+
+```json
+{
+ "reviewPipeline": {
+ "requiredReviewers": [
+ {
+ "agentRole": "security-auditor",
+ "checks": ["owasp-top-10", "dependency-scan", "secrets-detection"]
+ },
+ {
+ "agentRole": "developer",
+ "checks": ["code-style", "naming-conventions", "complexity"]
+ },
+ {
+ "agentRole": "tester",
+ "checks": ["coverage-threshold", "test-quality"]
+ },
+ {
+ "agentRole": "architect",
+ "checks": ["design-patterns", "architecture-compliance"],
+ "requiredForFiles": ["src/core/**", "src/api/**"]
+ }
+ ],
+ "approvalThreshold": "all",
+ "autoMerge": false
+ }
+}
+```
+
+### Implementation
+
+```typescript
+// File: scenarios/code-review.ts
+import { WebhookHandler } from '../src/github/WebhookHandler.js';
+import { TaskQueue } from '../src/tasks/TaskQueue.js';
+
+const webhookHandler = new WebhookHandler({
+ port: 3000,
+ path: '/webhook',
+ secret: process.env.GITHUB_WEBHOOK_SECRET!,
+});
+
+webhookHandler.on('pull_request.opened', async (payload) => {
+ const pr = payload.pull_request;
+
+ // Create review tasks for each reviewer type
+ const reviewTasks = [
+ {
+ id: `security-review-${pr.number}`,
+ description: `Security review for PR #${pr.number}`,
+ requiredCapabilities: ['security-audit', 'vulnerability-scan'],
+ priority: 'high' as const,
+ status: 'pending' as const,
+ createdAt: new Date(),
+ metadata: {
+ prNumber: pr.number,
+ prUrl: pr.html_url,
+ reviewType: 'security',
+ },
+ },
+ {
+ id: `code-review-${pr.number}`,
+ description: `Code style review for PR #${pr.number}`,
+ requiredCapabilities: ['code-review', 'style-check'],
+ priority: 'medium' as const,
+ status: 'pending' as const,
+ createdAt: new Date(),
+ metadata: {
+ prNumber: pr.number,
+ prUrl: pr.html_url,
+ reviewType: 'code-quality',
+ },
+ },
+ {
+ id: `test-review-${pr.number}`,
+ description: `Test coverage review for PR #${pr.number}`,
+ requiredCapabilities: ['testing', 'coverage-analysis'],
+ priority: 'medium' as const,
+ status: 'pending' as const,
+ createdAt: new Date(),
+ metadata: {
+ prNumber: pr.number,
+ prUrl: pr.html_url,
+ reviewType: 'test-coverage',
+ coverageThreshold: 80,
+ },
+ },
+ ];
+
+ // Add all review tasks
+ const queue = new TaskQueue();
+ for (const task of reviewTasks) {
+ await queue.addTask(task);
+ }
+
+ console.log(`Created ${reviewTasks.length} review tasks for PR #${pr.number}`);
+});
+
+await webhookHandler.start();
+```
+
+---
+
+## Scenario 4: Infrastructure Deployment
+
+**Goal**: Coordinate multi-stage deployment with validation
+
+**Agents Involved**:
+- Backend Developer (API deployment)
+- Frontend Developer (UI deployment)
+- Infrastructure Agent (Kubernetes/Docker)
+- Tester Agent (smoke tests)
+
+### Workflow
+
+```
+1. Backend Agent deploys API to staging
+ β
+2. Infrastructure Agent validates health checks
+ β
+3. Frontend Agent deploys UI to staging
+ β
+4. Tester Agent runs smoke tests
+ β (if tests pass)
+5. Infrastructure Agent promotes to production
+ β
+6. Tester Agent runs production smoke tests
+ β
+7. All agents report success
+```
+
+### Dependency Chain
+
+```typescript
+import { DependencyTracker } from '../src/tasks/DependencyTracker.js';
+
+const tracker = new DependencyTracker();
+
+// Define deployment tasks with dependencies
+const tasks = [
+ { id: 'deploy-api-staging', dependencies: [] },
+ { id: 'validate-api-health', dependencies: ['deploy-api-staging'] },
+ { id: 'deploy-ui-staging', dependencies: ['validate-api-health'] },
+ { id: 'run-smoke-tests', dependencies: ['deploy-ui-staging'] },
+ { id: 'deploy-api-prod', dependencies: ['run-smoke-tests'] },
+ { id: 'deploy-ui-prod', dependencies: ['deploy-api-prod'] },
+ { id: 'run-prod-smoke-tests', dependencies: ['deploy-ui-prod'] },
+];
+
+// Add all dependencies
+for (const task of tasks) {
+ for (const dep of task.dependencies) {
+ tracker.addDependency(task.id, dep);
+ }
+}
+
+// Check which tasks are ready
+const ready = tracker.getReadyTasks();
+console.log('Tasks ready to execute:', ready);
+
+// Mark task complete and get newly unblocked tasks
+const unblocked = tracker.markCompleted('deploy-api-staging');
+console.log('Newly unblocked tasks:', unblocked);
+```
+
+---
+
+## Scenario 5: Security Audit
+
+**Goal**: Comprehensive security review of codebase
+
+**Agents Involved**:
+- Security Auditor (vulnerability scanning)
+- Developer (fix implementation)
+- Tester (security test validation)
+- Documentation Writer (security docs)
+
+### Workflow
+
+1. **Security Auditor** scans entire codebase
+2. **Creates tasks** for each finding (by severity)
+3. **Developer Agents** assigned based on file ownership
+4. **Each fix** reviewed by Security Auditor
+5. **Tester Agent** validates fixes don't introduce regressions
+6. **Documentation Agent** updates security guidelines
+
+### Security Scan Example
+
+```typescript
+// File: scenarios/security-audit.ts
+import { TaskQueue } from '../src/tasks/TaskQueue.js';
+import { Task } from '../src/tasks/Task.js';
+
+interface SecurityFinding {
+ severity: 'critical' | 'high' | 'medium' | 'low';
+ category: string;
+ file: string;
+ line: number;
+ description: string;
+ recommendation: string;
+}
+
+const findings: SecurityFinding[] = [
+ {
+ severity: 'critical',
+ category: 'SQL Injection',
+ file: 'src/database/queries.ts',
+ line: 45,
+ description: 'Unsanitized user input in SQL query',
+ recommendation: 'Use parameterized queries',
+ },
+ {
+ severity: 'high',
+ category: 'XSS',
+ file: 'src/ui/UserProfile.tsx',
+ line: 123,
+ description: 'Unescaped user content rendered',
+ recommendation: 'Use DOMPurify or framework escaping',
+ },
+ // ... more findings
+];
+
+// Create tasks for each finding
+const queue = new TaskQueue();
+
+for (const finding of findings) {
+ const task: Task = {
+ id: `security-fix-${finding.file}-${finding.line}`,
+ description: `[${finding.severity.toUpperCase()}] Fix ${finding.category} in ${finding.file}`,
+ requiredCapabilities: ['security', finding.category.toLowerCase()],
+ priority: finding.severity === 'critical' ? 'critical' : 'high',
+ status: 'pending',
+ createdAt: new Date(),
+ metadata: {
+ securityFinding: finding,
+ file: finding.file,
+ line: finding.line,
+ recommendation: finding.recommendation,
+ },
+ };
+
+ await queue.addTask(task);
+}
+
+console.log(`Created ${findings.length} security fix tasks`);
+```
+
+---
+
+## Running These Scenarios
+
+### Option 1: Automated Test Suite
+
+```bash
+# Run all scenario tests
+cd packages/mcp-server
+npm run scenarios
+
+# Run specific scenario
+npm run scenario -- code-review
+```
+
+### Option 2: Interactive Demo
+
+```bash
+# Start demo environment
+./scripts/demo-setup.sh
+
+# This will:
+# 1. Start Redis
+# 2. Start 4 agents (developer, tester, architect, security)
+# 3. Load example GitHub issues
+# 4. Show real-time coordination
+```
+
+### Option 3: Manual Execution
+
+```bash
+# Terminal 1: Start infrastructure
+docker-compose up -d
+
+# Terminal 2-5: Start agents
+npm run cli -- agent start --role developer
+npm run cli -- agent start --role tester
+npm run cli -- agent start --role security-auditor
+npm run cli -- agent start --role architect
+
+# Terminal 6: Monitor
+npm run cli -- monitor
+
+# Terminal 7: Trigger scenario
+node scenarios/feature-development.js
+```
+
+---
+
+## Scenario Metrics
+
+Track coordination effectiveness:
+
+```typescript
+interface ScenarioMetrics {
+ totalTasks: number;
+ completedTasks: number;
+ averageTaskTime: number; // milliseconds
+ agentUtilization: Record; // percentage
+ taskSuccessRate: number; // percentage
+ coordinationOverhead: number; // milliseconds (avg message latency)
+}
+
+// Example output:
+{
+ totalTasks: 15,
+ completedTasks: 15,
+ averageTaskTime: 45000, // 45 seconds
+ agentUtilization: {
+ 'dev-agent-1': 85,
+ 'test-agent-1': 60,
+ 'security-agent-1': 40,
+ },
+ taskSuccessRate: 100,
+ coordinationOverhead: 120, // 120ms average
+}
+```
+
+---
+
+## Custom Scenarios
+
+Create your own scenario:
+
+```typescript
+// File: scenarios/my-scenario.ts
+import { ScenarioRunner } from './utils/ScenarioRunner.js';
+
+const scenario = new ScenarioRunner({
+ name: 'My Custom Workflow',
+ agents: [
+ { role: 'developer', count: 2 },
+ { role: 'tester', count: 1 },
+ ],
+ tasks: [
+ {
+ description: 'Implement feature X',
+ assignTo: 'developer',
+ dependencies: [],
+ },
+ {
+ description: 'Test feature X',
+ assignTo: 'tester',
+ dependencies: ['Implement feature X'],
+ },
+ ],
+});
+
+await scenario.run();
+scenario.printMetrics();
+```
+
+---
+
+## Next Steps
+
+1. **Try the scenarios** - Start with Scenario 1 (Feature Development)
+2. **Monitor activity** - Use `npm run cli -- monitor` to watch coordination
+3. **Customize workflows** - Modify scenarios for your use case
+4. **Measure performance** - Track metrics to optimize coordination
+5. **Scale up** - Add more agents and parallel workflows
+
+Happy coordinating! π€
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..1b51003
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,43 @@
+version: '3.8'
+
+services:
+ mcp-server:
+ build:
+ context: ./packages/mcp-server
+ dockerfile: Dockerfile
+ environment:
+ - NODE_ENV=development
+ - DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}
+ - CHANNEL_TOKEN=${CHANNEL_TOKEN}
+ - GITHUB_TOKEN=${GITHUB_TOKEN}
+ - GITHUB_WEBHOOK_SECRET=${GITHUB_WEBHOOK_SECRET}
+ volumes:
+ - ./.coorchat:/app/.coorchat
+ - ./packages/mcp-server/src:/app/src
+ ports:
+ - "3000:3000"
+ restart: unless-stopped
+
+ # Optional: Relay Server (C#/.NET component)
+ # relay-server:
+ # build:
+ # context: ./packages/relay-server
+ # dockerfile: Dockerfile
+ # environment:
+ # - ASPNETCORE_ENVIRONMENT=Development
+ # - ConnectionStrings__DefaultConnection=${DATABASE_URL}
+ # ports:
+ # - "5000:80"
+ # restart: unless-stopped
+
+ # Optional: Redis (if using Redis channel)
+ redis:
+ image: redis:7-alpine
+ command: redis-server --requirepass ${REDIS_PASSWORD:-changeme}
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis-data:/data
+
+volumes:
+ redis-data:
diff --git a/packages/mcp-server/CLI.md b/packages/mcp-server/CLI.md
new file mode 100644
index 0000000..a6266ee
--- /dev/null
+++ b/packages/mcp-server/CLI.md
@@ -0,0 +1,516 @@
+# CoorChat CLI Documentation
+
+Command-line interface for managing CoorChat agents, tokens, and monitoring coordination.
+
+## Installation
+
+```bash
+# From source
+cd packages/mcp-server
+npm install
+npm run build
+
+# Use locally
+npm run cli --
+
+# Or install globally
+npm install -g .
+coorchat
+```
+
+## Commands
+
+### Token Management
+
+#### `token generate`
+
+Generate secure authentication tokens.
+
+```bash
+# Generate channel token (default)
+npm run cli -- token generate
+
+# Generate API token
+npm run cli -- token generate --type api
+
+# Generate webhook secret
+npm run cli -- token generate --type webhook
+
+# Generate multiple tokens
+npm run cli -- token generate --count 5
+```
+
+**Options**:
+- `-t, --type `: Token type (`channel`, `api`, `webhook`) - default: `channel`
+- `-c, --count `: Number of tokens to generate - default: `1`
+
+**Output**:
+```
+Generated tokens:
+1. cct_a3f8d9e2c1b4f7a6e8d2c9b1f4a7e3d2c6b9f1e4a8d3c7b2f5e9a1d4c8b3f6
+
+Add to your .env file:
+SHARED_TOKEN=cct_a3f8d9e2c1b4f7a6e8d2c9b1f4a7e3d2c6b9f1e4a8d3c7b2f5e9a1d4c8b3f6
+```
+
+#### `token validate `
+
+Validate token format and security requirements.
+
+```bash
+npm run cli -- token validate cct_abc123def456
+```
+
+**Output**:
+```
+β
Token is valid
+Length: 71 characters
+Type: Channel Token
+```
+
+#### `token hash `
+
+Generate SHA-256 hash of a token for secure storage.
+
+```bash
+npm run cli -- token hash cct_your_token
+```
+
+**Output**:
+```
+Token hash:
+9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
+```
+
+---
+
+### Agent Management
+
+#### `agent start`
+
+Start an agent and connect to the coordination channel.
+
+```bash
+# Start with defaults
+npm run cli -- agent start
+
+# Specify role
+npm run cli -- agent start --role tester
+
+# Specify agent ID
+npm run cli -- agent start --id my-agent-1 --role developer
+
+# Specify channel type
+npm run cli -- agent start --channel discord --role architect
+```
+
+**Options**:
+- `-i, --id `: Agent ID (default: auto-generated)
+- `-r, --role `: Agent role (default: `developer`)
+- `-c, --channel `: Channel type (default: from env or `redis`)
+
+**Example**:
+```bash
+SHARED_TOKEN=cct_your_token \
+REDIS_HOST=localhost \
+REDIS_PORT=6379 \
+npm run cli -- agent start --role developer
+```
+
+**Output**:
+```
+π€ Starting agent: agent-1708012345678
+ Role: developer
+ Channel: redis
+
+β
Connected to channel
+
+Agent is running. Press Ctrl+C to stop.
+
+π¨ [TASK_ASSIGNED] from task-queue
+ {
+ "taskId": "issue-123",
+ "description": "Implement authentication"
+ }
+```
+
+#### `agent list`
+
+List all active agents (requires shared state store).
+
+```bash
+npm run cli -- agent list
+```
+
+---
+
+### Role Management
+
+#### `role list`
+
+List all available predefined roles and their capabilities.
+
+```bash
+npm run cli -- role list
+```
+
+**Output**:
+```
+π Available Roles:
+
+developer:
+ Description: Software developer for implementing features
+ Capabilities: coding, debugging, code-review, git-operations
+
+tester:
+ Description: Quality assurance and testing specialist
+ Capabilities: testing, test-automation, quality-assurance, bug-reporting
+
+architect:
+ Description: System architect for design and architecture decisions
+ Capabilities: architecture-design, system-design, technical-planning
+
+... (8 total roles)
+```
+
+#### `role suggest `
+
+Suggest roles based on required capabilities.
+
+```bash
+npm run cli -- role suggest testing code-review
+
+npm run cli -- role suggest security penetration-testing
+```
+
+**Output**:
+```
+π‘ Suggested Roles:
+
+1. tester
+ Quality assurance and testing specialist
+
+2. developer
+ Software developer for implementing features
+```
+
+---
+
+### Configuration
+
+#### `config show`
+
+Display current configuration from environment variables.
+
+```bash
+npm run cli -- config show
+```
+
+**Output**:
+```
+βοΈ Current Configuration:
+
+Channel Type: redis
+Agent ID: agent-claude-1
+Agent Role: developer
+Shared Token: ***b3f6a1d4
+
+Redis Configuration:
+ Host: localhost
+ Port: 6379
+ TLS: false
+
+GitHub Integration:
+ Token: ***ab12cd34
+ Owner: your-org
+ Repo: your-repo
+```
+
+#### `config init`
+
+Generate a configuration template.
+
+```bash
+# Generate for Redis
+npm run cli -- config init --channel redis
+
+# Generate for Discord
+npm run cli -- config init --channel discord
+
+# Generate for SignalR
+npm run cli -- config init --channel signalr
+```
+
+**Output**:
+```
+# CoorChat Configuration
+# Generated: 2026-02-15T00:00:00.000Z
+
+# Shared authentication token (use same token for all agents)
+SHARED_TOKEN=cct_a3f8d9e2c1b4f7a6e8d2c9b1f4a7e3d2
+
+# Channel configuration
+CHANNEL_TYPE=redis
+REDIS_HOST=localhost
+REDIS_PORT=6379
+REDIS_TLS=false
+
+# Agent configuration
+AGENT_ID=agent-1708012345678
+AGENT_ROLE=developer
+
+# Optional: GitHub integration
+# GITHUB_TOKEN=ghp_your_token_here
+# GITHUB_OWNER=your-org
+# GITHUB_REPO=your-repo
+
+# Logging
+LOG_LEVEL=info
+
+πΎ Save this to .env file in packages/mcp-server/
+```
+
+---
+
+### Monitoring
+
+#### `monitor`
+
+Monitor real-time agent coordination activity.
+
+```bash
+npm run cli -- monitor
+
+# Specify channel
+CHANNEL_TYPE=discord \
+DISCORD_BOT_TOKEN=your_token \
+npm run cli -- monitor --channel discord
+```
+
+**Options**:
+- `-c, --channel `: Channel type (default: from env or `redis`)
+
+**Output**:
+```
+ποΈ CoorChat Monitor
+
+Listening for agent activity...
+
+β
Connected to redis channel
+
+[10:30:45] TASK_ASSIGNED
+ From: github-sync
+ To: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "description": "Add user authentication"
+ }
+
+[10:30:50] TASK_STARTED
+ From: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "status": "in_progress"
+ }
+
+[10:45:20] TASK_COMPLETED
+ From: dev-agent-1
+ Payload: {
+ "taskId": "issue-123",
+ "branch": "feature/user-auth"
+ }
+```
+
+Press `Ctrl+C` to stop monitoring.
+
+---
+
+## Environment Variables
+
+All CLI commands respect these environment variables:
+
+### Required
+
+| Variable | Description | Example |
+|----------|-------------|---------|
+| `SHARED_TOKEN` | Authentication token (16+ chars) | `cct_a3f8d9e2...` |
+| `CHANNEL_TYPE` | Channel type | `redis`, `discord`, `signalr` |
+
+### Channel-Specific
+
+**Redis**:
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `REDIS_HOST` | Redis hostname | `localhost` |
+| `REDIS_PORT` | Redis port | `6379` |
+| `REDIS_PASSWORD` | Redis password | _(none)_ |
+| `REDIS_TLS` | Enable TLS | `false` |
+
+**Discord**:
+| Variable | Description |
+|----------|-------------|
+| `DISCORD_BOT_TOKEN` | Discord bot token |
+| `DISCORD_CHANNEL_ID` | Discord channel ID |
+
+**SignalR**:
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `SIGNALR_HUB_URL` | Hub URL | `https://localhost:5001/agentHub` |
+
+### Optional
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `AGENT_ID` | Agent identifier | Auto-generated |
+| `AGENT_ROLE` | Agent role | `developer` |
+| `LOG_LEVEL` | Log verbosity | `info` |
+| `GITHUB_TOKEN` | GitHub PAT | _(none)_ |
+| `GITHUB_OWNER` | GitHub org/user | _(none)_ |
+| `GITHUB_REPO` | GitHub repo | _(none)_ |
+
+---
+
+## Common Workflows
+
+### 1. Quick Start (Local Testing)
+
+```bash
+# Generate token
+npm run cli -- token generate
+
+# Save output to .env
+echo "SHARED_TOKEN=cct_..." > .env
+echo "CHANNEL_TYPE=redis" >> .env
+
+# Start Redis
+docker run -d -p 6379:6379 redis:7-alpine
+
+# Start agent
+npm run cli -- agent start --role developer
+```
+
+### 2. Multi-Agent Coordination
+
+```bash
+# Terminal 1: Developer
+AGENT_ID=dev-1 npm run cli -- agent start --role developer
+
+# Terminal 2: Tester
+AGENT_ID=test-1 npm run cli -- agent start --role tester
+
+# Terminal 3: Monitor
+npm run cli -- monitor
+```
+
+### 3. GitHub Integration
+
+```bash
+# Generate tokens
+GITHUB_TOKEN=ghp_your_token
+WEBHOOK_SECRET=$(npm run cli -- token generate --type webhook | tail -1)
+
+# Configure .env
+cat >> .env << EOF
+GITHUB_TOKEN=$GITHUB_TOKEN
+GITHUB_OWNER=your-org
+GITHUB_REPO=your-repo
+GITHUB_WEBHOOK_SECRET=$WEBHOOK_SECRET
+EOF
+
+# Start agent with GitHub sync
+npm run cli -- agent start --role developer
+```
+
+### 4. Production Deployment
+
+```bash
+# Use environment-specific config
+export NODE_ENV=production
+export CHANNEL_TYPE=signalr
+export SIGNALR_HUB_URL=https://coorchat.example.com/hub
+export SHARED_TOKEN=cct_production_token
+
+# Start agent
+npm run cli -- agent start \
+ --id prod-agent-1 \
+ --role developer
+```
+
+---
+
+## Troubleshooting
+
+### "Invalid or missing SHARED_TOKEN"
+
+```bash
+# Generate new token
+npm run cli -- token generate
+
+# Add to environment
+export SHARED_TOKEN=cct_your_generated_token
+```
+
+### "Connection refused" (Redis)
+
+```bash
+# Check Redis is running
+docker ps | grep redis
+
+# Start Redis if not running
+docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine
+```
+
+### "Cannot find module"
+
+```bash
+# Rebuild project
+npm run build
+```
+
+### Agent not receiving messages
+
+```bash
+# Check token matches across all agents
+npm run cli -- config show
+
+# Verify channel connection
+npm run cli -- monitor
+```
+
+---
+
+## Programmatic Usage
+
+Use the CLI programmatically in your Node.js scripts:
+
+```typescript
+import { TokenGenerator } from '@coorchat/mcp-server/config/TokenGenerator';
+import { ChannelFactory } from '@coorchat/mcp-server/channels/base/ChannelFactory';
+
+// Generate token
+const token = TokenGenerator.generateChannelToken();
+
+// Create channel
+const channel = ChannelFactory.create({
+ type: 'redis',
+ token,
+ connectionParams: {
+ host: 'localhost',
+ port: 6379,
+ },
+});
+
+// Connect and listen
+await channel.connect();
+channel.onMessage((message) => {
+ console.log('Received:', message);
+});
+```
+
+---
+
+## See Also
+
+- [Installation Guide](../../INSTALL.md) - Full installation instructions
+- [Scenarios](../../SCENARIOS.md) - Example coordination workflows
+- [README](../../README.md) - Project overview
diff --git a/packages/mcp-server/Dockerfile b/packages/mcp-server/Dockerfile
new file mode 100644
index 0000000..4a8e663
--- /dev/null
+++ b/packages/mcp-server/Dockerfile
@@ -0,0 +1,42 @@
+# Multi-stage build for CoorChat MCP Server
+FROM node:18-alpine AS builder
+
+WORKDIR /app
+
+# Copy package files
+COPY package*.json ./
+
+# Install dependencies
+RUN npm ci --only=production
+
+# Copy source code
+COPY . .
+
+# Build TypeScript
+RUN npm run build
+
+# Production image
+FROM node:18-alpine
+
+WORKDIR /app
+
+# Copy built application
+COPY --from=builder /app/dist ./dist
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/package.json ./
+
+# Create non-root user
+RUN addgroup -g 1001 -S coorchat && \
+ adduser -S coorchat -u 1001 && \
+ chown -R coorchat:coorchat /app
+
+USER coorchat
+
+# Expose port (if needed for webhooks)
+EXPOSE 3000
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD node -e "process.exit(0)"
+
+CMD ["node", "dist/index.js"]
diff --git a/packages/mcp-server/package-lock.json b/packages/mcp-server/package-lock.json
new file mode 100644
index 0000000..34e5827
--- /dev/null
+++ b/packages/mcp-server/package-lock.json
@@ -0,0 +1,6579 @@
+{
+ "name": "@coorchat/mcp-server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@coorchat/mcp-server",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@microsoft/signalr": "^8.0.0",
+ "@octokit/rest": "^20.0.2",
+ "ajv": "^8.12.0",
+ "ajv-formats": "^2.1.1",
+ "commander": "^12.1.0",
+ "crypto": "^1.0.1",
+ "discord.js": "^14.14.1",
+ "dotenv": "^16.6.1",
+ "express": "^4.18.2",
+ "ioredis": "^5.3.2",
+ "uuid": "^9.0.1",
+ "winston": "^3.11.0",
+ "ws": "^8.16.0",
+ "yaml": "^2.3.4",
+ "zod": "^3.22.4"
+ },
+ "bin": {
+ "coorchat": "dist/cli/index.js"
+ },
+ "devDependencies": {
+ "@types/express": "^4.17.21",
+ "@types/node": "^20.11.5",
+ "@types/uuid": "^9.0.7",
+ "@types/ws": "^8.5.10",
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
+ "@typescript-eslint/parser": "^6.19.0",
+ "@vitest/coverage-v8": "^1.2.1",
+ "eslint": "^8.56.0",
+ "prettier": "^3.2.4",
+ "tsc-alias": "^1.8.8",
+ "tsx": "^4.7.0",
+ "typescript": "^5.3.3",
+ "vitest": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.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/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/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/@colors/colors": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
+ "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/@dabh/diagnostics": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
+ "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@so-ric/colorspace": "^1.1.6",
+ "enabled": "2.0.x",
+ "kuler": "^2.0.0"
+ }
+ },
+ "node_modules/@discordjs/builders": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
+ "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@discordjs/formatters": "^0.6.2",
+ "@discordjs/util": "^1.2.0",
+ "@sapphire/shapeshift": "^4.0.0",
+ "discord-api-types": "^0.38.33",
+ "fast-deep-equal": "^3.1.3",
+ "ts-mixer": "^6.0.4",
+ "tslib": "^2.6.3"
+ },
+ "engines": {
+ "node": ">=16.11.0"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@discordjs/collection": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
+ "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.11.0"
+ }
+ },
+ "node_modules/@discordjs/formatters": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
+ "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "discord-api-types": "^0.38.33"
+ },
+ "engines": {
+ "node": ">=16.11.0"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@discordjs/rest": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz",
+ "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@discordjs/collection": "^2.1.1",
+ "@discordjs/util": "^1.1.1",
+ "@sapphire/async-queue": "^1.5.3",
+ "@sapphire/snowflake": "^3.5.3",
+ "@vladfrangu/async_event_emitter": "^2.4.6",
+ "discord-api-types": "^0.38.16",
+ "magic-bytes.js": "^1.10.0",
+ "tslib": "^2.6.3",
+ "undici": "6.21.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
+ "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@discordjs/util": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
+ "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "discord-api-types": "^0.38.33"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@discordjs/ws": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
+ "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@discordjs/collection": "^2.1.0",
+ "@discordjs/rest": "^2.5.1",
+ "@discordjs/util": "^1.1.0",
+ "@sapphire/async-queue": "^1.5.2",
+ "@types/ws": "^8.5.10",
+ "@vladfrangu/async_event_emitter": "^2.2.4",
+ "discord-api-types": "^0.38.1",
+ "tslib": "^2.6.2",
+ "ws": "^8.17.0"
+ },
+ "engines": {
+ "node": ">=16.11.0"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
+ "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@eslint/eslintrc/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/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/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/@humanwhocodes/config-array/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/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@ioredis/commands": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz",
+ "integrity": "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==",
+ "license": "MIT"
+ },
+ "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/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/@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/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/@microsoft/signalr": {
+ "version": "8.0.17",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.17.tgz",
+ "integrity": "sha512-5pM6xPtKZNJLO0Tq5nQasVyPFwi/WBY3QB5uc/v3dIPTpS1JXQbaXAQAPxFoQ5rTBFE094w8bbqkp17F9ReQvA==",
+ "license": "MIT",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "eventsource": "^2.0.2",
+ "fetch-cookie": "^2.0.3",
+ "node-fetch": "^2.6.7",
+ "ws": "^7.5.10"
+ }
+ },
+ "node_modules/@microsoft/signalr/node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@octokit/auth-token": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
+ "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/core": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
+ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@octokit/auth-token": "^4.0.0",
+ "@octokit/graphql": "^7.1.0",
+ "@octokit/request": "^8.4.1",
+ "@octokit/request-error": "^5.1.1",
+ "@octokit/types": "^13.0.0",
+ "before-after-hook": "^2.2.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/endpoint": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
+ "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/types": "^13.1.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/graphql": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
+ "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/request": "^8.4.1",
+ "@octokit/types": "^13.0.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/openapi-types": {
+ "version": "24.2.0",
+ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
+ "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
+ "license": "MIT"
+ },
+ "node_modules/@octokit/plugin-paginate-rest": {
+ "version": "11.4.4-cjs.2",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz",
+ "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/types": "^13.7.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@octokit/core": "5"
+ }
+ },
+ "node_modules/@octokit/plugin-request-log": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz",
+ "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@octokit/core": "5"
+ }
+ },
+ "node_modules/@octokit/plugin-rest-endpoint-methods": {
+ "version": "13.3.2-cjs.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz",
+ "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/types": "^13.8.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@octokit/core": "^5"
+ }
+ },
+ "node_modules/@octokit/request": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
+ "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/endpoint": "^9.0.6",
+ "@octokit/request-error": "^5.1.1",
+ "@octokit/types": "^13.1.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/request-error": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
+ "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/types": "^13.1.0",
+ "deprecation": "^2.0.0",
+ "once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/rest": {
+ "version": "20.1.2",
+ "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz",
+ "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/core": "^5.0.2",
+ "@octokit/plugin-paginate-rest": "11.4.4-cjs.2",
+ "@octokit/plugin-request-log": "^4.0.0",
+ "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/types": {
+ "version": "13.10.0",
+ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
+ "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/openapi-types": "^24.2.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sapphire/async-queue": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
+ "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=v14.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/@sapphire/shapeshift": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
+ "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "lodash": "^4.17.21"
+ },
+ "engines": {
+ "node": ">=v16"
+ }
+ },
+ "node_modules/@sapphire/snowflake": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
+ "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=v14.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "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/@so-ric/colorspace": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
+ "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
+ "license": "MIT",
+ "dependencies": {
+ "color": "^5.0.2",
+ "text-hex": "1.0.x"
+ }
+ },
+ "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/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.8",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
+ "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "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/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.33",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
+ "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
+ "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/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==",
+ "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": "1.15.10",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/triple-beam": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
+ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/uuid": {
+ "version": "9.0.8",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
+ "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
+ "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.5.1",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/type-utils": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
+ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+ "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
+ "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+ "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+ "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "9.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+ "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+ "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitest/coverage-v8": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz",
+ "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.1",
+ "@bcoe/v8-coverage": "^0.2.3",
+ "debug": "^4.3.4",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-lib-source-maps": "^5.0.4",
+ "istanbul-reports": "^3.1.6",
+ "magic-string": "^0.30.5",
+ "magicast": "^0.3.3",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "test-exclude": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "1.6.1"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz",
+ "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz",
+ "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "1.6.1",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/p-limit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+ "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/yocto-queue": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
+ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz",
+ "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz",
+ "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz",
+ "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vladfrangu/async_event_emitter": {
+ "version": "2.4.7",
+ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
+ "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=v14.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "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/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "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": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "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": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/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/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "license": "MIT"
+ },
+ "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/before-after-hook": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
+ "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/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/body-parser/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/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/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/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/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=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/chai": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
+ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/cluster-key-slot": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
+ "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/color": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
+ "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^3.1.3",
+ "color-string": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/color-string": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
+ "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/color-string/node_modules/color-name": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/color/node_modules/color-convert": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
+ "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.6"
+ }
+ },
+ "node_modules/color/node_modules/color-name": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "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/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/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+ "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
+ "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
+ "license": "ISC"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
+ "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "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/deprecation": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
+ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
+ "license": "ISC"
+ },
+ "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/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/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/discord-api-types": {
+ "version": "0.38.39",
+ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.39.tgz",
+ "integrity": "sha512-XRdDQvZvID1XvcFftjSmd4dcmMi/RL/jSy5sduBDAvCGFcNFHThdIQXCEBDZFe52lCNEzuIL0QJoKYAmRmxLUA==",
+ "license": "MIT",
+ "workspaces": [
+ "scripts/actions/documentation"
+ ]
+ },
+ "node_modules/discord.js": {
+ "version": "14.25.1",
+ "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
+ "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@discordjs/builders": "^1.13.0",
+ "@discordjs/collection": "1.5.3",
+ "@discordjs/formatters": "^0.6.2",
+ "@discordjs/rest": "^2.6.0",
+ "@discordjs/util": "^1.2.0",
+ "@discordjs/ws": "^1.2.3",
+ "@sapphire/snowflake": "3.5.3",
+ "discord-api-types": "^0.38.33",
+ "fast-deep-equal": "3.1.3",
+ "lodash.snakecase": "4.1.1",
+ "magic-bytes.js": "^1.10.0",
+ "tslib": "^2.6.3",
+ "undici": "6.21.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/discordjs/discord.js?sponsor"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "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/enabled": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
+ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
+ "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/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/eslint/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/eslint/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eslint/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/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
+ "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "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/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/express/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/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-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fecha": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
+ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
+ "license": "MIT"
+ },
+ "node_modules/fetch-cookie": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
+ "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
+ "license": "Unlicense",
+ "dependencies": {
+ "set-cookie-parser": "^2.4.8",
+ "tough-cookie": "^4.0.0"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "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/finalhandler/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/finalhandler/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/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fn.name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
+ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
+ "license": "MIT"
+ },
+ "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/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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-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": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "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/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/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/glob/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/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/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": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.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/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/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/ioredis": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.3.tgz",
+ "integrity": "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ioredis/commands": "1.5.0",
+ "cluster-key-slot": "^1.1.0",
+ "debug": "^4.3.4",
+ "denque": "^2.1.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.isarguments": "^3.1.0",
+ "redis-errors": "^1.2.0",
+ "redis-parser": "^3.0.0",
+ "standard-as-callback": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=12.22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ioredis"
+ }
+ },
+ "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-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-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-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "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==",
+ "dev": true,
+ "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-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": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "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-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kuler": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
+ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
+ "license": "MIT"
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.snakecase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
+ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
+ "license": "MIT"
+ },
+ "node_modules/logform": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
+ "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@colors/colors": "1.6.0",
+ "@types/triple-beam": "^1.3.2",
+ "fecha": "^4.2.0",
+ "ms": "^2.1.1",
+ "safe-stable-stringify": "^2.3.1",
+ "triple-beam": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
+ "node_modules/magic-bytes.js": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
+ "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "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/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/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+ "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
+ "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.1"
+ }
+ },
+ "node_modules/mlly/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/mylas": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz",
+ "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/raouldeheer"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "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/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "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": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/one-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
+ "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
+ "license": "MIT",
+ "dependencies": {
+ "fn.name": "1.x.x"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/pkg-types/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/plimit-lit": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
+ "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "queue-lit": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "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/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/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/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "license": "MIT"
+ },
+ "node_modules/queue-lit": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz",
+ "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "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/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": "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/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/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
+ "license": "MIT",
+ "dependencies": {
+ "redis-errors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/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/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "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",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "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/safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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": "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/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/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/send/node_modules/debug/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/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/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stack-trace": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/standard-as-callback": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
+ "license": "MIT"
+ },
+ "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/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "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-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/strip-literal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "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/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/test-exclude/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/test-exclude/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/text-hex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
+ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
+ "license": "MIT"
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinypool": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
+ "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "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/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/triple-beam": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
+ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/ts-mixer": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
+ "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
+ "license": "MIT"
+ },
+ "node_modules/tsc-alias": {
+ "version": "1.8.16",
+ "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz",
+ "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.3",
+ "commander": "^9.0.0",
+ "get-tsconfig": "^4.10.0",
+ "globby": "^11.0.4",
+ "mylas": "^2.1.9",
+ "normalize-path": "^3.0.0",
+ "plimit-lit": "^1.2.6"
+ },
+ "bin": {
+ "tsc-alias": "dist/bin/index.js"
+ },
+ "engines": {
+ "node": ">=16.20.2"
+ }
+ },
+ "node_modules/tsc-alias/node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/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-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
+ "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "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",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici": {
+ "version": "6.21.3",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
+ "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.17"
+ }
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/universal-user-agent": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
+ "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
+ "license": "ISC"
+ },
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "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/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "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/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz",
+ "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz",
+ "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vitest/expect": "1.6.1",
+ "@vitest/runner": "1.6.1",
+ "@vitest/snapshot": "1.6.1",
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.3",
+ "vite": "^5.0.0",
+ "vite-node": "1.6.1",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.6.1",
+ "@vitest/ui": "1.6.1",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/winston": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
+ "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@colors/colors": "^1.6.0",
+ "@dabh/diagnostics": "^2.0.8",
+ "async": "^3.2.3",
+ "is-stream": "^2.0.0",
+ "logform": "^2.7.0",
+ "one-time": "^1.0.0",
+ "readable-stream": "^3.4.0",
+ "safe-stable-stringify": "^2.3.1",
+ "stack-trace": "0.0.x",
+ "triple-beam": "^1.3.0",
+ "winston-transport": "^4.9.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/winston-transport": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
+ "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
+ "license": "MIT",
+ "dependencies": {
+ "logform": "^2.7.0",
+ "readable-stream": "^3.6.2",
+ "triple-beam": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/winston/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json
new file mode 100644
index 0000000..84864ab
--- /dev/null
+++ b/packages/mcp-server/package.json
@@ -0,0 +1,67 @@
+{
+ "name": "@coorchat/mcp-server",
+ "version": "1.0.0",
+ "description": "Multi-Agent Coordination System - MCP Server Component",
+ "main": "dist/index.js",
+ "type": "module",
+ "bin": {
+ "coorchat": "dist/cli/index.js"
+ },
+ "scripts": {
+ "build": "tsc && tsc-alias",
+ "dev": "tsx watch src/index.ts",
+ "start": "node dist/index.js",
+ "cli": "tsx src/cli/index.ts",
+ "test": "vitest",
+ "test:integration": "vitest --config vitest.integration.config.ts",
+ "test:coverage": "vitest --coverage",
+ "lint": "eslint src/ --ext .ts",
+ "format": "prettier --write \"src/**/*.ts\""
+ },
+ "keywords": [
+ "multi-agent",
+ "coordination",
+ "ai-agents",
+ "mcp",
+ "discord",
+ "signalr",
+ "redis"
+ ],
+ "author": "CoorChat",
+ "license": "MIT",
+ "dependencies": {
+ "@microsoft/signalr": "^8.0.0",
+ "@octokit/rest": "^20.0.2",
+ "ajv": "^8.12.0",
+ "ajv-formats": "^2.1.1",
+ "commander": "^12.1.0",
+ "crypto": "^1.0.1",
+ "discord.js": "^14.14.1",
+ "dotenv": "^16.6.1",
+ "express": "^4.18.2",
+ "ioredis": "^5.3.2",
+ "uuid": "^9.0.1",
+ "winston": "^3.11.0",
+ "ws": "^8.16.0",
+ "yaml": "^2.3.4",
+ "zod": "^3.22.4"
+ },
+ "devDependencies": {
+ "@types/express": "^4.17.21",
+ "@types/node": "^20.11.5",
+ "@types/uuid": "^9.0.7",
+ "@types/ws": "^8.5.10",
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
+ "@typescript-eslint/parser": "^6.19.0",
+ "@vitest/coverage-v8": "^1.2.1",
+ "eslint": "^8.56.0",
+ "prettier": "^3.2.4",
+ "tsc-alias": "^1.8.8",
+ "tsx": "^4.7.0",
+ "typescript": "^5.3.3",
+ "vitest": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+}
diff --git a/packages/mcp-server/src/agents/Agent.ts b/packages/mcp-server/src/agents/Agent.ts
new file mode 100644
index 0000000..71e3ff6
--- /dev/null
+++ b/packages/mcp-server/src/agents/Agent.ts
@@ -0,0 +1,232 @@
+/**
+ * Agent - Represents a specialized AI agent participating in coordination
+ * Based on specs/001-multi-agent-coordination/data-model.md
+ */
+
+import { Capability, Platform } from './Capability.js';
+
+/**
+ * Agent connection status
+ */
+export enum AgentStatus {
+ DISCONNECTED = 'disconnected',
+ CONNECTING = 'connecting',
+ CONNECTED = 'connected',
+}
+
+/**
+ * Agent entity
+ */
+export interface Agent {
+ /** Unique agent identifier (UUID v4) */
+ id: string;
+
+ /** Agent role type (extensible: developer, tester, architect, custom roles) */
+ role: string;
+
+ /** Operating system platform */
+ platform: Platform;
+
+ /** Execution environment (local, GitHub Actions, Azure DevOps, AWS, etc.) */
+ environment: string;
+
+ /** Agent capability set */
+ capabilities: Capability;
+
+ /** Connection status */
+ status: AgentStatus;
+
+ /** ID of currently assigned task (optional) */
+ currentTask?: string | null;
+
+ /** When agent joined the channel */
+ registeredAt: Date;
+
+ /** Last activity timestamp */
+ lastSeenAt: Date;
+}
+
+/**
+ * Agent registration data (for initial join)
+ */
+export interface AgentRegistration {
+ /** Agent role type */
+ role: string;
+
+ /** Operating system platform */
+ platform: Platform;
+
+ /** Execution environment */
+ environment: string;
+
+ /** Agent capabilities */
+ capabilities: Capability;
+}
+
+/**
+ * Agent update data
+ */
+export interface AgentUpdate {
+ /** Update connection status */
+ status?: AgentStatus;
+
+ /** Update current task */
+ currentTask?: string | null;
+
+ /** Update last seen timestamp */
+ lastSeenAt?: Date;
+
+ /** Update capabilities */
+ capabilities?: Partial;
+}
+
+/**
+ * Agent query filter
+ */
+export interface AgentQuery {
+ /** Filter by role type */
+ role?: string;
+
+ /** Filter by platform */
+ platform?: Platform;
+
+ /** Filter by environment type */
+ environment?: string;
+
+ /** Filter by connection status */
+ status?: AgentStatus;
+
+ /** Filter by whether agent has current task */
+ hasCurrentTask?: boolean;
+
+ /** Filter by minimum last seen timestamp */
+ lastSeenSince?: Date;
+}
+
+/**
+ * Validate agent object
+ */
+export function validateAgent(agent: unknown): agent is Agent {
+ if (typeof agent !== 'object' || agent === null) {
+ return false;
+ }
+
+ const a = agent as Partial;
+
+ return (
+ typeof a.id === 'string' &&
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
+ a.id
+ ) &&
+ typeof a.role === 'string' &&
+ a.role.length > 0 &&
+ a.role.length <= 50 &&
+ typeof a.platform === 'string' &&
+ ['Linux', 'macOS', 'Windows'].includes(a.platform) &&
+ typeof a.environment === 'string' &&
+ a.environment.length > 0 &&
+ a.environment.length <= 100 &&
+ typeof a.capabilities === 'object' &&
+ typeof a.status === 'string' &&
+ Object.values(AgentStatus).includes(a.status as AgentStatus) &&
+ a.registeredAt instanceof Date &&
+ a.lastSeenAt instanceof Date
+ );
+}
+
+/**
+ * Check if an agent matches a query
+ */
+export function matchesAgentQuery(agent: Agent, query: AgentQuery): boolean {
+ if (query.role && agent.role !== query.role) {
+ return false;
+ }
+
+ if (query.platform && agent.platform !== query.platform) {
+ return false;
+ }
+
+ if (query.environment && agent.environment !== query.environment) {
+ return false;
+ }
+
+ if (query.status && agent.status !== query.status) {
+ return false;
+ }
+
+ if (query.hasCurrentTask !== undefined) {
+ const hasTask = agent.currentTask !== null && agent.currentTask !== undefined;
+ if (query.hasCurrentTask !== hasTask) {
+ return false;
+ }
+ }
+
+ if (query.lastSeenSince && agent.lastSeenAt < query.lastSeenSince) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Create an agent from registration data
+ */
+export function createAgent(id: string, registration: AgentRegistration): Agent {
+ const now = new Date();
+
+ return {
+ id,
+ role: registration.role,
+ platform: registration.platform,
+ environment: registration.environment,
+ capabilities: registration.capabilities,
+ status: AgentStatus.CONNECTING,
+ currentTask: null,
+ registeredAt: now,
+ lastSeenAt: now,
+ };
+}
+
+/**
+ * Update an agent with partial data
+ */
+export function updateAgent(agent: Agent, update: AgentUpdate): Agent {
+ return {
+ ...agent,
+ ...(update.status !== undefined && { status: update.status }),
+ ...(update.currentTask !== undefined && { currentTask: update.currentTask }),
+ ...(update.lastSeenAt && { lastSeenAt: update.lastSeenAt }),
+ ...(update.capabilities && {
+ capabilities: { ...agent.capabilities, ...update.capabilities },
+ }),
+ };
+}
+
+/**
+ * Check if an agent is active (connected and seen recently)
+ */
+export function isAgentActive(agent: Agent, timeoutMs: number = 30000): boolean {
+ if (agent.status !== AgentStatus.CONNECTED) {
+ return false;
+ }
+
+ const timeSinceLastSeen = Date.now() - agent.lastSeenAt.getTime();
+ return timeSinceLastSeen < timeoutMs;
+}
+
+/**
+ * Check if an agent is available for task assignment
+ */
+export function isAgentAvailable(agent: Agent): boolean {
+ return (
+ agent.status === AgentStatus.CONNECTED &&
+ (agent.currentTask === null || agent.currentTask === undefined)
+ );
+}
+
+/**
+ * Get agent display name
+ */
+export function getAgentDisplayName(agent: Agent): string {
+ return `${agent.role} (${agent.platform}/${agent.environment})`;
+}
diff --git a/packages/mcp-server/src/agents/AgentRegistry.ts b/packages/mcp-server/src/agents/AgentRegistry.ts
new file mode 100644
index 0000000..4beb48f
--- /dev/null
+++ b/packages/mcp-server/src/agents/AgentRegistry.ts
@@ -0,0 +1,342 @@
+/**
+ * AgentRegistry - Track connected agents, add/remove, get by ID/role
+ * Maintains a registry of all agents participating in the coordination system
+ */
+
+import type { Agent, AgentStatus, AgentQuery, AgentUpdate } from './Agent.js';
+import {
+ matchesAgentQuery,
+ isAgentActive,
+ updateAgent,
+ getAgentDisplayName,
+} from './Agent.js';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Agent event types
+ */
+export type AgentEventType = 'agent_added' | 'agent_updated' | 'agent_removed' | 'agent_timeout';
+
+/**
+ * Agent event
+ */
+export interface AgentEvent {
+ type: AgentEventType;
+ agent: Agent;
+ timestamp: Date;
+}
+
+/**
+ * Agent event handler
+ */
+export type AgentEventHandler = (event: AgentEvent) => void | Promise;
+
+/**
+ * Agent registry configuration
+ */
+export interface AgentRegistryConfig {
+ /** Logger */
+ logger?: Logger;
+
+ /** Timeout for agent inactivity (ms) */
+ timeoutMs?: number;
+
+ /** Whether to enable automatic timeout checking */
+ enableTimeoutChecking?: boolean;
+}
+
+/**
+ * AgentRegistry class
+ */
+export class AgentRegistry {
+ private agents: Map; // agentId β Agent
+ private logger: Logger;
+ private eventHandlers: Set;
+ private timeoutMs: number;
+ private enableTimeoutChecking: boolean;
+ private timeoutCheckInterval?: NodeJS.Timeout;
+
+ constructor(config: AgentRegistryConfig = {}) {
+ this.agents = new Map();
+ this.logger = config.logger || createLogger();
+ this.eventHandlers = new Set();
+ this.timeoutMs = config.timeoutMs || 30000; // 30 seconds default
+ this.enableTimeoutChecking = config.enableTimeoutChecking ?? true;
+
+ if (this.enableTimeoutChecking) {
+ this.startTimeoutChecking();
+ }
+ }
+
+ /**
+ * Add agent to registry
+ */
+ async add(agent: Agent): Promise {
+ if (this.agents.has(agent.id)) {
+ this.logger.warn('Agent already registered', { agentId: agent.id });
+ return;
+ }
+
+ this.agents.set(agent.id, agent);
+
+ await this.notifyHandlers({
+ type: 'agent_added',
+ agent,
+ timestamp: new Date(),
+ });
+
+ this.logger.info('Agent added to registry', {
+ agentId: agent.id,
+ role: agent.role,
+ displayName: getAgentDisplayName(agent),
+ });
+ }
+
+ /**
+ * Update agent in registry
+ */
+ async update(agentId: string, update: AgentUpdate): Promise {
+ const agent = this.agents.get(agentId);
+ if (!agent) {
+ this.logger.warn('Agent not found for update', { agentId });
+ return undefined;
+ }
+
+ const updatedAgent = updateAgent(agent, update);
+ this.agents.set(agentId, updatedAgent);
+
+ await this.notifyHandlers({
+ type: 'agent_updated',
+ agent: updatedAgent,
+ timestamp: new Date(),
+ });
+
+ this.logger.debug('Agent updated', { agentId, update });
+
+ return updatedAgent;
+ }
+
+ /**
+ * Remove agent from registry
+ */
+ async remove(agentId: string): Promise {
+ const agent = this.agents.get(agentId);
+ if (!agent) {
+ return false;
+ }
+
+ this.agents.delete(agentId);
+
+ await this.notifyHandlers({
+ type: 'agent_removed',
+ agent,
+ timestamp: new Date(),
+ });
+
+ this.logger.info('Agent removed from registry', {
+ agentId,
+ role: agent.role,
+ });
+
+ return true;
+ }
+
+ /**
+ * Get agent by ID
+ */
+ getById(agentId: string): Agent | undefined {
+ return this.agents.get(agentId);
+ }
+
+ /**
+ * Get agents by role
+ */
+ getByRole(role: string): Agent[] {
+ return Array.from(this.agents.values()).filter(
+ (agent) => agent.role === role
+ );
+ }
+
+ /**
+ * Get agents by status
+ */
+ getByStatus(status: AgentStatus): Agent[] {
+ return Array.from(this.agents.values()).filter(
+ (agent) => agent.status === status
+ );
+ }
+
+ /**
+ * Find agents matching query
+ */
+ find(query: AgentQuery): Agent[] {
+ return Array.from(this.agents.values()).filter((agent) =>
+ matchesAgentQuery(agent, query)
+ );
+ }
+
+ /**
+ * Get all agents
+ */
+ getAll(): Agent[] {
+ return Array.from(this.agents.values());
+ }
+
+ /**
+ * Get active agents (connected and seen recently)
+ */
+ getActive(): Agent[] {
+ return Array.from(this.agents.values()).filter((agent) =>
+ isAgentActive(agent, this.timeoutMs)
+ );
+ }
+
+ /**
+ * Get agent count
+ */
+ count(): number {
+ return this.agents.size;
+ }
+
+ /**
+ * Check if agent exists
+ */
+ has(agentId: string): boolean {
+ return this.agents.has(agentId);
+ }
+
+ /**
+ * Clear all agents
+ */
+ clear(): void {
+ this.agents.clear();
+ this.logger.info('Agent registry cleared');
+ }
+
+ /**
+ * Update agent's last seen timestamp
+ */
+ async heartbeat(agentId: string): Promise {
+ await this.update(agentId, { lastSeenAt: new Date() });
+ }
+
+ /**
+ * Start timeout checking
+ */
+ private startTimeoutChecking(): void {
+ this.timeoutCheckInterval = setInterval(() => {
+ this.checkTimeouts().catch((error) => {
+ this.logger.error('Error checking timeouts', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ });
+ }, this.timeoutMs / 2); // Check at half the timeout interval
+ }
+
+ /**
+ * Check for timed-out agents
+ */
+ private async checkTimeouts(): Promise {
+ const now = Date.now();
+ const timedOutAgents: Agent[] = [];
+
+ for (const agent of this.agents.values()) {
+ const timeSinceLastSeen = now - agent.lastSeenAt.getTime();
+ if (timeSinceLastSeen > this.timeoutMs && agent.status !== 'disconnected') {
+ timedOutAgents.push(agent);
+ }
+ }
+
+ // Handle timed-out agents
+ for (const agent of timedOutAgents) {
+ this.logger.warn('Agent timed out', {
+ agentId: agent.id,
+ role: agent.role,
+ lastSeenAt: agent.lastSeenAt,
+ });
+
+ // Update status to disconnected
+ await this.update(agent.id, { status: 'disconnected' });
+
+ // Notify handlers
+ await this.notifyHandlers({
+ type: 'agent_timeout',
+ agent,
+ timestamp: new Date(),
+ });
+ }
+ }
+
+ /**
+ * Stop timeout checking
+ */
+ stopTimeoutChecking(): void {
+ if (this.timeoutCheckInterval) {
+ clearInterval(this.timeoutCheckInterval);
+ this.timeoutCheckInterval = undefined;
+ }
+ }
+
+ /**
+ * Register event handler
+ */
+ onEvent(handler: AgentEventHandler): () => void {
+ this.eventHandlers.add(handler);
+ return () => this.eventHandlers.delete(handler);
+ }
+
+ /**
+ * Notify event handlers
+ */
+ private async notifyHandlers(event: AgentEvent): Promise {
+ const promises = Array.from(this.eventHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in agent event handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Get registry statistics
+ */
+ getStats(): {
+ total: number;
+ active: number;
+ connected: number;
+ disconnected: number;
+ byRole: Record;
+ } {
+ const all = this.getAll();
+ const active = this.getActive();
+ const connected = this.getByStatus('connected');
+ const disconnected = this.getByStatus('disconnected');
+
+ // Count by role
+ const byRole: Record = {};
+ for (const agent of all) {
+ byRole[agent.role] = (byRole[agent.role] || 0) + 1;
+ }
+
+ return {
+ total: all.length,
+ active: active.length,
+ connected: connected.length,
+ disconnected: disconnected.length,
+ byRole,
+ };
+ }
+
+ /**
+ * Cleanup (stop timeout checking)
+ */
+ destroy(): void {
+ this.stopTimeoutChecking();
+ }
+}
diff --git a/packages/mcp-server/src/agents/Capability.ts b/packages/mcp-server/src/agents/Capability.ts
new file mode 100644
index 0000000..a8eee7e
--- /dev/null
+++ b/packages/mcp-server/src/agents/Capability.ts
@@ -0,0 +1,220 @@
+/**
+ * Capability - Agent capability registration and discovery
+ * Based on specs/001-multi-agent-coordination/contracts/capability-schema.json
+ */
+
+/**
+ * Resource limits for agent capabilities
+ */
+export interface ResourceLimits {
+ /** Maximum API calls per hour */
+ apiQuotaPerHour?: number;
+
+ /** Maximum number of simultaneous tasks (1-10) */
+ maxConcurrentTasks?: number;
+
+ /** Maximum requests per minute */
+ rateLimitPerMinute?: number;
+
+ /** Memory constraint in megabytes */
+ memoryLimitMB?: number;
+}
+
+/**
+ * Operating system platform
+ */
+export type Platform = 'Linux' | 'macOS' | 'Windows';
+
+/**
+ * Agent capability registration
+ */
+export interface Capability {
+ /** Unique identifier for the agent */
+ agentId: string;
+
+ /** Agent role (extensible: developer, tester, architect, or custom) */
+ roleType: string;
+
+ /** Operating system platform */
+ platform: Platform;
+
+ /** Execution environment */
+ environmentType?: string;
+
+ /** Available commands, CLIs, or APIs the agent can use */
+ tools: string[];
+
+ /** Programming languages the agent can work with */
+ languages?: string[];
+
+ /** External APIs the agent has access to */
+ apiAccess?: string[];
+
+ /** Resource constraints and quotas for the agent */
+ resourceLimits?: ResourceLimits;
+
+ /** Custom capability metadata for specialized agent types */
+ customMetadata?: Record;
+}
+
+/**
+ * Capability query filter
+ */
+export interface CapabilityQuery {
+ /** Filter by role type */
+ roleType?: string;
+
+ /** Filter by platform */
+ platform?: Platform;
+
+ /** Filter by environment type */
+ environmentType?: string;
+
+ /** Filter by required tools (must have all) */
+ requiredTools?: string[];
+
+ /** Filter by required languages (must have all) */
+ requiredLanguages?: string[];
+
+ /** Filter by required API access (must have all) */
+ requiredApiAccess?: string[];
+
+ /** Filter by minimum resource limits */
+ minResourceLimits?: Partial;
+}
+
+/**
+ * Validate capability object
+ */
+export function validateCapability(capability: unknown): capability is Capability {
+ if (typeof capability !== 'object' || capability === null) {
+ return false;
+ }
+
+ const cap = capability as Partial;
+
+ return (
+ typeof cap.agentId === 'string' &&
+ typeof cap.roleType === 'string' &&
+ cap.roleType.length > 0 &&
+ cap.roleType.length <= 50 &&
+ typeof cap.platform === 'string' &&
+ ['Linux', 'macOS', 'Windows'].includes(cap.platform) &&
+ Array.isArray(cap.tools) &&
+ cap.tools.length > 0 &&
+ cap.tools.every((tool) => typeof tool === 'string')
+ );
+}
+
+/**
+ * Check if a capability matches a query
+ */
+export function matchesQuery(
+ capability: Capability,
+ query: CapabilityQuery
+): boolean {
+ // Check role type
+ if (query.roleType && capability.roleType !== query.roleType) {
+ return false;
+ }
+
+ // Check platform
+ if (query.platform && capability.platform !== query.platform) {
+ return false;
+ }
+
+ // Check environment type
+ if (
+ query.environmentType &&
+ capability.environmentType !== query.environmentType
+ ) {
+ return false;
+ }
+
+ // Check required tools
+ if (query.requiredTools) {
+ const hasAllTools = query.requiredTools.every((tool) =>
+ capability.tools.includes(tool)
+ );
+ if (!hasAllTools) {
+ return false;
+ }
+ }
+
+ // Check required languages
+ if (query.requiredLanguages && capability.languages) {
+ const hasAllLanguages = query.requiredLanguages.every((lang) =>
+ capability.languages!.includes(lang)
+ );
+ if (!hasAllLanguages) {
+ return false;
+ }
+ }
+
+ // Check required API access
+ if (query.requiredApiAccess && capability.apiAccess) {
+ const hasAllApis = query.requiredApiAccess.every((api) =>
+ capability.apiAccess!.includes(api)
+ );
+ if (!hasAllApis) {
+ return false;
+ }
+ }
+
+ // Check minimum resource limits
+ if (query.minResourceLimits && capability.resourceLimits) {
+ if (
+ query.minResourceLimits.apiQuotaPerHour &&
+ (capability.resourceLimits.apiQuotaPerHour || 0) <
+ query.minResourceLimits.apiQuotaPerHour
+ ) {
+ return false;
+ }
+
+ if (
+ query.minResourceLimits.maxConcurrentTasks &&
+ (capability.resourceLimits.maxConcurrentTasks || 1) <
+ query.minResourceLimits.maxConcurrentTasks
+ ) {
+ return false;
+ }
+
+ if (
+ query.minResourceLimits.rateLimitPerMinute &&
+ (capability.resourceLimits.rateLimitPerMinute || 0) <
+ query.minResourceLimits.rateLimitPerMinute
+ ) {
+ return false;
+ }
+
+ if (
+ query.minResourceLimits.memoryLimitMB &&
+ (capability.resourceLimits.memoryLimitMB || 0) <
+ query.minResourceLimits.memoryLimitMB
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Create a default capability object
+ */
+export function createDefaultCapability(
+ agentId: string,
+ roleType: string,
+ platform: Platform,
+ tools: string[]
+): Capability {
+ return {
+ agentId,
+ roleType,
+ platform,
+ tools,
+ resourceLimits: {
+ maxConcurrentTasks: 1,
+ },
+ };
+}
diff --git a/packages/mcp-server/src/agents/RoleManager.ts b/packages/mcp-server/src/agents/RoleManager.ts
new file mode 100644
index 0000000..5359e6a
--- /dev/null
+++ b/packages/mcp-server/src/agents/RoleManager.ts
@@ -0,0 +1,411 @@
+/**
+ * RoleManager - Extensible role definitions and validation for custom roles
+ * Manages predefined and custom agent role types
+ */
+
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Role definition
+ */
+export interface RoleDefinition {
+ /** Role name (unique identifier) */
+ name: string;
+
+ /** Human-readable description */
+ description: string;
+
+ /** Predefined or custom */
+ type: 'predefined' | 'custom';
+
+ /** Suggested capabilities */
+ suggestedCapabilities?: {
+ tools?: string[];
+ languages?: string[];
+ apiAccess?: string[];
+ };
+
+ /** Metadata */
+ metadata?: Record;
+
+ /** When role was registered */
+ registeredAt: Date;
+}
+
+/**
+ * Role validation result
+ */
+export interface RoleValidation {
+ valid: boolean;
+ errors?: string[];
+}
+
+/**
+ * Role manager configuration
+ */
+export interface RoleManagerConfig {
+ /** Logger */
+ logger?: Logger;
+
+ /** Whether to allow custom roles */
+ allowCustomRoles?: boolean;
+
+ /** Maximum role name length */
+ maxRoleNameLength?: number;
+}
+
+/**
+ * RoleManager class
+ */
+export class RoleManager {
+ private roles: Map;
+ private logger: Logger;
+ private allowCustomRoles: boolean;
+ private maxRoleNameLength: number;
+
+ constructor(config: RoleManagerConfig = {}) {
+ this.roles = new Map();
+ this.logger = config.logger || createLogger();
+ this.allowCustomRoles = config.allowCustomRoles ?? true;
+ this.maxRoleNameLength = config.maxRoleNameLength || 50;
+
+ // Register predefined roles
+ this.registerPredefinedRoles();
+ }
+
+ /**
+ * Register predefined roles
+ */
+ private registerPredefinedRoles(): void {
+ const predefinedRoles: Omit[] = [
+ {
+ name: 'developer',
+ description: 'Software developer - writes code, implements features',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['git', 'npm', 'docker'],
+ languages: ['TypeScript', 'JavaScript', 'Python'],
+ },
+ },
+ {
+ name: 'tester',
+ description: 'Quality assurance tester - writes and runs tests',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['jest', 'pytest', 'selenium', 'playwright'],
+ languages: ['TypeScript', 'JavaScript', 'Python'],
+ },
+ },
+ {
+ name: 'architect',
+ description: 'Software architect - designs system architecture',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['draw.io', 'plantuml'],
+ },
+ },
+ {
+ name: 'frontend',
+ description: 'Frontend developer - UI/UX implementation',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['npm', 'webpack', 'vite'],
+ languages: ['TypeScript', 'JavaScript', 'HTML', 'CSS'],
+ },
+ },
+ {
+ name: 'backend',
+ description: 'Backend developer - server-side implementation',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['npm', 'docker', 'database-cli'],
+ languages: ['TypeScript', 'JavaScript', 'Python', 'Go'],
+ },
+ },
+ {
+ name: 'infrastructure',
+ description: 'Infrastructure engineer - DevOps, deployment, monitoring',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['docker', 'kubernetes', 'terraform', 'aws-cli', 'gcloud'],
+ },
+ },
+ {
+ name: 'security-auditor',
+ description: 'Security auditor - security analysis and vulnerability testing',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['nmap', 'burp-suite', 'owasp-zap'],
+ },
+ },
+ {
+ name: 'documentation-writer',
+ description: 'Technical writer - creates and maintains documentation',
+ type: 'predefined',
+ suggestedCapabilities: {
+ tools: ['markdown', 'docusaurus', 'sphinx'],
+ },
+ },
+ ];
+
+ for (const role of predefinedRoles) {
+ this.roles.set(role.name, {
+ ...role,
+ registeredAt: new Date(),
+ });
+ }
+
+ this.logger.info('Predefined roles registered', {
+ count: predefinedRoles.length,
+ });
+ }
+
+ /**
+ * Register a custom role
+ */
+ registerCustomRole(
+ name: string,
+ description: string,
+ suggestedCapabilities?: RoleDefinition['suggestedCapabilities'],
+ metadata?: Record
+ ): RoleDefinition {
+ // Validate role name
+ const validation = this.validateRoleName(name);
+ if (!validation.valid) {
+ throw new Error(`Invalid role name: ${validation.errors?.join(', ')}`);
+ }
+
+ // Check if custom roles are allowed
+ if (!this.allowCustomRoles) {
+ throw new Error('Custom roles are not allowed');
+ }
+
+ // Check if role already exists
+ if (this.roles.has(name)) {
+ throw new Error(`Role already exists: ${name}`);
+ }
+
+ const role: RoleDefinition = {
+ name,
+ description,
+ type: 'custom',
+ suggestedCapabilities,
+ metadata,
+ registeredAt: new Date(),
+ };
+
+ this.roles.set(name, role);
+
+ this.logger.info('Custom role registered', { name, description });
+
+ return role;
+ }
+
+ /**
+ * Get role definition
+ */
+ getRole(name: string): RoleDefinition | undefined {
+ return this.roles.get(name);
+ }
+
+ /**
+ * Check if role exists
+ */
+ hasRole(name: string): boolean {
+ return this.roles.has(name);
+ }
+
+ /**
+ * Get all roles
+ */
+ getAllRoles(): RoleDefinition[] {
+ return Array.from(this.roles.values());
+ }
+
+ /**
+ * Get predefined roles
+ */
+ getPredefinedRoles(): RoleDefinition[] {
+ return Array.from(this.roles.values()).filter(
+ (role) => role.type === 'predefined'
+ );
+ }
+
+ /**
+ * Get custom roles
+ */
+ getCustomRoles(): RoleDefinition[] {
+ return Array.from(this.roles.values()).filter(
+ (role) => role.type === 'custom'
+ );
+ }
+
+ /**
+ * Validate role name
+ */
+ validateRoleName(name: string): RoleValidation {
+ const errors: string[] = [];
+
+ if (!name || name.trim().length === 0) {
+ errors.push('Role name cannot be empty');
+ }
+
+ if (name.length > this.maxRoleNameLength) {
+ errors.push(`Role name too long (max: ${this.maxRoleNameLength})`);
+ }
+
+ if (!/^[a-z0-9-]+$/.test(name)) {
+ errors.push('Role name must contain only lowercase letters, numbers, and hyphens');
+ }
+
+ if (name.startsWith('-') || name.endsWith('-')) {
+ errors.push('Role name cannot start or end with a hyphen');
+ }
+
+ return {
+ valid: errors.length === 0,
+ errors: errors.length > 0 ? errors : undefined,
+ };
+ }
+
+ /**
+ * Validate role (check if it exists or can be created)
+ */
+ validateRole(name: string): RoleValidation {
+ // Check if role exists
+ if (this.hasRole(name)) {
+ return { valid: true };
+ }
+
+ // Check if role name is valid for custom roles
+ if (!this.allowCustomRoles) {
+ return {
+ valid: false,
+ errors: ['Custom roles are not allowed, and role is not predefined'],
+ };
+ }
+
+ return this.validateRoleName(name);
+ }
+
+ /**
+ * Remove custom role
+ */
+ removeCustomRole(name: string): boolean {
+ const role = this.roles.get(name);
+ if (!role) {
+ return false;
+ }
+
+ if (role.type === 'predefined') {
+ throw new Error('Cannot remove predefined role');
+ }
+
+ this.roles.delete(name);
+ this.logger.info('Custom role removed', { name });
+ return true;
+ }
+
+ /**
+ * Update role description
+ */
+ updateRole(
+ name: string,
+ updates: {
+ description?: string;
+ suggestedCapabilities?: RoleDefinition['suggestedCapabilities'];
+ metadata?: Record;
+ }
+ ): RoleDefinition | undefined {
+ const role = this.roles.get(name);
+ if (!role) {
+ return undefined;
+ }
+
+ if (role.type === 'predefined') {
+ throw new Error('Cannot update predefined role');
+ }
+
+ const updatedRole: RoleDefinition = {
+ ...role,
+ ...(updates.description && { description: updates.description }),
+ ...(updates.suggestedCapabilities && {
+ suggestedCapabilities: updates.suggestedCapabilities,
+ }),
+ ...(updates.metadata && { metadata: updates.metadata }),
+ };
+
+ this.roles.set(name, updatedRole);
+ this.logger.info('Role updated', { name });
+
+ return updatedRole;
+ }
+
+ /**
+ * Get role suggestions based on capabilities
+ */
+ suggestRoles(capabilities: {
+ tools?: string[];
+ languages?: string[];
+ }): RoleDefinition[] {
+ const suggestions: Array<{ role: RoleDefinition; score: number }> = [];
+
+ for (const role of this.roles.values()) {
+ if (!role.suggestedCapabilities) {
+ continue;
+ }
+
+ let score = 0;
+
+ // Match tools
+ if (capabilities.tools && role.suggestedCapabilities.tools) {
+ const matchingTools = capabilities.tools.filter((tool) =>
+ role.suggestedCapabilities.tools?.includes(tool)
+ );
+ score += matchingTools.length;
+ }
+
+ // Match languages
+ if (capabilities.languages && role.suggestedCapabilities.languages) {
+ const matchingLanguages = capabilities.languages.filter((lang) =>
+ role.suggestedCapabilities.languages?.includes(lang)
+ );
+ score += matchingLanguages.length;
+ }
+
+ if (score > 0) {
+ suggestions.push({ role, score });
+ }
+ }
+
+ // Sort by score (highest first)
+ suggestions.sort((a, b) => b.score - a.score);
+
+ return suggestions.map((s) => s.role);
+ }
+
+ /**
+ * Get statistics
+ */
+ getStats(): {
+ total: number;
+ predefined: number;
+ custom: number;
+ } {
+ const all = this.getAllRoles();
+ const predefined = this.getPredefinedRoles();
+ const custom = this.getCustomRoles();
+
+ return {
+ total: all.length,
+ predefined: predefined.length,
+ custom: custom.length,
+ };
+ }
+}
+
+/**
+ * Singleton role manager instance
+ */
+export const roleManager = new RoleManager();
diff --git a/packages/mcp-server/src/channels/base/Channel.ts b/packages/mcp-server/src/channels/base/Channel.ts
new file mode 100644
index 0000000..a3c6079
--- /dev/null
+++ b/packages/mcp-server/src/channels/base/Channel.ts
@@ -0,0 +1,187 @@
+/**
+ * Channel - Base interface for all communication channels
+ *
+ * Defines the contract for Discord, SignalR, Redis, and Relay channel implementations
+ */
+
+import { Message } from '../../protocol/Message.js';
+
+/**
+ * Channel connection status
+ */
+export enum ConnectionStatus {
+ DISCONNECTED = 'disconnected',
+ CONNECTING = 'connecting',
+ CONNECTED = 'connected',
+ RECONNECTING = 'reconnecting',
+ FAILED = 'failed',
+}
+
+/**
+ * Channel configuration
+ */
+export interface ChannelConfig {
+ /** Channel type identifier */
+ type: 'discord' | 'signalr' | 'redis' | 'relay';
+
+ /** Authentication token */
+ token: string;
+
+ /** Channel-specific connection parameters */
+ connectionParams: Record;
+
+ /** Retry configuration */
+ retry?: {
+ enabled: boolean;
+ maxAttempts: number;
+ initialDelayMs: number;
+ maxDelayMs: number;
+ };
+
+ /** Heartbeat configuration */
+ heartbeat?: {
+ enabled: boolean;
+ intervalMs: number;
+ timeoutMs: number;
+ };
+}
+
+/**
+ * Channel statistics
+ */
+export interface ChannelStats {
+ messagesSent: number;
+ messagesReceived: number;
+ messagesFailed: number;
+ bytesTransferred: number;
+ uptime: number;
+ lastHeartbeat?: Date;
+ lastError?: string;
+}
+
+/**
+ * Message handler callback
+ */
+export type MessageHandler = (message: Message) => void | Promise;
+
+/**
+ * Error handler callback
+ */
+export type ErrorHandler = (error: Error) => void | Promise;
+
+/**
+ * Connection state change callback
+ */
+export type ConnectionStateHandler = (
+ status: ConnectionStatus,
+ previousStatus: ConnectionStatus
+) => void | Promise;
+
+/**
+ * Base Channel interface
+ * All channel implementations must implement this interface
+ */
+export interface Channel {
+ /**
+ * Get channel unique identifier
+ */
+ readonly id: string;
+
+ /**
+ * Get channel type
+ */
+ readonly type: string;
+
+ /**
+ * Get current connection status
+ */
+ readonly status: ConnectionStatus;
+
+ /**
+ * Connect to the channel
+ * @throws Error if connection fails
+ */
+ connect(): Promise;
+
+ /**
+ * Disconnect from the channel
+ */
+ disconnect(): Promise;
+
+ /**
+ * Send a message through the channel
+ * @param message - Message to send
+ * @returns Promise that resolves when message is sent
+ * @throws Error if send fails
+ */
+ sendMessage(message: Message): Promise;
+
+ /**
+ * Register a message handler
+ * @param handler - Function to call when a message is received
+ * @returns Function to unregister the handler
+ */
+ onMessage(handler: MessageHandler): () => void;
+
+ /**
+ * Register an error handler
+ * @param handler - Function to call when an error occurs
+ * @returns Function to unregister the handler
+ */
+ onError(handler: ErrorHandler): () => void;
+
+ /**
+ * Register a connection state change handler
+ * @param handler - Function to call when connection state changes
+ * @returns Function to unregister the handler
+ */
+ onConnectionStateChange(handler: ConnectionStateHandler): () => void;
+
+ /**
+ * Get channel statistics
+ */
+ getStats(): ChannelStats;
+
+ /**
+ * Check if channel is connected
+ */
+ isConnected(): boolean;
+
+ /**
+ * Retrieve message history (if supported by channel)
+ * @param limit - Maximum number of messages to retrieve
+ * @param before - Retrieve messages before this timestamp
+ * @returns Array of messages (empty if not supported)
+ */
+ getHistory(limit?: number, before?: Date): Promise;
+
+ /**
+ * Ping the channel to check connectivity
+ * @returns Round-trip time in milliseconds
+ * @throws Error if ping fails
+ */
+ ping(): Promise;
+}
+
+/**
+ * Type guard to check if an object implements the Channel interface
+ */
+export function isChannel(obj: unknown): obj is Channel {
+ if (typeof obj !== 'object' || obj === null) {
+ return false;
+ }
+
+ const channel = obj as Partial;
+
+ return (
+ typeof channel.id === 'string' &&
+ typeof channel.type === 'string' &&
+ typeof channel.status === 'string' &&
+ typeof channel.connect === 'function' &&
+ typeof channel.disconnect === 'function' &&
+ typeof channel.sendMessage === 'function' &&
+ typeof channel.onMessage === 'function' &&
+ typeof channel.onError === 'function' &&
+ typeof channel.isConnected === 'function'
+ );
+}
diff --git a/packages/mcp-server/src/channels/base/ChannelAdapter.ts b/packages/mcp-server/src/channels/base/ChannelAdapter.ts
new file mode 100644
index 0000000..239fde4
--- /dev/null
+++ b/packages/mcp-server/src/channels/base/ChannelAdapter.ts
@@ -0,0 +1,459 @@
+/**
+ * ChannelAdapter - Base class for channel implementations
+ *
+ * Provides common functionality:
+ * - Reconnection logic with exponential backoff
+ * - Error handling and recovery
+ * - Message handler management
+ * - Statistics tracking
+ * - Heartbeat mechanism
+ */
+
+import { v4 as uuidv4 } from 'uuid';
+import {
+ Channel,
+ ChannelConfig,
+ ChannelStats,
+ ConnectionStatus,
+ MessageHandler,
+ ErrorHandler,
+ ConnectionStateHandler,
+} from './Channel.js';
+import { Message } from '../../protocol/Message.js';
+
+/**
+ * Abstract base class for channel implementations
+ */
+export abstract class ChannelAdapter implements Channel {
+ public readonly id: string;
+ public readonly type: string;
+ protected config: ChannelConfig;
+ protected _status: ConnectionStatus;
+ protected stats: ChannelStats;
+ protected messageHandlers: Set;
+ protected errorHandlers: Set;
+ protected stateChangeHandlers: Set;
+ protected reconnectAttempts: number;
+ protected reconnectTimer?: NodeJS.Timeout;
+ protected heartbeatTimer?: NodeJS.Timeout;
+ protected connectionStartTime?: Date;
+
+ constructor(config: ChannelConfig) {
+ this.id = uuidv4();
+ this.type = config.type;
+ this.config = config;
+ this._status = ConnectionStatus.DISCONNECTED;
+ this.reconnectAttempts = 0;
+ this.messageHandlers = new Set();
+ this.errorHandlers = new Set();
+ this.stateChangeHandlers = new Set();
+ this.stats = {
+ messagesSent: 0,
+ messagesReceived: 0,
+ messagesFailed: 0,
+ bytesTransferred: 0,
+ uptime: 0,
+ };
+
+ // Validate authentication token
+ this.validateToken();
+ }
+
+ /**
+ * Validate authentication token
+ */
+ protected validateToken(): void {
+ if (!this.config.token || this.config.token.length < 16) {
+ throw new Error('Invalid or missing authentication token (minimum 16 characters)');
+ }
+ }
+
+ /**
+ * Verify token matches expected value
+ */
+ protected verifyToken(providedToken: string): boolean {
+ // Use timing-safe comparison to prevent timing attacks
+ if (!providedToken || !this.config.token) {
+ return false;
+ }
+
+ // Convert to buffers for timing-safe comparison
+ const providedBuffer = Buffer.from(providedToken);
+ const expectedBuffer = Buffer.from(this.config.token);
+
+ // Lengths must match
+ if (providedBuffer.length !== expectedBuffer.length) {
+ return false;
+ }
+
+ // Timing-safe comparison
+ let result = 0;
+ for (let i = 0; i < providedBuffer.length; i++) {
+ result |= providedBuffer[i] ^ expectedBuffer[i];
+ }
+
+ return result === 0;
+ }
+
+ /**
+ * Get authentication token for outbound connections
+ */
+ protected getAuthToken(): string {
+ return this.config.token;
+ }
+
+ /**
+ * Get current connection status
+ */
+ get status(): ConnectionStatus {
+ return this._status;
+ }
+
+ /**
+ * Set connection status and notify handlers
+ */
+ protected setStatus(newStatus: ConnectionStatus): void {
+ const previousStatus = this._status;
+ if (previousStatus === newStatus) {
+ return;
+ }
+
+ this._status = newStatus;
+
+ // Update connection start time
+ if (newStatus === ConnectionStatus.CONNECTED) {
+ this.connectionStartTime = new Date();
+ this.reconnectAttempts = 0;
+ }
+
+ // Notify state change handlers
+ this.stateChangeHandlers.forEach((handler) => {
+ this.safeCall(handler, newStatus, previousStatus);
+ });
+ }
+
+ /**
+ * Connect to the channel (with retry logic)
+ */
+ async connect(): Promise {
+ if (this.isConnected()) {
+ return;
+ }
+
+ this.setStatus(ConnectionStatus.CONNECTING);
+
+ try {
+ await this.doConnect();
+ this.setStatus(ConnectionStatus.CONNECTED);
+ this.startHeartbeat();
+ } catch (error) {
+ this.setStatus(ConnectionStatus.FAILED);
+ this.handleError(
+ error instanceof Error
+ ? error
+ : new Error(`Connection failed: ${String(error)}`)
+ );
+
+ // Attempt reconnection if configured
+ if (this.config.retry?.enabled) {
+ await this.scheduleReconnect();
+ } else {
+ throw error;
+ }
+ }
+ }
+
+ /**
+ * Disconnect from the channel
+ */
+ async disconnect(): Promise {
+ this.stopHeartbeat();
+ this.clearReconnectTimer();
+
+ if (this._status === ConnectionStatus.DISCONNECTED) {
+ return;
+ }
+
+ try {
+ await this.doDisconnect();
+ } finally {
+ this.setStatus(ConnectionStatus.DISCONNECTED);
+ this.connectionStartTime = undefined;
+ }
+ }
+
+ /**
+ * Send a message through the channel
+ */
+ async sendMessage(message: Message): Promise {
+ if (!this.isConnected()) {
+ throw new Error(`Cannot send message: channel is ${this._status}`);
+ }
+
+ try {
+ // Route message based on recipient
+ await this.routeMessage(message);
+ this.stats.messagesSent++;
+ this.stats.bytesTransferred += this.estimateMessageSize(message);
+ } catch (error) {
+ this.stats.messagesFailed++;
+ this.handleError(
+ error instanceof Error
+ ? error
+ : new Error(`Failed to send message: ${String(error)}`)
+ );
+ throw error;
+ }
+ }
+
+ /**
+ * Route message (broadcast vs unicast)
+ */
+ protected async routeMessage(message: Message): Promise {
+ if (this.isBroadcast(message)) {
+ // Broadcast message to all participants
+ await this.broadcastMessage(message);
+ } else {
+ // Unicast message to specific recipient
+ await this.unicastMessage(message);
+ }
+ }
+
+ /**
+ * Check if message is a broadcast
+ */
+ protected isBroadcast(message: Message): boolean {
+ return !message.recipientId || message.recipientId === null;
+ }
+
+ /**
+ * Broadcast message to all participants
+ */
+ protected async broadcastMessage(message: Message): Promise {
+ // Default implementation: send to channel (all subscribers receive it)
+ await this.doSendMessage(message);
+ }
+
+ /**
+ * Unicast message to specific recipient
+ */
+ protected async unicastMessage(message: Message): Promise {
+ // Default implementation: still send to channel
+ // Subclasses can override for direct messaging if supported
+ await this.doSendMessage(message);
+ }
+
+ /**
+ * Register a message handler
+ */
+ onMessage(handler: MessageHandler): () => void {
+ this.messageHandlers.add(handler);
+ return () => this.messageHandlers.delete(handler);
+ }
+
+ /**
+ * Register an error handler
+ */
+ onError(handler: ErrorHandler): () => void {
+ this.errorHandlers.add(handler);
+ return () => this.errorHandlers.delete(handler);
+ }
+
+ /**
+ * Register a connection state change handler
+ */
+ onConnectionStateChange(handler: ConnectionStateHandler): () => void {
+ this.stateChangeHandlers.add(handler);
+ return () => this.stateChangeHandlers.delete(handler);
+ }
+
+ /**
+ * Get channel statistics
+ */
+ getStats(): ChannelStats {
+ return {
+ ...this.stats,
+ uptime: this.connectionStartTime
+ ? Date.now() - this.connectionStartTime.getTime()
+ : 0,
+ };
+ }
+
+ /**
+ * Check if channel is connected
+ */
+ isConnected(): boolean {
+ return this._status === ConnectionStatus.CONNECTED;
+ }
+
+ /**
+ * Retrieve message history (default: not supported)
+ */
+ async getHistory(_limit?: number, _before?: Date): Promise {
+ return [];
+ }
+
+ /**
+ * Ping the channel (default implementation)
+ */
+ async ping(): Promise {
+ const start = Date.now();
+ await this.doPing();
+ return Date.now() - start;
+ }
+
+ /**
+ * Abstract methods to be implemented by subclasses
+ */
+ protected abstract doConnect(): Promise;
+ protected abstract doDisconnect(): Promise;
+ protected abstract doSendMessage(message: Message): Promise;
+ protected abstract doPing(): Promise;
+
+ /**
+ * Handle received message
+ */
+ protected handleMessage(message: Message): void {
+ this.stats.messagesReceived++;
+ this.stats.bytesTransferred += this.estimateMessageSize(message);
+
+ this.messageHandlers.forEach((handler) => {
+ this.safeCall(handler, message);
+ });
+ }
+
+ /**
+ * Handle error
+ */
+ protected handleError(error: Error): void {
+ this.stats.lastError = error.message;
+
+ this.errorHandlers.forEach((handler) => {
+ this.safeCall(handler, error);
+ });
+ }
+
+ /**
+ * Schedule reconnection with exponential backoff
+ */
+ protected async scheduleReconnect(): Promise {
+ if (!this.config.retry?.enabled) {
+ return;
+ }
+
+ this.clearReconnectTimer();
+
+ if (this.reconnectAttempts >= (this.config.retry.maxAttempts || 5)) {
+ this.handleError(
+ new Error('Max reconnection attempts reached, giving up')
+ );
+ return;
+ }
+
+ this.reconnectAttempts++;
+ const delay = this.calculateBackoff();
+
+ this.setStatus(ConnectionStatus.RECONNECTING);
+
+ this.reconnectTimer = setTimeout(async () => {
+ try {
+ await this.connect();
+ } catch (error) {
+ // connect() already handles retry
+ }
+ }, delay);
+ }
+
+ /**
+ * Calculate exponential backoff delay with jitter
+ */
+ protected calculateBackoff(): number {
+ const { initialDelayMs = 1000, maxDelayMs = 60000 } = this.config.retry || {};
+ const exponentialDelay = initialDelayMs * Math.pow(2, this.reconnectAttempts - 1);
+ const jitter = Math.random() * 0.3 * exponentialDelay; // 30% jitter
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
+ }
+
+ /**
+ * Clear reconnect timer
+ */
+ protected clearReconnectTimer(): void {
+ if (this.reconnectTimer) {
+ clearTimeout(this.reconnectTimer);
+ this.reconnectTimer = undefined;
+ }
+ }
+
+ /**
+ * Start heartbeat mechanism
+ */
+ protected startHeartbeat(): void {
+ if (!this.config.heartbeat?.enabled) {
+ return;
+ }
+
+ this.stopHeartbeat();
+
+ this.heartbeatTimer = setInterval(() => {
+ this.sendHeartbeat().catch((error) => {
+ this.handleError(
+ error instanceof Error
+ ? error
+ : new Error(`Heartbeat failed: ${String(error)}`)
+ );
+ });
+ }, this.config.heartbeat.intervalMs || 15000);
+ }
+
+ /**
+ * Stop heartbeat mechanism
+ */
+ protected stopHeartbeat(): void {
+ if (this.heartbeatTimer) {
+ clearInterval(this.heartbeatTimer);
+ this.heartbeatTimer = undefined;
+ }
+ }
+
+ /**
+ * Send heartbeat (can be overridden by subclasses)
+ */
+ protected async sendHeartbeat(): Promise {
+ try {
+ await this.ping();
+ this.stats.lastHeartbeat = new Date();
+ } catch (error) {
+ // Heartbeat failed, trigger reconnection if needed
+ if (this.config.retry?.enabled) {
+ await this.scheduleReconnect();
+ }
+ throw error;
+ }
+ }
+
+ /**
+ * Estimate message size in bytes
+ */
+ protected estimateMessageSize(message: Message): number {
+ return JSON.stringify(message).length;
+ }
+
+ /**
+ * Safely call a handler with error protection
+ */
+ protected safeCall(
+ handler: (...args: T) => void | Promise,
+ ...args: T
+ ): void {
+ try {
+ const result = handler(...args);
+ if (result instanceof Promise) {
+ result.catch((error) => {
+ console.error('Handler error:', error);
+ });
+ }
+ } catch (error) {
+ console.error('Handler error:', error);
+ }
+ }
+}
diff --git a/packages/mcp-server/src/channels/base/ChannelFactory.ts b/packages/mcp-server/src/channels/base/ChannelFactory.ts
new file mode 100644
index 0000000..72bfb9a
--- /dev/null
+++ b/packages/mcp-server/src/channels/base/ChannelFactory.ts
@@ -0,0 +1,194 @@
+/**
+ * ChannelFactory - Factory pattern for creating channel instances
+ *
+ * Creates appropriate channel implementation based on configuration
+ */
+
+import { Channel, ChannelConfig } from './Channel.js';
+
+/**
+ * Channel constructor type
+ */
+export type ChannelConstructor = new (config: ChannelConfig) => Channel;
+
+/**
+ * Factory for creating channel instances
+ */
+export class ChannelFactory {
+ /**
+ * Registry of channel constructors by type
+ */
+ private static registry: Map = new Map();
+
+ /**
+ * Register a channel implementation
+ * @param type - Channel type identifier
+ * @param constructor - Channel class constructor
+ */
+ static register(type: string, constructor: ChannelConstructor): void {
+ if (this.registry.has(type)) {
+ throw new Error(`Channel type "${type}" is already registered`);
+ }
+ this.registry.set(type, constructor);
+ }
+
+ /**
+ * Unregister a channel implementation
+ * @param type - Channel type identifier
+ */
+ static unregister(type: string): void {
+ this.registry.delete(type);
+ }
+
+ /**
+ * Check if a channel type is registered
+ * @param type - Channel type identifier
+ */
+ static isRegistered(type: string): boolean {
+ return this.registry.has(type);
+ }
+
+ /**
+ * Get all registered channel types
+ */
+ static getRegisteredTypes(): string[] {
+ return Array.from(this.registry.keys());
+ }
+
+ /**
+ * Create a channel instance
+ * @param config - Channel configuration
+ * @returns Channel instance
+ * @throws Error if channel type is not registered
+ */
+ static create(config: ChannelConfig): Channel {
+ const constructor = this.registry.get(config.type);
+
+ if (!constructor) {
+ throw new Error(
+ `Unknown channel type: "${config.type}". ` +
+ `Available types: ${this.getRegisteredTypes().join(', ')}`
+ );
+ }
+
+ try {
+ return new constructor(config);
+ } catch (error) {
+ throw new Error(
+ `Failed to create channel of type "${config.type}": ${
+ error instanceof Error ? error.message : String(error)
+ }`
+ );
+ }
+ }
+
+ /**
+ * Create multiple channel instances from an array of configurations
+ * @param configs - Array of channel configurations
+ * @returns Array of channel instances
+ */
+ static createMany(configs: ChannelConfig[]): Channel[] {
+ return configs.map((config) => this.create(config));
+ }
+
+ /**
+ * Validate channel configuration
+ * @param config - Channel configuration to validate
+ * @throws Error if configuration is invalid
+ */
+ static validateConfig(config: ChannelConfig): void {
+ if (!config.type) {
+ throw new Error('Channel configuration must include a type');
+ }
+
+ if (!config.token) {
+ throw new Error('Channel configuration must include an authentication token');
+ }
+
+ if (!config.connectionParams || typeof config.connectionParams !== 'object') {
+ throw new Error('Channel configuration must include connectionParams object');
+ }
+
+ if (!this.isRegistered(config.type)) {
+ throw new Error(
+ `Channel type "${config.type}" is not registered. ` +
+ `Available types: ${this.getRegisteredTypes().join(', ')}`
+ );
+ }
+ }
+
+ /**
+ * Create a channel with validation
+ * @param config - Channel configuration
+ * @returns Channel instance
+ * @throws Error if configuration is invalid or creation fails
+ */
+ static createSafe(config: ChannelConfig): Channel {
+ this.validateConfig(config);
+ return this.create(config);
+ }
+
+ /**
+ * Clear all registered channel types (useful for testing)
+ */
+ static clear(): void {
+ this.registry.clear();
+ }
+
+ /**
+ * Get default retry configuration
+ */
+ static getDefaultRetryConfig() {
+ return {
+ enabled: true,
+ maxAttempts: 5,
+ initialDelayMs: 1000,
+ maxDelayMs: 60000,
+ };
+ }
+
+ /**
+ * Get default heartbeat configuration
+ */
+ static getDefaultHeartbeatConfig() {
+ return {
+ enabled: true,
+ intervalMs: 15000,
+ timeoutMs: 30000,
+ };
+ }
+
+ /**
+ * Create a channel configuration with defaults
+ * @param type - Channel type
+ * @param token - Authentication token
+ * @param connectionParams - Connection parameters
+ * @param overrides - Optional configuration overrides
+ */
+ static createConfig(
+ type: 'discord' | 'signalr' | 'redis' | 'relay',
+ token: string,
+ connectionParams: Record,
+ overrides?: Partial
+ ): ChannelConfig {
+ return {
+ type,
+ token,
+ connectionParams,
+ retry: overrides?.retry ?? this.getDefaultRetryConfig(),
+ heartbeat: overrides?.heartbeat ?? this.getDefaultHeartbeatConfig(),
+ ...overrides,
+ };
+ }
+}
+
+/**
+ * Decorator for auto-registering channel implementations
+ * Usage: @RegisterChannel('discord')
+ */
+export function RegisterChannel(type: string) {
+ return function (constructor: T): T {
+ ChannelFactory.register(type, constructor);
+ return constructor;
+ };
+}
diff --git a/packages/mcp-server/src/channels/discord/DiscordChannel.ts b/packages/mcp-server/src/channels/discord/DiscordChannel.ts
new file mode 100644
index 0000000..d47f8d7
--- /dev/null
+++ b/packages/mcp-server/src/channels/discord/DiscordChannel.ts
@@ -0,0 +1,256 @@
+/**
+ * DiscordChannel - Discord.js implementation of Channel interface
+ * Provides Discord-based communication for agent coordination
+ */
+
+import { Client, GatewayIntentBits, TextChannel, Message as DiscordMessage } from 'discord.js';
+import { ChannelAdapter } from '../base/ChannelAdapter.js';
+import type { ChannelConfig } from '../base/Channel.js';
+import type { Message } from '../../protocol/Message.js';
+import { MessageBuilder } from '../../protocol/MessageBuilder.js';
+import { validator } from '../../protocol/MessageValidator.js';
+
+/**
+ * Discord-specific configuration
+ */
+export interface DiscordConfig {
+ guildId: string;
+ channelId: string;
+ botToken: string;
+}
+
+/**
+ * DiscordChannel implementation
+ */
+export class DiscordChannel extends ChannelAdapter {
+ private client: Client;
+ private discordConfig: DiscordConfig;
+ private textChannel?: TextChannel;
+
+ constructor(config: ChannelConfig) {
+ super(config);
+
+ this.discordConfig = config.connectionParams as DiscordConfig;
+
+ // Initialize Discord client
+ this.client = new Client({
+ intents: [
+ GatewayIntentBits.Guilds,
+ GatewayIntentBits.GuildMessages,
+ GatewayIntentBits.MessageContent,
+ ],
+ });
+
+ this.setupEventHandlers();
+ }
+
+ /**
+ * Setup Discord event handlers
+ */
+ private setupEventHandlers(): void {
+ this.client.on('ready', () => {
+ this.logger.info('Discord client ready', {
+ username: this.client.user?.tag,
+ });
+ });
+
+ this.client.on('messageCreate', (message) => {
+ this.handleDiscordMessage(message).catch((error) => {
+ this.handleError(
+ error instanceof Error ? error : new Error(String(error))
+ );
+ });
+ });
+
+ this.client.on('error', (error) => {
+ this.handleError(error);
+ });
+ }
+
+ /**
+ * Handle incoming Discord message
+ */
+ private async handleDiscordMessage(discordMessage: DiscordMessage): Promise {
+ // Ignore messages from bots (except our own for history)
+ if (discordMessage.author.bot && discordMessage.author.id !== this.client.user?.id) {
+ return;
+ }
+
+ // Only process messages from our channel
+ if (discordMessage.channelId !== this.discordConfig.channelId) {
+ return;
+ }
+
+ try {
+ // Parse message content as JSON
+ const content = discordMessage.content;
+ const parsedMessage = JSON.parse(content);
+
+ // Validate message
+ const validationResult = validator.validateFull(parsedMessage);
+ if (!validationResult.valid) {
+ this.logger.warn('Invalid message received', {
+ errors: validator.getErrorSummary(validationResult),
+ });
+ return;
+ }
+
+ // Authenticate message: check if sender has valid token
+ // For Discord, we trust all messages in the channel since channel access is controlled
+ // by Discord's own authentication. The shared token is used for bot authentication.
+ if (!this.authenticateMessage(parsedMessage)) {
+ this.logger.warn('Message authentication failed', {
+ senderId: parsedMessage.senderId,
+ });
+ return;
+ }
+
+ // Handle the message
+ this.handleMessage(parsedMessage as Message);
+ } catch (error) {
+ // If parsing fails, it's likely not a protocol message
+ this.logger.debug('Failed to parse message as protocol message', {
+ content: discordMessage.content,
+ });
+ }
+ }
+
+ /**
+ * Authenticate incoming message
+ * For Discord, we rely on channel access control
+ */
+ private authenticateMessage(message: any): boolean {
+ // Basic validation that message has required fields
+ // Discord channel access itself provides authentication
+ return true;
+ }
+
+ /**
+ * Get authentication headers for Discord connection
+ */
+ protected getAuthToken(): string {
+ return this.discordConfig.botToken;
+ }
+
+ /**
+ * Connect to Discord
+ */
+ protected async doConnect(): Promise {
+ // Login to Discord
+ await this.client.login(this.discordConfig.botToken);
+
+ // Wait for client to be ready
+ await new Promise((resolve, reject) => {
+ const timeout = setTimeout(() => {
+ reject(new Error('Discord connection timeout'));
+ }, 30000);
+
+ this.client.once('ready', () => {
+ clearTimeout(timeout);
+ resolve();
+ });
+ });
+
+ // Get the text channel
+ const channel = await this.client.channels.fetch(this.discordConfig.channelId);
+ if (!channel || !channel.isTextBased()) {
+ throw new Error('Channel not found or is not a text channel');
+ }
+
+ this.textChannel = channel as TextChannel;
+ this.logger.info('Connected to Discord channel', {
+ guildId: this.discordConfig.guildId,
+ channelId: this.discordConfig.channelId,
+ });
+ }
+
+ /**
+ * Disconnect from Discord
+ */
+ protected async doDisconnect(): Promise {
+ this.client.destroy();
+ this.textChannel = undefined;
+ }
+
+ /**
+ * Send message to Discord
+ */
+ protected async doSendMessage(message: Message): Promise {
+ if (!this.textChannel) {
+ throw new Error('Not connected to Discord channel');
+ }
+
+ // Serialize message to JSON
+ const content = JSON.stringify(message);
+
+ // Discord has a 2000 character limit
+ if (content.length > 2000) {
+ throw new Error(`Message too long: ${content.length} characters (max 2000)`);
+ }
+
+ // Send message
+ await this.textChannel.send(content);
+ }
+
+ /**
+ * Ping Discord
+ */
+ protected async doPing(): Promise {
+ // Check if client is ready
+ if (!this.client.isReady()) {
+ throw new Error('Discord client not ready');
+ }
+
+ // Discord doesn't have a native ping, so we'll just check the websocket
+ const ping = this.client.ws.ping;
+ if (ping === -1) {
+ throw new Error('Discord websocket not connected');
+ }
+ }
+
+ /**
+ * Get message history from Discord
+ */
+ async getHistory(limit: number = 50, before?: Date): Promise {
+ if (!this.textChannel) {
+ return [];
+ }
+
+ try {
+ const options: any = { limit };
+ if (before) {
+ // Discord uses snowflake IDs, we need to convert timestamp
+ // This is an approximation
+ options.before = String(before.getTime());
+ }
+
+ const messages = await this.textChannel.messages.fetch(options);
+ const parsedMessages: Message[] = [];
+
+ for (const [_, discordMessage] of messages) {
+ if (discordMessage.author.bot) {
+ try {
+ const parsed = JSON.parse(discordMessage.content);
+ const validationResult = validator.validate(parsed);
+ if (validationResult.valid) {
+ parsedMessages.push(parsed as Message);
+ }
+ } catch {
+ // Skip invalid messages
+ }
+ }
+ }
+
+ return parsedMessages;
+ } catch (error) {
+ this.logger.error('Failed to fetch message history', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ return [];
+ }
+ }
+}
+
+// Register with factory
+import { ChannelFactory } from '../base/ChannelFactory.js';
+ChannelFactory.register('discord', DiscordChannel);
diff --git a/packages/mcp-server/src/channels/redis/RedisChannel.ts b/packages/mcp-server/src/channels/redis/RedisChannel.ts
new file mode 100644
index 0000000..e3e73e9
--- /dev/null
+++ b/packages/mcp-server/src/channels/redis/RedisChannel.ts
@@ -0,0 +1,342 @@
+/**
+ * RedisChannel - Redis pub/sub implementation of Channel interface
+ * Provides Redis-based message queue for agent coordination
+ */
+
+import Redis, { RedisOptions } from 'ioredis';
+import { ChannelAdapter } from '../base/ChannelAdapter.js';
+import type { ChannelConfig } from '../base/Channel.js';
+import type { Message } from '../../protocol/Message.js';
+import { validator } from '../../protocol/MessageValidator.js';
+
+/**
+ * Redis-specific configuration
+ */
+export interface RedisConfig {
+ host: string;
+ port: number;
+ password?: string;
+ db?: number;
+ keyPrefix?: string;
+ tls?: boolean;
+}
+
+/**
+ * RedisChannel implementation
+ */
+export class RedisChannel extends ChannelAdapter {
+ private publisher: Redis;
+ private subscriber: Redis;
+ private redisConfig: RedisConfig;
+ private channelName: string;
+
+ constructor(config: ChannelConfig) {
+ super(config);
+
+ this.redisConfig = config.connectionParams as RedisConfig;
+ this.channelName = `${this.redisConfig.keyPrefix || 'coorchat:'}channel`;
+
+ // Create Redis options with authentication
+ const redisOptions: RedisOptions = {
+ host: this.redisConfig.host,
+ port: this.redisConfig.port,
+ password: this.redisConfig.password || this.config.token,
+ db: this.redisConfig.db || 0,
+ retryStrategy: (times) => {
+ // Exponential backoff
+ return Math.min(times * 1000, 30000);
+ },
+ };
+
+ // Enable TLS for secure connections
+ if (this.redisConfig.tls) {
+ redisOptions.tls = {
+ // Reject unauthorized certificates in production
+ rejectUnauthorized: process.env.NODE_ENV === 'production',
+ // Additional TLS options can be configured here:
+ // - ca: Certificate Authority certificates
+ // - cert: Client certificate
+ // - key: Client private key
+ };
+ this.logger.info('Redis TLS enabled', {
+ host: this.redisConfig.host,
+ rejectUnauthorized: redisOptions.tls.rejectUnauthorized,
+ });
+ } else {
+ this.logger.warn('Redis TLS not enabled - connection may be insecure', {
+ host: this.redisConfig.host,
+ });
+ }
+
+ // Create separate connections for pub and sub
+ this.publisher = new Redis(redisOptions);
+ this.subscriber = new Redis(redisOptions);
+
+ this.setupEventHandlers();
+ }
+
+ /**
+ * Setup Redis event handlers
+ */
+ private setupEventHandlers(): void {
+ // Publisher events
+ this.publisher.on('error', (error) => {
+ this.logger.error('Redis publisher error', { error });
+ this.handleError(error);
+ });
+
+ this.publisher.on('connect', () => {
+ this.logger.debug('Redis publisher connected');
+ });
+
+ // Subscriber events
+ this.subscriber.on('error', (error) => {
+ this.logger.error('Redis subscriber error', { error });
+ this.handleError(error);
+ });
+
+ this.subscriber.on('connect', () => {
+ this.logger.debug('Redis subscriber connected');
+ });
+
+ this.subscriber.on('message', (channel, message) => {
+ if (channel === this.channelName) {
+ this.handleRedisMessage(message).catch((error) => {
+ this.handleError(
+ error instanceof Error ? error : new Error(String(error))
+ );
+ });
+ }
+ });
+
+ this.subscriber.on('subscribe', (channel, count) => {
+ this.logger.info('Subscribed to Redis channel', { channel, count });
+ });
+ }
+
+ /**
+ * Handle incoming Redis message
+ */
+ private async handleRedisMessage(messageStr: string): Promise {
+ try {
+ // Parse message
+ const parsedMessage = JSON.parse(messageStr);
+
+ // Verify authentication
+ if (!this.verifyAuthSignature(parsedMessage)) {
+ this.logger.warn('Message authentication failed', {
+ senderId: parsedMessage.senderId,
+ });
+ return;
+ }
+
+ // Validate message
+ const validationResult = validator.validateFull(parsedMessage);
+ if (!validationResult.valid) {
+ this.logger.warn('Invalid message received', {
+ errors: validator.getErrorSummary(validationResult),
+ });
+ return;
+ }
+
+ // Handle the message
+ this.handleMessage(parsedMessage as Message);
+ } catch (error) {
+ this.logger.error('Failed to parse Redis message', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Connect to Redis
+ */
+ protected async doConnect(): Promise {
+ // Wait for both connections to be ready
+ await Promise.all([
+ this.waitForConnection(this.publisher),
+ this.waitForConnection(this.subscriber),
+ ]);
+
+ // Subscribe to channel
+ await this.subscriber.subscribe(this.channelName);
+
+ this.logger.info('Connected to Redis', {
+ host: this.redisConfig.host,
+ port: this.redisConfig.port,
+ channel: this.channelName,
+ });
+ }
+
+ /**
+ * Wait for Redis connection
+ */
+ private async waitForConnection(redis: Redis): Promise {
+ if (redis.status === 'ready') {
+ return;
+ }
+
+ return new Promise((resolve, reject) => {
+ const timeout = setTimeout(() => {
+ reject(new Error('Redis connection timeout'));
+ }, 10000);
+
+ redis.once('ready', () => {
+ clearTimeout(timeout);
+ resolve();
+ });
+
+ redis.once('error', (error) => {
+ clearTimeout(timeout);
+ reject(error);
+ });
+ });
+ }
+
+ /**
+ * Disconnect from Redis
+ */
+ protected async doDisconnect(): Promise {
+ await this.subscriber.unsubscribe(this.channelName);
+ await Promise.all([this.publisher.quit(), this.subscriber.quit()]);
+ }
+
+ /**
+ * Send message via Redis pub/sub
+ */
+ protected async doSendMessage(message: Message): Promise {
+ // Add authentication metadata to message
+ const authenticatedMessage = {
+ ...message,
+ _auth: this.createAuthSignature(message),
+ };
+
+ // Serialize message to JSON
+ const messageStr = JSON.stringify(authenticatedMessage);
+
+ // Publish to channel
+ const subscriberCount = await this.publisher.publish(
+ this.channelName,
+ messageStr
+ );
+
+ this.logger.debug('Message published to Redis', {
+ subscriberCount,
+ messageType: message.messageType,
+ });
+ }
+
+ /**
+ * Create authentication signature for message
+ */
+ private createAuthSignature(message: Message): string {
+ const { createHmac } = require('crypto');
+ const hmac = createHmac('sha256', this.config.token);
+ hmac.update(JSON.stringify({
+ messageType: message.messageType,
+ senderId: message.senderId,
+ timestamp: message.timestamp,
+ }));
+ return hmac.digest('hex');
+ }
+
+ /**
+ * Verify message authentication
+ */
+ private verifyAuthSignature(message: any): boolean {
+ if (!message._auth) {
+ return false;
+ }
+
+ const providedSignature = message._auth;
+ delete message._auth;
+
+ const expectedSignature = this.createAuthSignature(message);
+ return this.verifyToken(providedSignature) || providedSignature === expectedSignature;
+ }
+
+ /**
+ * Ping Redis connection
+ */
+ protected async doPing(): Promise {
+ const result = await this.publisher.ping();
+ if (result !== 'PONG') {
+ throw new Error('Redis ping failed');
+ }
+ }
+
+ /**
+ * Get message history from Redis
+ * Note: Redis pub/sub doesn't persist messages by default
+ * This would require additional Redis Streams or List storage
+ */
+ async getHistory(limit: number = 50, before?: Date): Promise {
+ try {
+ // Try to get messages from a Redis list (if implemented)
+ const historyKey = `${this.redisConfig.keyPrefix || 'coorchat:'}history`;
+ const messages = await this.publisher.lrange(historyKey, 0, limit - 1);
+
+ const parsedMessages: Message[] = [];
+ for (const messageStr of messages) {
+ try {
+ const parsed = JSON.parse(messageStr);
+ const validationResult = validator.validate(parsed);
+ if (validationResult.valid) {
+ const message = parsed as Message;
+
+ // Filter by timestamp if before is specified
+ if (!before || new Date(message.timestamp) < before) {
+ parsedMessages.push(message);
+ }
+ }
+ } catch {
+ // Skip invalid messages
+ }
+ }
+
+ return parsedMessages;
+ } catch (error) {
+ this.logger.debug('Message history not available', {
+ error: error instanceof Error ? error.message : String(error),
+ });
+ return [];
+ }
+ }
+
+ /**
+ * Store message in history (if history persistence is enabled)
+ */
+ private async storeInHistory(message: Message): Promise {
+ try {
+ const historyKey = `${this.redisConfig.keyPrefix || 'coorchat:'}history`;
+ const messageStr = JSON.stringify(message);
+
+ // Add to list (newest first)
+ await this.publisher.lpush(historyKey, messageStr);
+
+ // Trim to keep only recent messages (e.g., 1000 messages)
+ await this.publisher.ltrim(historyKey, 0, 999);
+ } catch (error) {
+ this.logger.debug('Failed to store message in history', {
+ error: error instanceof Error ? error.message : String(error),
+ });
+ }
+ }
+
+ /**
+ * Get Redis connection status
+ */
+ getRedisStatus(): {
+ publisher: string;
+ subscriber: string;
+ } {
+ return {
+ publisher: this.publisher.status,
+ subscriber: this.subscriber.status,
+ };
+ }
+}
+
+// Register with factory
+import { ChannelFactory } from '../base/ChannelFactory.js';
+ChannelFactory.register('redis', RedisChannel);
diff --git a/packages/mcp-server/src/channels/signalr/SignalRChannel.ts b/packages/mcp-server/src/channels/signalr/SignalRChannel.ts
new file mode 100644
index 0000000..d5b3086
--- /dev/null
+++ b/packages/mcp-server/src/channels/signalr/SignalRChannel.ts
@@ -0,0 +1,235 @@
+/**
+ * SignalRChannel - SignalR implementation of Channel interface
+ * Provides SignalR-based real-time communication for agent coordination
+ */
+
+import * as signalR from '@microsoft/signalr';
+import { ChannelAdapter } from '../base/ChannelAdapter.js';
+import type { ChannelConfig } from '../base/Channel.js';
+import type { Message } from '../../protocol/Message.js';
+import { validator } from '../../protocol/MessageValidator.js';
+
+/**
+ * SignalR-specific configuration
+ */
+export interface SignalRConfig {
+ hubUrl: string;
+ accessToken: string;
+}
+
+/**
+ * SignalRChannel implementation
+ */
+export class SignalRChannel extends ChannelAdapter {
+ private connection: signalR.HubConnection;
+ private signalRConfig: SignalRConfig;
+
+ constructor(config: ChannelConfig) {
+ super(config);
+
+ this.signalRConfig = config.connectionParams as SignalRConfig;
+
+ // Validate TLS/HTTPS usage for security
+ if (!this.signalRConfig.hubUrl.startsWith('https://')) {
+ this.logger.warn('SignalR hub URL is not using HTTPS - connection may be insecure', {
+ hubUrl: this.signalRConfig.hubUrl,
+ });
+ }
+
+ // Initialize SignalR connection with authentication and TLS
+ this.connection = new signalR.HubConnectionBuilder()
+ .withUrl(this.signalRConfig.hubUrl, {
+ accessTokenFactory: () => this.getAuthToken(),
+ // SignalR will use HTTPS automatically if URL starts with https://
+ // Additional transport options can be configured here
+ skipNegotiation: false,
+ transport: signalR.HttpTransportType.WebSockets | signalR.HttpTransportType.ServerSentEvents,
+ })
+ .withAutomaticReconnect({
+ nextRetryDelayInMilliseconds: (retryContext) => {
+ // Exponential backoff
+ return Math.min(1000 * Math.pow(2, retryContext.previousRetryCount), 30000);
+ },
+ })
+ .configureLogging(signalR.LogLevel.Information)
+ .build();
+
+ this.setupEventHandlers();
+ }
+
+ /**
+ * Setup SignalR event handlers
+ */
+ private setupEventHandlers(): void {
+ // Handle incoming messages
+ this.connection.on('ReceiveMessage', (messageJson: string) => {
+ this.handleSignalRMessage(messageJson).catch((error) => {
+ this.handleError(
+ error instanceof Error ? error : new Error(String(error))
+ );
+ });
+ });
+
+ // Handle reconnecting
+ this.connection.onreconnecting((error) => {
+ this.logger.warn('SignalR reconnecting', {
+ error: error?.message,
+ });
+ this.setStatus('reconnecting');
+ });
+
+ // Handle reconnected
+ this.connection.onreconnected((connectionId) => {
+ this.logger.info('SignalR reconnected', { connectionId });
+ this.setStatus('connected');
+ });
+
+ // Handle close
+ this.connection.onclose((error) => {
+ this.logger.warn('SignalR connection closed', {
+ error: error?.message,
+ });
+ this.setStatus('disconnected');
+ });
+ }
+
+ /**
+ * Handle incoming SignalR message
+ */
+ private async handleSignalRMessage(messageJson: string): Promise {
+ try {
+ // Parse message
+ const parsedMessage = JSON.parse(messageJson);
+
+ // Validate message
+ const validationResult = validator.validateFull(parsedMessage);
+ if (!validationResult.valid) {
+ this.logger.warn('Invalid message received', {
+ errors: validator.getErrorSummary(validationResult),
+ });
+ return;
+ }
+
+ // Handle the message
+ this.handleMessage(parsedMessage as Message);
+ } catch (error) {
+ this.logger.error('Failed to parse SignalR message', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Connect to SignalR hub
+ */
+ protected async doConnect(): Promise {
+ try {
+ await this.connection.start();
+ this.logger.info('Connected to SignalR hub', {
+ hubUrl: this.signalRConfig.hubUrl,
+ connectionId: this.connection.connectionId,
+ });
+ } catch (error) {
+ throw new Error(
+ `Failed to connect to SignalR hub: ${
+ error instanceof Error ? error.message : String(error)
+ }`
+ );
+ }
+ }
+
+ /**
+ * Disconnect from SignalR hub
+ */
+ protected async doDisconnect(): Promise {
+ await this.connection.stop();
+ }
+
+ /**
+ * Send message via SignalR
+ */
+ protected async doSendMessage(message: Message): Promise {
+ if (this.connection.state !== signalR.HubConnectionState.Connected) {
+ throw new Error(`Cannot send message: connection state is ${this.connection.state}`);
+ }
+
+ // Serialize message to JSON
+ const messageJson = JSON.stringify(message);
+
+ // Send via SignalR hub method
+ await this.connection.invoke('SendMessage', messageJson);
+ }
+
+ /**
+ * Ping SignalR connection
+ */
+ protected async doPing(): Promise {
+ if (this.connection.state !== signalR.HubConnectionState.Connected) {
+ throw new Error('SignalR connection not connected');
+ }
+
+ // Send a ping message to the hub
+ try {
+ await this.connection.invoke('Ping');
+ } catch (error) {
+ throw new Error(
+ `SignalR ping failed: ${
+ error instanceof Error ? error.message : String(error)
+ }`
+ );
+ }
+ }
+
+ /**
+ * Get message history (if supported by hub)
+ */
+ async getHistory(limit: number = 50, before?: Date): Promise {
+ try {
+ // Request history from hub
+ const messagesJson = await this.connection.invoke(
+ 'GetMessageHistory',
+ limit,
+ before?.toISOString()
+ );
+
+ const messages: Message[] = [];
+ for (const messageJson of messagesJson) {
+ try {
+ const parsed = JSON.parse(messageJson);
+ const validationResult = validator.validate(parsed);
+ if (validationResult.valid) {
+ messages.push(parsed as Message);
+ }
+ } catch {
+ // Skip invalid messages
+ }
+ }
+
+ return messages;
+ } catch (error) {
+ this.logger.debug('Message history not supported by SignalR hub', {
+ error: error instanceof Error ? error.message : String(error),
+ });
+ return [];
+ }
+ }
+
+ /**
+ * Get connection state
+ */
+ getConnectionState(): signalR.HubConnectionState {
+ return this.connection.state;
+ }
+
+ /**
+ * Get authentication token for SignalR
+ */
+ protected getAuthToken(): string {
+ // Use the shared channel token for authentication
+ return this.config.token;
+ }
+}
+
+// Register with factory
+import { ChannelFactory } from '../base/ChannelFactory.js';
+ChannelFactory.register('signalr', SignalRChannel);
diff --git a/packages/mcp-server/src/cli/index.ts b/packages/mcp-server/src/cli/index.ts
new file mode 100644
index 0000000..e0f416c
--- /dev/null
+++ b/packages/mcp-server/src/cli/index.ts
@@ -0,0 +1,381 @@
+#!/usr/bin/env node
+/**
+ * CoorChat CLI - Command-line interface for managing agents
+ */
+
+import { Command } from 'commander';
+import * as dotenv from 'dotenv';
+import { TokenGenerator } from '../config/TokenGenerator.js';
+import { ChannelFactory } from '../channels/base/ChannelFactory.js';
+import { AgentRegistry } from '../agents/AgentRegistry.js';
+import { RoleManager } from '../agents/RoleManager.js';
+import { TaskQueue } from '../tasks/TaskQueue.js';
+import type { ChannelConfig } from '../channels/base/Channel.js';
+import type { Agent } from '../agents/Agent.js';
+
+// Load environment variables
+dotenv.config();
+
+const program = new Command();
+
+program
+ .name('coorchat')
+ .description('CoorChat CLI - Multi-Agent Coordination System')
+ .version('1.0.0');
+
+// Token commands
+const tokenCmd = program.command('token').description('Token management commands');
+
+tokenCmd
+ .command('generate')
+ .description('Generate a secure token')
+ .option('-t, --type ', 'Token type (channel, api, webhook)', 'channel')
+ .option('-c, --count ', 'Number of tokens to generate', '1')
+ .action((options) => {
+ const count = parseInt(options.count);
+ const tokens: string[] = [];
+
+ for (let i = 0; i < count; i++) {
+ let token: string;
+ switch (options.type) {
+ case 'channel':
+ token = TokenGenerator.generateChannelToken();
+ break;
+ case 'api':
+ token = TokenGenerator.generateAPIToken();
+ break;
+ case 'webhook':
+ token = TokenGenerator.generateWebhookSecret();
+ break;
+ default:
+ console.error(`Invalid token type: ${options.type}`);
+ process.exit(1);
+ }
+ tokens.push(token);
+ }
+
+ console.log('Generated tokens:');
+ tokens.forEach((token, index) => {
+ console.log(`${index + 1}. ${token}`);
+ });
+
+ if (count === 1) {
+ console.log('\nAdd to your .env file:');
+ console.log(`SHARED_TOKEN=${tokens[0]}`);
+ }
+ });
+
+tokenCmd
+ .command('validate ')
+ .description('Validate token format')
+ .action((token) => {
+ const isValid = TokenGenerator.validateFormat(token);
+ if (isValid) {
+ console.log('β
Token is valid');
+ console.log(`Length: ${token.length} characters`);
+ if (token.startsWith('cct_')) {
+ console.log('Type: Channel Token');
+ } else if (token.startsWith('cca_')) {
+ console.log('Type: API Token');
+ } else {
+ console.log('Type: Generic');
+ }
+ } else {
+ console.log('β Token is invalid');
+ console.log('Requirements:');
+ console.log(' - Minimum 16 characters');
+ console.log(' - Alphanumeric with _ and -');
+ console.log(' - No whitespace');
+ }
+ });
+
+tokenCmd
+ .command('hash ')
+ .description('Hash a token (SHA-256)')
+ .action((token) => {
+ const hash = TokenGenerator.hash(token);
+ console.log('Token hash:');
+ console.log(hash);
+ });
+
+// Agent commands
+const agentCmd = program.command('agent').description('Agent management commands');
+
+agentCmd
+ .command('start')
+ .description('Start an agent')
+ .option('-i, --id ', 'Agent ID')
+ .option('-r, --role ', 'Agent role', 'developer')
+ .option('-c, --channel ', 'Channel type', process.env.CHANNEL_TYPE || 'redis')
+ .action(async (options) => {
+ const agentId = options.id || `agent-${Date.now()}`;
+ const role = options.role;
+
+ console.log(`π€ Starting agent: ${agentId}`);
+ console.log(` Role: ${role}`);
+ console.log(` Channel: ${options.channel}`);
+ console.log('');
+
+ try {
+ // Create channel config
+ const config: ChannelConfig = {
+ type: options.channel,
+ token: process.env.SHARED_TOKEN || '',
+ connectionParams: getConnectionParams(options.channel),
+ };
+
+ // Validate token
+ if (!config.token || config.token.length < 16) {
+ console.error('β Invalid or missing SHARED_TOKEN in environment');
+ console.error('Run: coorchat token generate');
+ process.exit(1);
+ }
+
+ // Create channel
+ const channel = ChannelFactory.create(config);
+ await channel.connect();
+
+ console.log('β
Connected to channel');
+
+ // Register agent
+ const agent: Agent = {
+ id: agentId,
+ role,
+ capabilities: [],
+ status: 'active',
+ metadata: {
+ platform: process.platform,
+ environment: 'CLI',
+ },
+ };
+
+ // Listen for messages
+ channel.onMessage((message) => {
+ console.log(`π¨ [${message.messageType}] from ${message.senderId}`);
+ if (message.payload) {
+ console.log(` ${JSON.stringify(message.payload, null, 2)}`);
+ }
+ });
+
+ // Keep alive
+ console.log('');
+ console.log('Agent is running. Press Ctrl+C to stop.');
+ console.log('');
+
+ // Graceful shutdown
+ process.on('SIGINT', async () => {
+ console.log('\n\nπ Shutting down...');
+ await channel.disconnect();
+ console.log('β
Disconnected');
+ process.exit(0);
+ });
+
+ // Keep process alive
+ await new Promise(() => {});
+ } catch (error) {
+ console.error('β Failed to start agent:', error);
+ process.exit(1);
+ }
+ });
+
+agentCmd
+ .command('list')
+ .description('List active agents')
+ .action(async () => {
+ // This would need a shared state store (Redis, etc.)
+ console.log('π Active Agents:');
+ console.log('(This feature requires a shared state store)');
+ });
+
+// Role commands
+const roleCmd = program.command('role').description('Role management commands');
+
+roleCmd
+ .command('list')
+ .description('List available roles')
+ .action(() => {
+ const roleManager = new RoleManager();
+ const roles = roleManager.getPredefinedRoles();
+
+ console.log('π Available Roles:\n');
+ roles.forEach((role) => {
+ console.log(`${role.name}:`);
+ console.log(` Description: ${role.description}`);
+ console.log(` Capabilities: ${role.defaultCapabilities.join(', ')}`);
+ console.log('');
+ });
+ });
+
+roleCmd
+ .command('suggest <...capabilities>')
+ .description('Suggest roles based on capabilities')
+ .action((capabilities) => {
+ const roleManager = new RoleManager();
+ const suggestions = roleManager.suggestRole(capabilities);
+
+ if (suggestions.length > 0) {
+ console.log('π‘ Suggested Roles:\n');
+ suggestions.forEach((role, index) => {
+ console.log(`${index + 1}. ${role.name}`);
+ console.log(` ${role.description}`);
+ console.log('');
+ });
+ } else {
+ console.log('No matching roles found');
+ }
+ });
+
+// Config commands
+const configCmd = program.command('config').description('Configuration commands');
+
+configCmd
+ .command('show')
+ .description('Show current configuration')
+ .action(() => {
+ console.log('βοΈ Current Configuration:\n');
+ console.log(`Channel Type: ${process.env.CHANNEL_TYPE || '(not set)'}`);
+ console.log(`Agent ID: ${process.env.AGENT_ID || '(not set)'}`);
+ console.log(`Agent Role: ${process.env.AGENT_ROLE || '(not set)'}`);
+ console.log(`Shared Token: ${process.env.SHARED_TOKEN ? '***' + process.env.SHARED_TOKEN.slice(-8) : '(not set)'}`);
+ console.log('');
+
+ if (process.env.CHANNEL_TYPE === 'redis') {
+ console.log('Redis Configuration:');
+ console.log(` Host: ${process.env.REDIS_HOST || 'localhost'}`);
+ console.log(` Port: ${process.env.REDIS_PORT || '6379'}`);
+ console.log(` TLS: ${process.env.REDIS_TLS || 'false'}`);
+ } else if (process.env.CHANNEL_TYPE === 'discord') {
+ console.log('Discord Configuration:');
+ console.log(` Bot Token: ${process.env.DISCORD_BOT_TOKEN ? '***' : '(not set)'}`);
+ console.log(` Channel ID: ${process.env.DISCORD_CHANNEL_ID || '(not set)'}`);
+ } else if (process.env.CHANNEL_TYPE === 'signalr') {
+ console.log('SignalR Configuration:');
+ console.log(` Hub URL: ${process.env.SIGNALR_HUB_URL || '(not set)'}`);
+ }
+
+ console.log('');
+
+ if (process.env.GITHUB_TOKEN) {
+ console.log('GitHub Integration:');
+ console.log(` Token: ***${process.env.GITHUB_TOKEN.slice(-8)}`);
+ console.log(` Owner: ${process.env.GITHUB_OWNER || '(not set)'}`);
+ console.log(` Repo: ${process.env.GITHUB_REPO || '(not set)'}`);
+ console.log('');
+ }
+ });
+
+configCmd
+ .command('init')
+ .description('Initialize configuration file')
+ .option('-c, --channel ', 'Channel type (redis, discord, signalr)', 'redis')
+ .action((options) => {
+ const token = TokenGenerator.generateChannelToken();
+
+ const envContent = `# CoorChat Configuration
+# Generated: ${new Date().toISOString()}
+
+# Shared authentication token (use same token for all agents)
+SHARED_TOKEN=${token}
+
+# Channel configuration
+CHANNEL_TYPE=${options.channel}
+${options.channel === 'redis' ? `REDIS_HOST=localhost
+REDIS_PORT=6379
+REDIS_TLS=false` : ''}
+${options.channel === 'discord' ? `DISCORD_BOT_TOKEN=your_bot_token_here
+DISCORD_CHANNEL_ID=your_channel_id_here` : ''}
+${options.channel === 'signalr' ? `SIGNALR_HUB_URL=https://localhost:5001/agentHub` : ''}
+
+# Agent configuration
+AGENT_ID=agent-${Date.now()}
+AGENT_ROLE=developer
+
+# Optional: GitHub integration
+# GITHUB_TOKEN=ghp_your_token_here
+# GITHUB_OWNER=your-org
+# GITHUB_REPO=your-repo
+
+# Logging
+LOG_LEVEL=info
+`;
+
+ console.log(envContent);
+ console.log('πΎ Save this to .env file in packages/mcp-server/');
+ });
+
+// Monitor command
+program
+ .command('monitor')
+ .description('Monitor agent coordination activity')
+ .option('-c, --channel ', 'Channel type', process.env.CHANNEL_TYPE || 'redis')
+ .action(async (options) => {
+ console.log('ποΈ CoorChat Monitor\n');
+ console.log('Listening for agent activity...\n');
+
+ try {
+ const config: ChannelConfig = {
+ type: options.channel,
+ token: process.env.SHARED_TOKEN || '',
+ connectionParams: getConnectionParams(options.channel),
+ };
+
+ const channel = ChannelFactory.create(config);
+ await channel.connect();
+
+ console.log(`β
Connected to ${options.channel} channel\n`);
+
+ channel.onMessage((message) => {
+ const timestamp = new Date(message.timestamp).toLocaleTimeString();
+ console.log(`[${timestamp}] ${message.messageType}`);
+ console.log(` From: ${message.senderId}`);
+ if (message.recipientId) {
+ console.log(` To: ${message.recipientId}`);
+ }
+ if (message.payload) {
+ console.log(` Payload: ${JSON.stringify(message.payload, null, 2)}`);
+ }
+ console.log('');
+ });
+
+ // Graceful shutdown
+ process.on('SIGINT', async () => {
+ console.log('\n\nπ Stopping monitor...');
+ await channel.disconnect();
+ console.log('β
Disconnected');
+ process.exit(0);
+ });
+
+ // Keep alive
+ await new Promise(() => {});
+ } catch (error) {
+ console.error('β Failed to start monitor:', error);
+ process.exit(1);
+ }
+ });
+
+// Helper function to get connection params
+function getConnectionParams(channelType: string): any {
+ switch (channelType) {
+ case 'redis':
+ return {
+ host: process.env.REDIS_HOST || 'localhost',
+ port: parseInt(process.env.REDIS_PORT || '6379'),
+ password: process.env.REDIS_PASSWORD,
+ tls: process.env.REDIS_TLS === 'true',
+ };
+ case 'discord':
+ return {
+ botToken: process.env.DISCORD_BOT_TOKEN,
+ channelId: process.env.DISCORD_CHANNEL_ID,
+ };
+ case 'signalr':
+ return {
+ hubUrl: process.env.SIGNALR_HUB_URL || 'https://localhost:5001/agentHub',
+ };
+ default:
+ return {};
+ }
+}
+
+// Parse CLI arguments
+program.parse();
diff --git a/packages/mcp-server/src/config/ConfigLoader.ts b/packages/mcp-server/src/config/ConfigLoader.ts
new file mode 100644
index 0000000..646effd
--- /dev/null
+++ b/packages/mcp-server/src/config/ConfigLoader.ts
@@ -0,0 +1,242 @@
+/**
+ * ConfigLoader - Load configuration from JSON/YAML files with environment variable substitution
+ */
+
+import { readFileSync, existsSync } from 'fs';
+import { parse as parseYAML } from 'yaml';
+import { EnvironmentResolver } from './EnvironmentResolver.js';
+
+/**
+ * Configuration file format
+ */
+export type ConfigFormat = 'json' | 'yaml' | 'yml';
+
+/**
+ * Load options
+ */
+export interface LoadOptions {
+ /** Whether to resolve environment variables */
+ resolveEnv?: boolean;
+
+ /** Whether to throw on missing file */
+ throwOnMissing?: boolean;
+
+ /** Default config to merge with loaded config */
+ defaults?: Record;
+
+ /** Encoding for reading files */
+ encoding?: BufferEncoding;
+}
+
+/**
+ * ConfigLoader class for loading configuration files
+ */
+export class ConfigLoader {
+ private resolver: EnvironmentResolver;
+
+ constructor() {
+ this.resolver = new EnvironmentResolver();
+ }
+
+ /**
+ * Load configuration from a file
+ * @param filePath - Path to configuration file
+ * @param options - Load options
+ * @returns Parsed configuration object
+ */
+ load>(
+ filePath: string,
+ options: LoadOptions = {}
+ ): T {
+ const {
+ resolveEnv = true,
+ throwOnMissing = true,
+ defaults = {},
+ encoding = 'utf-8',
+ } = options;
+
+ // Check if file exists
+ if (!existsSync(filePath)) {
+ if (throwOnMissing) {
+ throw new Error(`Configuration file not found: ${filePath}`);
+ }
+ return defaults as T;
+ }
+
+ // Read file content
+ let content: string;
+ try {
+ content = readFileSync(filePath, encoding);
+ } catch (error) {
+ throw new Error(
+ `Failed to read configuration file: ${
+ error instanceof Error ? error.message : String(error)
+ }`
+ );
+ }
+
+ // Resolve environment variables if requested
+ if (resolveEnv) {
+ content = this.resolver.resolve(content);
+ }
+
+ // Determine format from file extension
+ const format = this.detectFormat(filePath);
+
+ // Parse content based on format
+ let config: Record;
+ try {
+ config = this.parse(content, format);
+ } catch (error) {
+ throw new Error(
+ `Failed to parse configuration file (${format}): ${
+ error instanceof Error ? error.message : String(error)
+ }`
+ );
+ }
+
+ // Merge with defaults
+ return this.mergeDeep(defaults, config) as T;
+ }
+
+ /**
+ * Load configuration from multiple files (cascade)
+ * Later files override earlier files
+ */
+ loadMultiple>(
+ filePaths: string[],
+ options: LoadOptions = {}
+ ): T {
+ let merged: Record = options.defaults || {};
+
+ for (const filePath of filePaths) {
+ try {
+ const config = this.load(filePath, {
+ ...options,
+ throwOnMissing: false,
+ defaults: {},
+ });
+ merged = this.mergeDeep(merged, config);
+ } catch (error) {
+ if (options.throwOnMissing) {
+ throw error;
+ }
+ // Skip files that can't be loaded if throwOnMissing is false
+ }
+ }
+
+ return merged as T;
+ }
+
+ /**
+ * Detect configuration format from file extension
+ */
+ private detectFormat(filePath: string): ConfigFormat {
+ const ext = filePath.split('.').pop()?.toLowerCase();
+
+ switch (ext) {
+ case 'json':
+ return 'json';
+ case 'yaml':
+ case 'yml':
+ return 'yaml';
+ default:
+ throw new Error(
+ `Unknown configuration file format: ${ext}. Supported formats: json, yaml, yml`
+ );
+ }
+ }
+
+ /**
+ * Parse configuration content based on format
+ */
+ private parse(content: string, format: ConfigFormat): Record {
+ switch (format) {
+ case 'json':
+ return JSON.parse(content);
+
+ case 'yaml':
+ case 'yml':
+ return parseYAML(content) as Record;
+
+ default:
+ throw new Error(`Unsupported format: ${format}`);
+ }
+ }
+
+ /**
+ * Deep merge two objects
+ * Later object properties override earlier object properties
+ */
+ private mergeDeep(
+ target: Record,
+ source: Record
+ ): Record {
+ const result = { ...target };
+
+ for (const key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ const sourceValue = source[key];
+ const targetValue = result[key];
+
+ if (
+ this.isPlainObject(sourceValue) &&
+ this.isPlainObject(targetValue)
+ ) {
+ result[key] = this.mergeDeep(
+ targetValue as Record,
+ sourceValue as Record
+ );
+ } else {
+ result[key] = sourceValue;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Check if a value is a plain object
+ */
+ private isPlainObject(value: unknown): boolean {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ !Array.isArray(value) &&
+ Object.getPrototypeOf(value) === Object.prototype
+ );
+ }
+
+ /**
+ * Get environment resolver instance
+ */
+ getResolver(): EnvironmentResolver {
+ return this.resolver;
+ }
+}
+
+/**
+ * Singleton config loader instance
+ */
+export const configLoader = new ConfigLoader();
+
+/**
+ * Convenience function to load configuration
+ */
+export function loadConfig>(
+ filePath: string,
+ options?: LoadOptions
+): T {
+ return configLoader.load(filePath, options);
+}
+
+/**
+ * Convenience function to load multiple configuration files
+ */
+export function loadMultipleConfigs>(
+ filePaths: string[],
+ options?: LoadOptions
+): T {
+ return configLoader.loadMultiple(filePaths, options);
+}
diff --git a/packages/mcp-server/src/config/ConfigValidator.ts b/packages/mcp-server/src/config/ConfigValidator.ts
new file mode 100644
index 0000000..9fb2bf9
--- /dev/null
+++ b/packages/mcp-server/src/config/ConfigValidator.ts
@@ -0,0 +1,269 @@
+/**
+ * ConfigValidator - Validate configuration using Zod schemas
+ * Based on specs/001-multi-agent-coordination/plan.md configuration requirements
+ */
+
+import { z } from 'zod';
+
+/**
+ * Retry configuration schema
+ */
+export const RetryConfigSchema = z.object({
+ enabled: z.boolean().default(true),
+ maxAttempts: z.number().int().positive().default(5),
+ initialDelayMs: z.number().int().positive().default(1000),
+ maxDelayMs: z.number().int().positive().default(60000),
+});
+
+/**
+ * Heartbeat configuration schema
+ */
+export const HeartbeatConfigSchema = z.object({
+ enabled: z.boolean().default(true),
+ intervalMs: z.number().int().positive().default(15000),
+ timeoutMs: z.number().int().positive().default(30000),
+});
+
+/**
+ * Discord channel configuration schema
+ */
+export const DiscordConfigSchema = z.object({
+ guildId: z.string().min(1),
+ channelId: z.string().min(1),
+ botToken: z.string().min(1),
+});
+
+/**
+ * SignalR channel configuration schema
+ */
+export const SignalRConfigSchema = z.object({
+ hubUrl: z.string().url(),
+ accessToken: z.string().min(1),
+});
+
+/**
+ * Redis channel configuration schema
+ */
+export const RedisConfigSchema = z.object({
+ host: z.string().min(1).default('localhost'),
+ port: z.number().int().positive().default(6379),
+ password: z.string().optional(),
+ db: z.number().int().min(0).default(0),
+ keyPrefix: z.string().default('coorchat:'),
+ tls: z.boolean().default(false),
+});
+
+/**
+ * Relay channel configuration schema
+ */
+export const RelayConfigSchema = z.object({
+ serverUrl: z.string().url(),
+ accessToken: z.string().min(1),
+ channelId: z.string().uuid(),
+});
+
+/**
+ * Channel configuration schema (discriminated union)
+ */
+export const ChannelConfigSchema = z.discriminatedUnion('type', [
+ z.object({
+ type: z.literal('discord'),
+ token: z.string().min(1),
+ connectionParams: DiscordConfigSchema,
+ retry: RetryConfigSchema.optional(),
+ heartbeat: HeartbeatConfigSchema.optional(),
+ }),
+ z.object({
+ type: z.literal('signalr'),
+ token: z.string().min(1),
+ connectionParams: SignalRConfigSchema,
+ retry: RetryConfigSchema.optional(),
+ heartbeat: HeartbeatConfigSchema.optional(),
+ }),
+ z.object({
+ type: z.literal('redis'),
+ token: z.string().min(1),
+ connectionParams: RedisConfigSchema,
+ retry: RetryConfigSchema.optional(),
+ heartbeat: HeartbeatConfigSchema.optional(),
+ }),
+ z.object({
+ type: z.literal('relay'),
+ token: z.string().min(1),
+ connectionParams: RelayConfigSchema,
+ retry: RetryConfigSchema.optional(),
+ heartbeat: HeartbeatConfigSchema.optional(),
+ }),
+]);
+
+/**
+ * GitHub configuration schema
+ */
+export const GitHubConfigSchema = z.object({
+ token: z.string().min(1),
+ owner: z.string().min(1),
+ repo: z.string().min(1),
+ webhookSecret: z.string().optional(),
+ webhookPort: z.number().int().positive().default(3000),
+ pollingEnabled: z.boolean().default(true),
+ pollingIntervalMs: z.number().int().positive().default(30000),
+});
+
+/**
+ * Agent configuration schema
+ */
+export const AgentConfigSchema = z.object({
+ role: z.string().min(1).max(50),
+ platform: z.enum(['Linux', 'macOS', 'Windows']),
+ environment: z.string().min(1).max(100),
+ tools: z.array(z.string()).min(1),
+ languages: z.array(z.string()).optional(),
+ apiAccess: z.array(z.string()).optional(),
+ resourceLimits: z
+ .object({
+ apiQuotaPerHour: z.number().int().nonnegative().optional(),
+ maxConcurrentTasks: z.number().int().min(1).max(10).default(1),
+ rateLimitPerMinute: z.number().int().nonnegative().optional(),
+ memoryLimitMB: z.number().int().nonnegative().optional(),
+ })
+ .optional(),
+});
+
+/**
+ * Logging configuration schema
+ */
+export const LoggingConfigSchema = z.object({
+ level: z.enum(['ERROR', 'WARN', 'INFO', 'DEBUG']).default('INFO'),
+ format: z.enum(['json', 'text']).default('json'),
+ output: z.enum(['console', 'file', 'both']).default('console'),
+ filePath: z.string().optional(),
+});
+
+/**
+ * Main application configuration schema
+ */
+export const AppConfigSchema = z.object({
+ channel: ChannelConfigSchema,
+ github: GitHubConfigSchema.optional(),
+ agent: AgentConfigSchema,
+ logging: LoggingConfigSchema.optional(),
+});
+
+/**
+ * Type exports
+ */
+export type RetryConfig = z.infer;
+export type HeartbeatConfig = z.infer;
+export type DiscordConfig = z.infer;
+export type SignalRConfig = z.infer;
+export type RedisConfig = z.infer;
+export type RelayConfig = z.infer;
+export type ChannelConfig = z.infer;
+export type GitHubConfig = z.infer;
+export type AgentConfig = z.infer;
+export type LoggingConfig = z.infer;
+export type AppConfig = z.infer;
+
+/**
+ * Validation result
+ */
+export interface ValidationResult {
+ success: boolean;
+ data?: T;
+ errors?: z.ZodError;
+}
+
+/**
+ * ConfigValidator class
+ */
+export class ConfigValidator {
+ /**
+ * Validate configuration against a schema
+ */
+ validate(schema: z.ZodSchema, data: unknown): ValidationResult {
+ const result = schema.safeParse(data);
+
+ if (result.success) {
+ return {
+ success: true,
+ data: result.data,
+ };
+ }
+
+ return {
+ success: false,
+ errors: result.error,
+ };
+ }
+
+ /**
+ * Validate and throw on error
+ */
+ validateOrThrow(schema: z.ZodSchema, data: unknown): T {
+ return schema.parse(data);
+ }
+
+ /**
+ * Validate application configuration
+ */
+ validateAppConfig(data: unknown): ValidationResult {
+ return this.validate(AppConfigSchema, data);
+ }
+
+ /**
+ * Validate channel configuration
+ */
+ validateChannelConfig(data: unknown): ValidationResult {
+ return this.validate(ChannelConfigSchema, data);
+ }
+
+ /**
+ * Validate GitHub configuration
+ */
+ validateGitHubConfig(data: unknown): ValidationResult {
+ return this.validate(GitHubConfigSchema, data);
+ }
+
+ /**
+ * Validate agent configuration
+ */
+ validateAgentConfig(data: unknown): ValidationResult {
+ return this.validate(AgentConfigSchema, data);
+ }
+
+ /**
+ * Get formatted error messages
+ */
+ formatErrors(errors: z.ZodError): string[] {
+ return errors.errors.map((err) => {
+ const path = err.path.join('.');
+ return `${path}: ${err.message}`;
+ });
+ }
+
+ /**
+ * Get error summary
+ */
+ getErrorSummary(errors: z.ZodError): string {
+ return this.formatErrors(errors).join('; ');
+ }
+}
+
+/**
+ * Singleton validator instance
+ */
+export const validator = new ConfigValidator();
+
+/**
+ * Convenience function to validate app configuration
+ */
+export function validateAppConfig(data: unknown): AppConfig {
+ return validator.validateOrThrow(AppConfigSchema, data);
+}
+
+/**
+ * Convenience function to validate channel configuration
+ */
+export function validateChannelConfig(data: unknown): ChannelConfig {
+ return validator.validateOrThrow(ChannelConfigSchema, data);
+}
diff --git a/packages/mcp-server/src/config/EnvironmentResolver.ts b/packages/mcp-server/src/config/EnvironmentResolver.ts
new file mode 100644
index 0000000..4809593
--- /dev/null
+++ b/packages/mcp-server/src/config/EnvironmentResolver.ts
@@ -0,0 +1,211 @@
+/**
+ * EnvironmentResolver - Resolve environment variable placeholders in configuration
+ * Supports ${VAR_NAME}, ${VAR_NAME:-default}, and ${VAR_NAME:?error message} syntax
+ */
+
+/**
+ * Environment variable placeholder patterns
+ */
+const ENV_VAR_PATTERN = /\$\{([^}:]+)(?:([:-])([^}]+))?\}/g;
+
+/**
+ * Resolution options
+ */
+export interface ResolverOptions {
+ /** Whether to throw on undefined variables */
+ throwOnUndefined?: boolean;
+
+ /** Custom environment variables (overrides process.env) */
+ env?: Record;
+
+ /** Whether to resolve recursively */
+ recursive?: boolean;
+
+ /** Maximum recursion depth (prevents infinite loops) */
+ maxDepth?: number;
+}
+
+/**
+ * EnvironmentResolver class for resolving environment variable placeholders
+ */
+export class EnvironmentResolver {
+ private options: Required;
+
+ constructor(options: ResolverOptions = {}) {
+ this.options = {
+ throwOnUndefined: false,
+ env: process.env,
+ recursive: true,
+ maxDepth: 10,
+ ...options,
+ };
+ }
+
+ /**
+ * Resolve environment variables in a string
+ * Supported syntax:
+ * - ${VAR_NAME} - Simple substitution
+ * - ${VAR_NAME:-default} - Use default if undefined
+ * - ${VAR_NAME:?error message} - Throw error if undefined
+ *
+ * @param input - String containing environment variable placeholders
+ * @param depth - Current recursion depth (internal)
+ * @returns Resolved string
+ */
+ resolve(input: string, depth: number = 0): string {
+ if (depth >= this.options.maxDepth) {
+ throw new Error(
+ `Maximum recursion depth (${this.options.maxDepth}) exceeded while resolving environment variables`
+ );
+ }
+
+ let hasUnresolved = false;
+
+ const resolved = input.replace(
+ ENV_VAR_PATTERN,
+ (match, varName, operator, operand) => {
+ const value = this.options.env[varName.trim()];
+
+ // Handle :- operator (default value)
+ if (operator === '-') {
+ return value !== undefined ? value : operand || '';
+ }
+
+ // Handle :? operator (required with error message)
+ if (operator === '?') {
+ if (value === undefined) {
+ throw new Error(operand || `Required environment variable not set: ${varName}`);
+ }
+ return value;
+ }
+
+ // Simple ${VAR_NAME} substitution
+ if (value === undefined) {
+ if (this.options.throwOnUndefined) {
+ throw new Error(`Undefined environment variable: ${varName}`);
+ }
+ hasUnresolved = true;
+ return match; // Keep placeholder if undefined
+ }
+
+ return value;
+ }
+ );
+
+ // Recursively resolve if needed and if there were changes
+ if (this.options.recursive && resolved !== input && hasUnresolved) {
+ return this.resolve(resolved, depth + 1);
+ }
+
+ return resolved;
+ }
+
+ /**
+ * Resolve environment variables in an object (deep)
+ * @param obj - Object containing values with environment variable placeholders
+ * @returns Object with resolved values
+ */
+ resolveObject(obj: T): T {
+ if (typeof obj !== 'object' || obj === null) {
+ return obj;
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map((item) => this.resolveObject(item)) as T;
+ }
+
+ const result: Record = {};
+
+ for (const [key, value] of Object.entries(obj)) {
+ if (typeof value === 'string') {
+ result[key] = this.resolve(value);
+ } else if (typeof value === 'object' && value !== null) {
+ result[key] = this.resolveObject(value);
+ } else {
+ result[key] = value;
+ }
+ }
+
+ return result as T;
+ }
+
+ /**
+ * Check if a string contains environment variable placeholders
+ */
+ hasPlaceholders(input: string): boolean {
+ return ENV_VAR_PATTERN.test(input);
+ }
+
+ /**
+ * Extract all environment variable names from a string
+ */
+ extractVariables(input: string): string[] {
+ const variables: string[] = [];
+ const regex = new RegExp(ENV_VAR_PATTERN.source, 'g');
+ let match;
+
+ while ((match = regex.exec(input)) !== null) {
+ variables.push(match[1].trim());
+ }
+
+ return variables;
+ }
+
+ /**
+ * Validate that all required environment variables are set
+ * @param input - String or object to check
+ * @returns Array of missing variable names
+ */
+ findMissingVariables(input: string | Record): string[] {
+ const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
+ const variables = this.extractVariables(inputStr);
+ const missing: string[] = [];
+
+ for (const varName of variables) {
+ if (this.options.env[varName] === undefined) {
+ missing.push(varName);
+ }
+ }
+
+ return [...new Set(missing)]; // Remove duplicates
+ }
+
+ /**
+ * Get current environment (or custom env if provided)
+ */
+ getEnv(): Record {
+ return this.options.env;
+ }
+
+ /**
+ * Update resolver options
+ */
+ setOptions(options: Partial): void {
+ this.options = { ...this.options, ...options };
+ }
+}
+
+/**
+ * Singleton resolver instance
+ */
+export const resolver = new EnvironmentResolver();
+
+/**
+ * Convenience function to resolve environment variables in a string
+ */
+export function resolveEnv(input: string, options?: ResolverOptions): string {
+ if (options) {
+ return new EnvironmentResolver(options).resolve(input);
+ }
+ return resolver.resolve(input);
+}
+
+/**
+ * Convenience function to resolve environment variables in an object
+ */
+export function resolveEnvObject(obj: T, options?: ResolverOptions): T {
+ if (options) {
+ return new EnvironmentResolver(options).resolveObject(obj);
+ }
+ return resolver.resolveObject(obj);
+}
diff --git a/packages/mcp-server/src/config/TokenGenerator.ts b/packages/mcp-server/src/config/TokenGenerator.ts
new file mode 100644
index 0000000..5053710
--- /dev/null
+++ b/packages/mcp-server/src/config/TokenGenerator.ts
@@ -0,0 +1,171 @@
+/**
+ * TokenGenerator - Generate secure random tokens for authentication
+ * Uses crypto.randomBytes for cryptographically secure token generation
+ */
+
+import { randomBytes, createHash } from 'crypto';
+
+/**
+ * Token generation options
+ */
+export interface TokenOptions {
+ /** Token length in bytes (default: 32) */
+ length?: number;
+
+ /** Output encoding (default: 'hex') */
+ encoding?: 'hex' | 'base64' | 'base64url';
+
+ /** Whether to include a prefix */
+ prefix?: string;
+}
+
+/**
+ * TokenGenerator class
+ */
+export class TokenGenerator {
+ /**
+ * Generate a secure random token
+ */
+ static generate(options: TokenOptions = {}): string {
+ const {
+ length = 32,
+ encoding = 'hex',
+ prefix = '',
+ } = options;
+
+ // Generate random bytes
+ const bytes = randomBytes(length);
+
+ // Convert to string based on encoding
+ let token: string;
+ switch (encoding) {
+ case 'hex':
+ token = bytes.toString('hex');
+ break;
+ case 'base64':
+ token = bytes.toString('base64');
+ break;
+ case 'base64url':
+ token = bytes.toString('base64url');
+ break;
+ default:
+ token = bytes.toString('hex');
+ }
+
+ // Add prefix if specified
+ return prefix ? `${prefix}${token}` : token;
+ }
+
+ /**
+ * Generate a channel token (128-bit security)
+ */
+ static generateChannelToken(): string {
+ return this.generate({
+ length: 32,
+ encoding: 'hex',
+ prefix: 'cct_',
+ });
+ }
+
+ /**
+ * Generate an API token (256-bit security)
+ */
+ static generateAPIToken(): string {
+ return this.generate({
+ length: 64,
+ encoding: 'base64url',
+ prefix: 'cca_',
+ });
+ }
+
+ /**
+ * Generate a webhook secret (256-bit security)
+ */
+ static generateWebhookSecret(): string {
+ return this.generate({
+ length: 64,
+ encoding: 'hex',
+ });
+ }
+
+ /**
+ * Hash a token (for secure storage)
+ */
+ static hash(token: string): string {
+ return createHash('sha256').update(token).digest('hex');
+ }
+
+ /**
+ * Validate token format
+ */
+ static validateFormat(token: string, options: {
+ minLength?: number;
+ prefix?: string;
+ } = {}): boolean {
+ const { minLength = 16, prefix } = options;
+
+ // Check for null/undefined
+ if (!token) {
+ return false;
+ }
+
+ // Check minimum length
+ if (token.length < minLength) {
+ return false;
+ }
+
+ // Check prefix if specified
+ if (prefix && !token.startsWith(prefix)) {
+ return false;
+ }
+
+ // Check for valid characters (alphanumeric + URL-safe characters)
+ if (!/^[a-zA-Z0-9_-]+$/.test(token)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generate a nonce (number used once)
+ */
+ static generateNonce(): string {
+ return this.generate({
+ length: 16,
+ encoding: 'hex',
+ });
+ }
+
+ /**
+ * Generate multiple tokens at once
+ */
+ static generateBatch(count: number, options: TokenOptions = {}): string[] {
+ const tokens: string[] = [];
+ for (let i = 0; i < count; i++) {
+ tokens.push(this.generate(options));
+ }
+ return tokens;
+ }
+}
+
+/**
+ * Convenience function to generate a token
+ */
+export function generateToken(options?: TokenOptions): string {
+ return TokenGenerator.generate(options);
+}
+
+/**
+ * Convenience function to generate a channel token
+ */
+export function generateChannelToken(): string {
+ return TokenGenerator.generateChannelToken();
+}
+
+/**
+ * Convenience function to hash a token
+ */
+export function hashToken(token: string): string {
+ return TokenGenerator.hash(token);
+}
diff --git a/packages/mcp-server/src/github/GitHubClient.ts b/packages/mcp-server/src/github/GitHubClient.ts
new file mode 100644
index 0000000..4d85e61
--- /dev/null
+++ b/packages/mcp-server/src/github/GitHubClient.ts
@@ -0,0 +1,367 @@
+/**
+ * GitHubClient - Wrapper for GitHub API using @octokit/rest
+ * Provides simplified interface for fetching issues, PRs, and managing webhooks
+ */
+
+import { Octokit } from '@octokit/rest';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * GitHub issue data
+ */
+export interface GitHubIssue {
+ id: number;
+ number: number;
+ title: string;
+ body: string | null;
+ state: 'open' | 'closed';
+ url: string;
+ htmlUrl: string;
+ createdAt: Date;
+ updatedAt: Date;
+ labels: string[];
+ assignees: string[];
+}
+
+/**
+ * GitHub pull request data
+ */
+export interface GitHubPullRequest {
+ id: number;
+ number: number;
+ title: string;
+ body: string | null;
+ state: 'open' | 'closed';
+ url: string;
+ htmlUrl: string;
+ createdAt: Date;
+ updatedAt: Date;
+ head: string;
+ base: string;
+ mergeable: boolean | null;
+}
+
+/**
+ * GitHub webhook event
+ */
+export interface GitHubWebhookEvent {
+ type: 'issues' | 'pull_request' | 'push' | 'unknown';
+ action?: string;
+ issue?: GitHubIssue;
+ pullRequest?: GitHubPullRequest;
+ repository: {
+ owner: string;
+ name: string;
+ };
+}
+
+/**
+ * GitHub client configuration
+ */
+export interface GitHubClientConfig {
+ token: string;
+ owner: string;
+ repo: string;
+ logger?: Logger;
+}
+
+/**
+ * GitHubClient class
+ */
+export class GitHubClient {
+ private octokit: Octokit;
+ private owner: string;
+ private repo: string;
+ private logger: Logger;
+
+ constructor(config: GitHubClientConfig) {
+ this.octokit = new Octokit({
+ auth: config.token,
+ });
+ this.owner = config.owner;
+ this.repo = config.repo;
+ this.logger = config.logger || createLogger();
+ }
+
+ /**
+ * Get a single issue by number
+ */
+ async getIssue(issueNumber: number): Promise {
+ try {
+ const { data } = await this.octokit.rest.issues.get({
+ owner: this.owner,
+ repo: this.repo,
+ issue_number: issueNumber,
+ });
+
+ return this.mapIssue(data);
+ } catch (error) {
+ this.logger.error(`Failed to fetch issue #${issueNumber}`, {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * List issues with optional filters
+ */
+ async listIssues(options?: {
+ state?: 'open' | 'closed' | 'all';
+ labels?: string[];
+ since?: Date;
+ perPage?: number;
+ }): Promise {
+ try {
+ const { data } = await this.octokit.rest.issues.listForRepo({
+ owner: this.owner,
+ repo: this.repo,
+ state: options?.state || 'open',
+ labels: options?.labels?.join(','),
+ since: options?.since?.toISOString(),
+ per_page: options?.perPage || 30,
+ });
+
+ return data.filter((issue) => !issue.pull_request).map(this.mapIssue);
+ } catch (error) {
+ this.logger.error('Failed to list issues', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Get a single pull request by number
+ */
+ async getPullRequest(prNumber: number): Promise {
+ try {
+ const { data } = await this.octokit.rest.pulls.get({
+ owner: this.owner,
+ repo: this.repo,
+ pull_number: prNumber,
+ });
+
+ return this.mapPullRequest(data);
+ } catch (error) {
+ this.logger.error(`Failed to fetch PR #${prNumber}`, {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * List pull requests with optional filters
+ */
+ async listPullRequests(options?: {
+ state?: 'open' | 'closed' | 'all';
+ head?: string;
+ base?: string;
+ perPage?: number;
+ }): Promise {
+ try {
+ const { data } = await this.octokit.rest.pulls.list({
+ owner: this.owner,
+ repo: this.repo,
+ state: options?.state || 'open',
+ head: options?.head,
+ base: options?.base,
+ per_page: options?.perPage || 30,
+ });
+
+ return data.map(this.mapPullRequest);
+ } catch (error) {
+ this.logger.error('Failed to list pull requests', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Create a comment on an issue
+ */
+ async createIssueComment(issueNumber: number, body: string): Promise {
+ try {
+ await this.octokit.rest.issues.createComment({
+ owner: this.owner,
+ repo: this.repo,
+ issue_number: issueNumber,
+ body,
+ });
+
+ this.logger.info(`Created comment on issue #${issueNumber}`);
+ } catch (error) {
+ this.logger.error(`Failed to create comment on issue #${issueNumber}`, {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Update issue labels
+ */
+ async updateIssueLabels(
+ issueNumber: number,
+ labels: string[]
+ ): Promise {
+ try {
+ await this.octokit.rest.issues.setLabels({
+ owner: this.owner,
+ repo: this.repo,
+ issue_number: issueNumber,
+ labels,
+ });
+
+ this.logger.info(`Updated labels on issue #${issueNumber}`);
+ } catch (error) {
+ this.logger.error(`Failed to update labels on issue #${issueNumber}`, {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Assign issue to users
+ */
+ async assignIssue(issueNumber: number, assignees: string[]): Promise {
+ try {
+ await this.octokit.rest.issues.addAssignees({
+ owner: this.owner,
+ repo: this.repo,
+ issue_number: issueNumber,
+ assignees,
+ });
+
+ this.logger.info(`Assigned issue #${issueNumber} to ${assignees.join(', ')}`);
+ } catch (error) {
+ this.logger.error(`Failed to assign issue #${issueNumber}`, {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Close an issue
+ */
+ async closeIssue(issueNumber: number): Promise {
+ try {
+ await this.octokit.rest.issues.update({
+ owner: this.owner,
+ repo: this.repo,
+ issue_number: issueNumber,
+ state: 'closed',
+ });
+
+ this.logger.info(`Closed issue #${issueNumber}`);
+ } catch (error) {
+ this.logger.error(`Failed to close issue #${issueNumber}`, {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Get repository information
+ */
+ async getRepository(): Promise<{ owner: string; name: string; url: string }> {
+ try {
+ const { data } = await this.octokit.rest.repos.get({
+ owner: this.owner,
+ repo: this.repo,
+ });
+
+ return {
+ owner: data.owner.login,
+ name: data.name,
+ url: data.html_url,
+ };
+ } catch (error) {
+ this.logger.error('Failed to fetch repository info', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Check rate limit status
+ */
+ async getRateLimit(): Promise<{
+ limit: number;
+ remaining: number;
+ reset: Date;
+ }> {
+ try {
+ const { data } = await this.octokit.rest.rateLimit.get();
+
+ return {
+ limit: data.rate.limit,
+ remaining: data.rate.remaining,
+ reset: new Date(data.rate.reset * 1000),
+ };
+ } catch (error) {
+ this.logger.error('Failed to fetch rate limit', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ throw error;
+ }
+ }
+
+ /**
+ * Map Octokit issue to our GitHubIssue interface
+ */
+ private mapIssue(data: any): GitHubIssue {
+ return {
+ id: data.id,
+ number: data.number,
+ title: data.title,
+ body: data.body,
+ state: data.state as 'open' | 'closed',
+ url: data.url,
+ htmlUrl: data.html_url,
+ createdAt: new Date(data.created_at),
+ updatedAt: new Date(data.updated_at),
+ labels: data.labels.map((label: any) =>
+ typeof label === 'string' ? label : label.name
+ ),
+ assignees: data.assignees.map((assignee: any) => assignee.login),
+ };
+ }
+
+ /**
+ * Map Octokit PR to our GitHubPullRequest interface
+ */
+ private mapPullRequest(data: any): GitHubPullRequest {
+ return {
+ id: data.id,
+ number: data.number,
+ title: data.title,
+ body: data.body,
+ state: data.state as 'open' | 'closed',
+ url: data.url,
+ htmlUrl: data.html_url,
+ createdAt: new Date(data.created_at),
+ updatedAt: new Date(data.updated_at),
+ head: data.head.ref,
+ base: data.base.ref,
+ mergeable: data.mergeable,
+ };
+ }
+
+ /**
+ * Get owner and repo
+ */
+ getRepoInfo(): { owner: string; repo: string } {
+ return {
+ owner: this.owner,
+ repo: this.repo,
+ };
+ }
+}
diff --git a/packages/mcp-server/src/github/PollingService.ts b/packages/mcp-server/src/github/PollingService.ts
new file mode 100644
index 0000000..927bf6a
--- /dev/null
+++ b/packages/mcp-server/src/github/PollingService.ts
@@ -0,0 +1,310 @@
+/**
+ * PollingService - Fallback polling with conditional requests (ETags)
+ * Polls GitHub API at regular intervals with optimization using ETags
+ */
+
+import type { GitHubClient, GitHubIssue, GitHubPullRequest } from './GitHubClient.js';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Polling event types
+ */
+export type PollingEventType = 'issue_updated' | 'pr_updated' | 'new_issue' | 'new_pr';
+
+/**
+ * Polling event
+ */
+export interface PollingEvent {
+ type: PollingEventType;
+ issue?: GitHubIssue;
+ pullRequest?: GitHubPullRequest;
+ previousState?: any;
+}
+
+/**
+ * Polling event handler
+ */
+export type PollingEventHandler = (event: PollingEvent) => void | Promise;
+
+/**
+ * Polling service configuration
+ */
+export interface PollingServiceConfig {
+ /** GitHub client instance */
+ client: GitHubClient;
+
+ /** Polling interval in milliseconds */
+ intervalMs?: number;
+
+ /** Logger instance */
+ logger?: Logger;
+
+ /** Whether to poll issues */
+ pollIssues?: boolean;
+
+ /** Whether to poll pull requests */
+ pollPullRequests?: boolean;
+}
+
+/**
+ * Cached state for conditional requests
+ */
+interface CachedState {
+ etag?: string;
+ lastModified?: Date;
+ data: Map;
+}
+
+/**
+ * PollingService class
+ */
+export class PollingService {
+ private client: GitHubClient;
+ private config: Required>;
+ private logger: Logger;
+ private eventHandlers: Set;
+ private timer?: NodeJS.Timeout;
+ private isRunning: boolean;
+ private issueCache: CachedState;
+ private prCache: CachedState;
+
+ constructor(config: PollingServiceConfig) {
+ this.client = config.client;
+ this.config = {
+ intervalMs: config.intervalMs || 30000, // 30 seconds default
+ logger: config.logger || createLogger(),
+ pollIssues: config.pollIssues ?? true,
+ pollPullRequests: config.pollPullRequests ?? true,
+ };
+ this.logger = this.config.logger;
+ this.eventHandlers = new Set();
+ this.isRunning = false;
+ this.issueCache = { data: new Map() };
+ this.prCache = { data: new Map() };
+ }
+
+ /**
+ * Start polling
+ */
+ start(): void {
+ if (this.isRunning) {
+ this.logger.warn('Polling service already running');
+ return;
+ }
+
+ this.isRunning = true;
+ this.logger.info('Starting polling service', {
+ intervalMs: this.config.intervalMs,
+ });
+
+ // Run initial poll immediately
+ this.poll().catch((error) => {
+ this.logger.error('Error in initial poll', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ });
+
+ // Schedule periodic polling
+ this.timer = setInterval(() => {
+ this.poll().catch((error) => {
+ this.logger.error('Error in polling', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ });
+ }, this.config.intervalMs);
+ }
+
+ /**
+ * Stop polling
+ */
+ stop(): void {
+ if (!this.isRunning) {
+ return;
+ }
+
+ this.isRunning = false;
+
+ if (this.timer) {
+ clearInterval(this.timer);
+ this.timer = undefined;
+ }
+
+ this.logger.info('Stopped polling service');
+ }
+
+ /**
+ * Perform a poll
+ */
+ private async poll(): Promise {
+ const startTime = Date.now();
+
+ try {
+ // Poll issues if enabled
+ if (this.config.pollIssues) {
+ await this.pollIssues();
+ }
+
+ // Poll pull requests if enabled
+ if (this.config.pollPullRequests) {
+ await this.pollPullRequests();
+ }
+
+ const duration = Date.now() - startTime;
+ this.logger.debug('Poll completed', { duration });
+ } catch (error) {
+ this.logger.error('Poll failed', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Poll issues
+ */
+ private async pollIssues(): Promise {
+ try {
+ // Fetch issues (GitHub API will use If-None-Match header with ETag automatically)
+ const since = this.issueCache.lastModified;
+ const issues = await this.client.listIssues({
+ state: 'open',
+ since,
+ });
+
+ // Detect changes
+ const newData = new Map();
+ for (const issue of issues) {
+ newData.set(issue.number, issue);
+
+ const cached = this.issueCache.data.get(issue.number);
+ if (!cached) {
+ // New issue
+ await this.notifyHandlers({
+ type: 'new_issue',
+ issue,
+ });
+ } else if (cached.updatedAt.getTime() !== issue.updatedAt.getTime()) {
+ // Updated issue
+ await this.notifyHandlers({
+ type: 'issue_updated',
+ issue,
+ previousState: cached,
+ });
+ }
+ }
+
+ // Update cache
+ this.issueCache.data = newData;
+ this.issueCache.lastModified = new Date();
+ } catch (error) {
+ this.logger.error('Error polling issues', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Poll pull requests
+ */
+ private async pollPullRequests(): Promise {
+ try {
+ const pullRequests = await this.client.listPullRequests({
+ state: 'open',
+ });
+
+ // Detect changes
+ const newData = new Map();
+ for (const pr of pullRequests) {
+ newData.set(pr.number, pr);
+
+ const cached = this.prCache.data.get(pr.number);
+ if (!cached) {
+ // New PR
+ await this.notifyHandlers({
+ type: 'new_pr',
+ pullRequest: pr,
+ });
+ } else if (cached.updatedAt.getTime() !== pr.updatedAt.getTime()) {
+ // Updated PR
+ await this.notifyHandlers({
+ type: 'pr_updated',
+ pullRequest: pr,
+ previousState: cached,
+ });
+ }
+ }
+
+ // Update cache
+ this.prCache.data = newData;
+ this.prCache.lastModified = new Date();
+ } catch (error) {
+ this.logger.error('Error polling pull requests', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Register event handler
+ */
+ onEvent(handler: PollingEventHandler): () => void {
+ this.eventHandlers.add(handler);
+ return () => this.eventHandlers.delete(handler);
+ }
+
+ /**
+ * Notify all handlers of an event
+ */
+ private async notifyHandlers(event: PollingEvent): Promise {
+ const promises = Array.from(this.eventHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in polling handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Check if service is running
+ */
+ getStatus(): {
+ running: boolean;
+ intervalMs: number;
+ lastPoll?: Date;
+ cachedIssues: number;
+ cachedPRs: number;
+ } {
+ return {
+ running: this.isRunning,
+ intervalMs: this.config.intervalMs,
+ lastPoll: this.issueCache.lastModified || this.prCache.lastModified,
+ cachedIssues: this.issueCache.data.size,
+ cachedPRs: this.prCache.data.size,
+ };
+ }
+
+ /**
+ * Force an immediate poll
+ */
+ async pollNow(): Promise {
+ if (!this.isRunning) {
+ throw new Error('Polling service is not running');
+ }
+
+ await this.poll();
+ }
+
+ /**
+ * Clear cache
+ */
+ clearCache(): void {
+ this.issueCache = { data: new Map() };
+ this.prCache = { data: new Map() };
+ this.logger.info('Cache cleared');
+ }
+}
diff --git a/packages/mcp-server/src/github/SyncManager.ts b/packages/mcp-server/src/github/SyncManager.ts
new file mode 100644
index 0000000..39863ee
--- /dev/null
+++ b/packages/mcp-server/src/github/SyncManager.ts
@@ -0,0 +1,401 @@
+/**
+ * SyncManager - Orchestrate webhook + polling, deduplicate events, map issues β tasks
+ */
+
+import { v4 as uuidv4 } from 'uuid';
+import type { GitHubClient, GitHubWebhookEvent, GitHubIssue } from './GitHubClient.js';
+import type { WebhookHandler } from './WebhookHandler.js';
+import type { PollingService, PollingEvent } from './PollingService.js';
+import type { Task, TaskCreation } from '../tasks/Task.js';
+import { createTask } from '../tasks/Task.js';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Sync event (deduplicated GitHub event β task creation)
+ */
+export interface SyncEvent {
+ /** Event type */
+ type: 'task_created' | 'task_updated' | 'task_closed';
+
+ /** Created/updated task */
+ task: Task;
+
+ /** Original GitHub issue */
+ issue: GitHubIssue;
+
+ /** Event source */
+ source: 'webhook' | 'polling';
+}
+
+/**
+ * Sync event handler
+ */
+export type SyncEventHandler = (event: SyncEvent) => void | Promise;
+
+/**
+ * SyncManager configuration
+ */
+export interface SyncManagerConfig {
+ /** GitHub client */
+ client: GitHubClient;
+
+ /** Webhook handler (optional) */
+ webhookHandler?: WebhookHandler;
+
+ /** Polling service (optional) */
+ pollingService?: PollingService;
+
+ /** Logger */
+ logger?: Logger;
+
+ /** Deduplication window in milliseconds */
+ deduplicationWindowMs?: number;
+}
+
+/**
+ * Event deduplication entry
+ */
+interface DeduplicationEntry {
+ issueNumber: number;
+ action: string;
+ timestamp: Date;
+}
+
+/**
+ * SyncManager class
+ */
+export class SyncManager {
+ private client: GitHubClient;
+ private webhookHandler?: WebhookHandler;
+ private pollingService?: PollingService;
+ private logger: Logger;
+ private eventHandlers: Set;
+ private isRunning: boolean;
+ private deduplicationWindowMs: number;
+ private recentEvents: DeduplicationEntry[];
+ private taskCache: Map; // issueNumber β Task
+
+ constructor(config: SyncManagerConfig) {
+ this.client = config.client;
+ this.webhookHandler = config.webhookHandler;
+ this.pollingService = config.pollingService;
+ this.logger = config.logger || createLogger();
+ this.eventHandlers = new Set();
+ this.isRunning = false;
+ this.deduplicationWindowMs = config.deduplicationWindowMs || 5000; // 5 seconds
+ this.recentEvents = [];
+ this.taskCache = new Map();
+ }
+
+ /**
+ * Start sync manager
+ */
+ async start(): Promise {
+ if (this.isRunning) {
+ this.logger.warn('SyncManager already running');
+ return;
+ }
+
+ this.isRunning = true;
+
+ // Start webhook handler if configured
+ if (this.webhookHandler) {
+ this.webhookHandler.onEvent(this.handleWebhookEvent.bind(this));
+ if (!this.webhookHandler.isRunning()) {
+ await this.webhookHandler.start();
+ }
+ this.logger.info('Webhook handler attached');
+ }
+
+ // Start polling service if configured
+ if (this.pollingService) {
+ this.pollingService.onEvent(this.handlePollingEvent.bind(this));
+ this.pollingService.start();
+ this.logger.info('Polling service attached');
+ }
+
+ // Start deduplication cleanup timer
+ this.startDeduplicationCleanup();
+
+ this.logger.info('SyncManager started');
+ }
+
+ /**
+ * Stop sync manager
+ */
+ async stop(): Promise {
+ if (!this.isRunning) {
+ return;
+ }
+
+ this.isRunning = false;
+
+ // Stop webhook handler
+ if (this.webhookHandler?.isRunning()) {
+ await this.webhookHandler.stop();
+ }
+
+ // Stop polling service
+ if (this.pollingService) {
+ this.pollingService.stop();
+ }
+
+ this.logger.info('SyncManager stopped');
+ }
+
+ /**
+ * Handle webhook event
+ */
+ private async handleWebhookEvent(event: GitHubWebhookEvent): Promise {
+ try {
+ if (event.type !== 'issues' || !event.issue) {
+ return; // Only handle issue events
+ }
+
+ const action = event.action || 'unknown';
+ const issue = event.issue;
+
+ // Check for duplicates
+ if (this.isDuplicate(issue.number, action, 'webhook')) {
+ this.logger.debug('Duplicate webhook event ignored', {
+ issueNumber: issue.number,
+ action,
+ });
+ return;
+ }
+
+ // Process event
+ await this.processIssueEvent(issue, action, 'webhook');
+ } catch (error) {
+ this.logger.error('Error handling webhook event', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Handle polling event
+ */
+ private async handlePollingEvent(event: PollingEvent): Promise {
+ try {
+ if (!event.issue) {
+ return; // Only handle issue events
+ }
+
+ const action = event.type === 'new_issue' ? 'opened' : 'updated';
+ const issue = event.issue;
+
+ // Check for duplicates
+ if (this.isDuplicate(issue.number, action, 'polling')) {
+ this.logger.debug('Duplicate polling event ignored', {
+ issueNumber: issue.number,
+ action,
+ });
+ return;
+ }
+
+ // Process event
+ await this.processIssueEvent(issue, action, 'polling');
+ } catch (error) {
+ this.logger.error('Error handling polling event', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+
+ /**
+ * Process issue event
+ */
+ private async processIssueEvent(
+ issue: GitHubIssue,
+ action: string,
+ source: 'webhook' | 'polling'
+ ): Promise {
+ // Map to sync event type
+ let syncType: SyncEvent['type'];
+ if (action === 'opened') {
+ syncType = 'task_created';
+ } else if (action === 'closed') {
+ syncType = 'task_closed';
+ } else {
+ syncType = 'task_updated';
+ }
+
+ // Get or create task
+ let task = this.taskCache.get(issue.number);
+
+ if (!task && syncType === 'task_created') {
+ // Create new task from issue
+ task = this.issueToTask(issue);
+ this.taskCache.set(issue.number, task);
+ } else if (task) {
+ // Update existing task from issue
+ task = this.updateTaskFromIssue(task, issue, action);
+ this.taskCache.set(issue.number, task);
+ } else {
+ // Task not in cache but event is not 'opened' - fetch from issue
+ task = this.issueToTask(issue);
+ this.taskCache.set(issue.number, task);
+ syncType = 'task_created'; // Treat as new
+ }
+
+ // Notify handlers
+ await this.notifyHandlers({
+ type: syncType,
+ task,
+ issue,
+ source,
+ });
+
+ this.logger.info('Synced issue to task', {
+ issueNumber: issue.number,
+ taskId: task.id,
+ action,
+ source,
+ });
+ }
+
+ /**
+ * Map GitHub issue to Task
+ */
+ private issueToTask(issue: GitHubIssue): Task {
+ const taskData: TaskCreation = {
+ description: issue.title,
+ dependencies: [],
+ githubIssueId: String(issue.number),
+ githubIssueUrl: issue.htmlUrl,
+ };
+
+ return createTask(uuidv4(), taskData);
+ }
+
+ /**
+ * Update task from issue
+ */
+ private updateTaskFromIssue(task: Task, issue: GitHubIssue, action: string): Task {
+ // Update description if title changed
+ if (task.description !== issue.title) {
+ task = { ...task, description: issue.title };
+ }
+
+ // Update status based on issue state
+ if (action === 'closed' && issue.state === 'closed') {
+ task = { ...task, completedAt: new Date() };
+ }
+
+ return task;
+ }
+
+ /**
+ * Check if event is a duplicate
+ */
+ private isDuplicate(
+ issueNumber: number,
+ action: string,
+ source: 'webhook' | 'polling'
+ ): boolean {
+ const now = new Date();
+ const cutoff = new Date(now.getTime() - this.deduplicationWindowMs);
+
+ // Check recent events
+ const isDup = this.recentEvents.some(
+ (entry) =>
+ entry.issueNumber === issueNumber &&
+ entry.action === action &&
+ entry.timestamp > cutoff
+ );
+
+ if (!isDup) {
+ // Record this event
+ this.recentEvents.push({
+ issueNumber,
+ action,
+ timestamp: now,
+ });
+ }
+
+ return isDup;
+ }
+
+ /**
+ * Start deduplication cleanup timer
+ */
+ private startDeduplicationCleanup(): void {
+ setInterval(() => {
+ const now = new Date();
+ const cutoff = new Date(now.getTime() - this.deduplicationWindowMs);
+
+ // Remove old entries
+ this.recentEvents = this.recentEvents.filter(
+ (entry) => entry.timestamp > cutoff
+ );
+ }, this.deduplicationWindowMs);
+ }
+
+ /**
+ * Register sync event handler
+ */
+ onSync(handler: SyncEventHandler): () => void {
+ this.eventHandlers.add(handler);
+ return () => this.eventHandlers.delete(handler);
+ }
+
+ /**
+ * Notify all handlers
+ */
+ private async notifyHandlers(event: SyncEvent): Promise {
+ const promises = Array.from(this.eventHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in sync handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Get task by GitHub issue number
+ */
+ getTaskByIssueNumber(issueNumber: number): Task | undefined {
+ return this.taskCache.get(issueNumber);
+ }
+
+ /**
+ * Get all synced tasks
+ */
+ getAllTasks(): Task[] {
+ return Array.from(this.taskCache.values());
+ }
+
+ /**
+ * Clear task cache
+ */
+ clearCache(): void {
+ this.taskCache.clear();
+ this.logger.info('Task cache cleared');
+ }
+
+ /**
+ * Get sync status
+ */
+ getStatus(): {
+ running: boolean;
+ webhookEnabled: boolean;
+ pollingEnabled: boolean;
+ cachedTasks: number;
+ recentEvents: number;
+ } {
+ return {
+ running: this.isRunning,
+ webhookEnabled: this.webhookHandler !== undefined,
+ pollingEnabled: this.pollingService !== undefined,
+ cachedTasks: this.taskCache.size,
+ recentEvents: this.recentEvents.length,
+ };
+ }
+}
diff --git a/packages/mcp-server/src/github/WebhookHandler.ts b/packages/mcp-server/src/github/WebhookHandler.ts
new file mode 100644
index 0000000..6af7b11
--- /dev/null
+++ b/packages/mcp-server/src/github/WebhookHandler.ts
@@ -0,0 +1,340 @@
+/**
+ * WebhookHandler - Express endpoint for GitHub webhook events
+ * Validates webhook signatures and parses issue/PR events
+ */
+
+import express, { Request, Response, Application } from 'express';
+import { createHmac, timingSafeEqual } from 'crypto';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+import { GitHubWebhookEvent, GitHubIssue, GitHubPullRequest } from './GitHubClient.js';
+
+/**
+ * Webhook event handler callback
+ */
+export type WebhookEventHandler = (event: GitHubWebhookEvent) => void | Promise;
+
+/**
+ * Webhook handler configuration
+ */
+export interface WebhookHandlerConfig {
+ /** Webhook secret for signature validation */
+ secret?: string;
+
+ /** Port to listen on */
+ port?: number;
+
+ /** Path for webhook endpoint */
+ path?: string;
+
+ /** Logger instance */
+ logger?: Logger;
+
+ /** Whether to verify signatures */
+ verifySignature?: boolean;
+}
+
+/**
+ * WebhookHandler class
+ */
+export class WebhookHandler {
+ private app: Application;
+ private config: Required;
+ private logger: Logger;
+ private eventHandlers: Set;
+ private server?: ReturnType;
+
+ constructor(config: WebhookHandlerConfig = {}) {
+ this.config = {
+ secret: config.secret || '',
+ port: config.port || 3000,
+ path: config.path || '/webhook',
+ logger: config.logger || createLogger(),
+ verifySignature: config.verifySignature ?? true,
+ };
+ this.logger = this.config.logger;
+ this.eventHandlers = new Set();
+ this.app = express();
+ this.setupMiddleware();
+ this.setupRoutes();
+ }
+
+ /**
+ * Setup Express middleware
+ */
+ private setupMiddleware(): void {
+ // Raw body parser for signature verification
+ this.app.use(
+ express.json({
+ verify: (req: any, res, buf) => {
+ req.rawBody = buf.toString('utf-8');
+ },
+ })
+ );
+ }
+
+ /**
+ * Setup Express routes
+ */
+ private setupRoutes(): void {
+ // Health check endpoint
+ this.app.get('/health', (req, res) => {
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
+ });
+
+ // Webhook endpoint
+ this.app.post(this.config.path, this.handleWebhook.bind(this));
+ }
+
+ /**
+ * Handle incoming webhook
+ */
+ private async handleWebhook(req: Request, res: Response): Promise {
+ try {
+ // Verify signature if enabled
+ if (this.config.verifySignature) {
+ const isValid = this.verifySignature(
+ req.headers['x-hub-signature-256'] as string,
+ (req as any).rawBody
+ );
+
+ if (!isValid) {
+ this.logger.warn('Invalid webhook signature');
+ res.status(401).json({ error: 'Invalid signature' });
+ return;
+ }
+ }
+
+ // Get event type from header
+ const eventType = req.headers['x-github-event'] as string;
+ const deliveryId = req.headers['x-github-delivery'] as string;
+
+ this.logger.debug('Received webhook event', {
+ eventType,
+ deliveryId,
+ });
+
+ // Parse event
+ const event = this.parseEvent(eventType, req.body);
+
+ // Notify handlers
+ await this.notifyHandlers(event);
+
+ res.status(200).json({ received: true });
+ } catch (error) {
+ this.logger.error('Error handling webhook', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ res.status(500).json({ error: 'Internal server error' });
+ }
+ }
+
+ /**
+ * Verify webhook signature
+ */
+ private verifySignature(signature: string | undefined, body: string): boolean {
+ if (!signature || !this.config.secret) {
+ return !this.config.verifySignature; // Skip verification if no secret configured
+ }
+
+ try {
+ const hmac = createHmac('sha256', this.config.secret);
+ hmac.update(body);
+ const expectedSignature = `sha256=${hmac.digest('hex')}`;
+
+ // Use timing-safe comparison
+ return timingSafeEqual(
+ Buffer.from(signature),
+ Buffer.from(expectedSignature)
+ );
+ } catch (error) {
+ this.logger.error('Error verifying signature', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ return false;
+ }
+ }
+
+ /**
+ * Parse webhook event
+ */
+ private parseEvent(eventType: string, payload: any): GitHubWebhookEvent {
+ switch (eventType) {
+ case 'issues':
+ return this.parseIssueEvent(payload);
+
+ case 'pull_request':
+ return this.parsePullRequestEvent(payload);
+
+ case 'push':
+ return this.parsePushEvent(payload);
+
+ default:
+ return {
+ type: 'unknown',
+ repository: {
+ owner: payload.repository?.owner?.login || 'unknown',
+ name: payload.repository?.name || 'unknown',
+ },
+ };
+ }
+ }
+
+ /**
+ * Parse issue event
+ */
+ private parseIssueEvent(payload: any): GitHubWebhookEvent {
+ const issue: GitHubIssue = {
+ id: payload.issue.id,
+ number: payload.issue.number,
+ title: payload.issue.title,
+ body: payload.issue.body,
+ state: payload.issue.state,
+ url: payload.issue.url,
+ htmlUrl: payload.issue.html_url,
+ createdAt: new Date(payload.issue.created_at),
+ updatedAt: new Date(payload.issue.updated_at),
+ labels: payload.issue.labels.map((label: any) => label.name),
+ assignees: payload.issue.assignees.map((assignee: any) => assignee.login),
+ };
+
+ return {
+ type: 'issues',
+ action: payload.action,
+ issue,
+ repository: {
+ owner: payload.repository.owner.login,
+ name: payload.repository.name,
+ },
+ };
+ }
+
+ /**
+ * Parse pull request event
+ */
+ private parsePullRequestEvent(payload: any): GitHubWebhookEvent {
+ const pullRequest: GitHubPullRequest = {
+ id: payload.pull_request.id,
+ number: payload.pull_request.number,
+ title: payload.pull_request.title,
+ body: payload.pull_request.body,
+ state: payload.pull_request.state,
+ url: payload.pull_request.url,
+ htmlUrl: payload.pull_request.html_url,
+ createdAt: new Date(payload.pull_request.created_at),
+ updatedAt: new Date(payload.pull_request.updated_at),
+ head: payload.pull_request.head.ref,
+ base: payload.pull_request.base.ref,
+ mergeable: payload.pull_request.mergeable,
+ };
+
+ return {
+ type: 'pull_request',
+ action: payload.action,
+ pullRequest,
+ repository: {
+ owner: payload.repository.owner.login,
+ name: payload.repository.name,
+ },
+ };
+ }
+
+ /**
+ * Parse push event
+ */
+ private parsePushEvent(payload: any): GitHubWebhookEvent {
+ return {
+ type: 'push',
+ action: 'pushed',
+ repository: {
+ owner: payload.repository.owner.login,
+ name: payload.repository.name,
+ },
+ };
+ }
+
+ /**
+ * Register event handler
+ */
+ onEvent(handler: WebhookEventHandler): () => void {
+ this.eventHandlers.add(handler);
+ return () => this.eventHandlers.delete(handler);
+ }
+
+ /**
+ * Notify all handlers of an event
+ */
+ private async notifyHandlers(event: GitHubWebhookEvent): Promise {
+ const promises = Array.from(this.eventHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in webhook handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Start the webhook server
+ */
+ async start(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.server = this.app.listen(this.config.port, () => {
+ this.logger.info('Webhook server started', {
+ port: this.config.port,
+ path: this.config.path,
+ });
+ resolve();
+ });
+
+ this.server.on('error', reject);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ /**
+ * Stop the webhook server
+ */
+ async stop(): Promise {
+ return new Promise((resolve, reject) => {
+ if (!this.server) {
+ resolve();
+ return;
+ }
+
+ this.server.close((error) => {
+ if (error) {
+ reject(error);
+ } else {
+ this.logger.info('Webhook server stopped');
+ this.server = undefined;
+ resolve();
+ }
+ });
+ });
+ }
+
+ /**
+ * Check if server is running
+ */
+ isRunning(): boolean {
+ return this.server !== undefined;
+ }
+
+ /**
+ * Get server configuration
+ */
+ getConfig(): { port: number; path: string } {
+ return {
+ port: this.config.port,
+ path: this.config.path,
+ };
+ }
+}
diff --git a/packages/mcp-server/src/logging/LogFormatter.ts b/packages/mcp-server/src/logging/LogFormatter.ts
new file mode 100644
index 0000000..0b4ded5
--- /dev/null
+++ b/packages/mcp-server/src/logging/LogFormatter.ts
@@ -0,0 +1,330 @@
+/**
+ * LogFormatter - Structured JSON logging formatter
+ * Formats log entries for output to console, files, or external logging services
+ */
+
+import { LogEntry, LogLevel, LogMetadata } from './Logger.js';
+
+/**
+ * Formatting options
+ */
+export interface FormatOptions {
+ /** Output format */
+ format: 'json' | 'text';
+
+ /** Whether to colorize output (text format only) */
+ colorize?: boolean;
+
+ /** Whether to include timestamp */
+ includeTimestamp?: boolean;
+
+ /** Whether to pretty-print JSON */
+ prettyPrint?: boolean;
+
+ /** Indent size for pretty-printing */
+ indent?: number;
+
+ /** Maximum metadata depth to include */
+ maxDepth?: number;
+}
+
+/**
+ * ANSI color codes for console output
+ */
+const Colors = {
+ reset: '\x1b[0m',
+ bright: '\x1b[1m',
+ dim: '\x1b[2m',
+ red: '\x1b[31m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ gray: '\x1b[90m',
+};
+
+/**
+ * Log level colors
+ */
+const LevelColors: Record = {
+ [LogLevel.ERROR]: Colors.red,
+ [LogLevel.WARN]: Colors.yellow,
+ [LogLevel.INFO]: Colors.blue,
+ [LogLevel.DEBUG]: Colors.gray,
+};
+
+/**
+ * LogFormatter class
+ */
+export class LogFormatter {
+ private options: Required;
+
+ constructor(options: Partial = {}) {
+ this.options = {
+ format: 'json',
+ colorize: false,
+ includeTimestamp: true,
+ prettyPrint: false,
+ indent: 2,
+ maxDepth: 5,
+ ...options,
+ };
+ }
+
+ /**
+ * Format a log entry
+ */
+ format(entry: LogEntry): string {
+ switch (this.options.format) {
+ case 'json':
+ return this.formatJSON(entry);
+ case 'text':
+ return this.formatText(entry);
+ default:
+ return this.formatJSON(entry);
+ }
+ }
+
+ /**
+ * Format as JSON
+ */
+ private formatJSON(entry: LogEntry): string {
+ const obj = this.prepareEntry(entry);
+
+ if (this.options.prettyPrint) {
+ return JSON.stringify(obj, this.getReplacer(), this.options.indent);
+ }
+
+ return JSON.stringify(obj, this.getReplacer());
+ }
+
+ /**
+ * Format as human-readable text
+ */
+ private formatText(entry: LogEntry): string {
+ const parts: string[] = [];
+
+ // Timestamp
+ if (this.options.includeTimestamp) {
+ const timestamp = this.formatTimestamp(entry.timestamp);
+ parts.push(this.colorize(timestamp, Colors.gray));
+ }
+
+ // Level
+ const level = this.formatLevel(entry.level);
+ parts.push(level);
+
+ // Message
+ parts.push(entry.message);
+
+ // Metadata
+ if (entry.metadata && Object.keys(entry.metadata).length > 0) {
+ const metadata = this.formatMetadata(entry.metadata);
+ parts.push(this.colorize(metadata, Colors.dim));
+ }
+
+ return parts.join(' ');
+ }
+
+ /**
+ * Prepare entry for serialization
+ */
+ private prepareEntry(entry: LogEntry): Record {
+ const obj: Record = {
+ level: entry.level,
+ message: entry.message,
+ };
+
+ if (this.options.includeTimestamp) {
+ obj.timestamp = entry.timestamp;
+ }
+
+ if (entry.metadata) {
+ // Flatten metadata into top level or keep nested
+ obj.metadata = this.sanitizeMetadata(entry.metadata);
+ }
+
+ return obj;
+ }
+
+ /**
+ * Sanitize metadata (handle errors, circular references, depth)
+ */
+ private sanitizeMetadata(
+ metadata: LogMetadata,
+ depth: number = 0
+ ): Record {
+ if (depth >= this.options.maxDepth) {
+ return { __truncated: true };
+ }
+
+ const sanitized: Record = {};
+
+ for (const [key, value] of Object.entries(metadata)) {
+ if (value instanceof Error) {
+ sanitized[key] = {
+ message: value.message,
+ name: value.name,
+ stack: value.stack,
+ };
+ } else if (value instanceof Date) {
+ sanitized[key] = value.toISOString();
+ } else if (Array.isArray(value)) {
+ sanitized[key] = value.map((item) =>
+ typeof item === 'object' && item !== null
+ ? this.sanitizeMetadata(item as LogMetadata, depth + 1)
+ : item
+ );
+ } else if (typeof value === 'object' && value !== null) {
+ sanitized[key] = this.sanitizeMetadata(value as LogMetadata, depth + 1);
+ } else {
+ sanitized[key] = value;
+ }
+ }
+
+ return sanitized;
+ }
+
+ /**
+ * Get JSON replacer function
+ */
+ private getReplacer(): (key: string, value: unknown) => unknown {
+ const seen = new WeakSet();
+
+ return (key: string, value: unknown) => {
+ // Handle circular references
+ if (typeof value === 'object' && value !== null) {
+ if (seen.has(value)) {
+ return '[Circular]';
+ }
+ seen.add(value);
+ }
+
+ // Handle special types
+ if (value instanceof Error) {
+ return {
+ message: value.message,
+ name: value.name,
+ stack: value.stack,
+ };
+ }
+
+ if (value instanceof Date) {
+ return value.toISOString();
+ }
+
+ return value;
+ };
+ }
+
+ /**
+ * Format timestamp for text output
+ */
+ private formatTimestamp(timestamp: string): string {
+ const date = new Date(timestamp);
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+ const seconds = String(date.getSeconds()).padStart(2, '0');
+ const ms = String(date.getMilliseconds()).padStart(3, '0');
+ return `${hours}:${minutes}:${seconds}.${ms}`;
+ }
+
+ /**
+ * Format log level for text output
+ */
+ private formatLevel(level: LogLevel): string {
+ const formatted = `[${level}]`.padEnd(7);
+ return this.colorize(formatted, LevelColors[level]);
+ }
+
+ /**
+ * Format metadata for text output
+ */
+ private formatMetadata(metadata: LogMetadata): string {
+ const parts: string[] = [];
+
+ // Common fields first
+ if (metadata.component) {
+ parts.push(`component=${metadata.component}`);
+ }
+ if (metadata.agentId) {
+ parts.push(`agent=${metadata.agentId.slice(0, 8)}`);
+ }
+ if (metadata.taskId) {
+ parts.push(`task=${metadata.taskId.slice(0, 8)}`);
+ }
+ if (metadata.duration !== undefined) {
+ parts.push(`duration=${metadata.duration}ms`);
+ }
+
+ // Other fields
+ for (const [key, value] of Object.entries(metadata)) {
+ if (!['component', 'agentId', 'taskId', 'duration', 'error'].includes(key)) {
+ parts.push(`${key}=${this.stringifyValue(value)}`);
+ }
+ }
+
+ // Error last
+ if (metadata.error instanceof Error) {
+ parts.push(`error="${metadata.error.message}"`);
+ }
+
+ return parts.length > 0 ? `{${parts.join(', ')}}` : '';
+ }
+
+ /**
+ * Stringify a value for text output
+ */
+ private stringifyValue(value: unknown): string {
+ if (typeof value === 'string') {
+ return `"${value}"`;
+ }
+ if (typeof value === 'object') {
+ return JSON.stringify(value);
+ }
+ return String(value);
+ }
+
+ /**
+ * Apply color if colorize is enabled
+ */
+ private colorize(text: string, color: string): string {
+ if (!this.options.colorize) {
+ return text;
+ }
+ return `${color}${text}${Colors.reset}`;
+ }
+
+ /**
+ * Update formatter options
+ */
+ setOptions(options: Partial): void {
+ this.options = { ...this.options, ...options };
+ }
+
+ /**
+ * Get current options
+ */
+ getOptions(): FormatOptions {
+ return { ...this.options };
+ }
+}
+
+/**
+ * Create a formatter instance
+ */
+export function createFormatter(options?: Partial): LogFormatter {
+ return new LogFormatter(options);
+}
+
+/**
+ * Default formatters
+ */
+export const jsonFormatter = createFormatter({ format: 'json' });
+export const prettyJsonFormatter = createFormatter({
+ format: 'json',
+ prettyPrint: true,
+});
+export const textFormatter = createFormatter({
+ format: 'text',
+ colorize: true,
+});
diff --git a/packages/mcp-server/src/logging/Logger.ts b/packages/mcp-server/src/logging/Logger.ts
new file mode 100644
index 0000000..7392da9
--- /dev/null
+++ b/packages/mcp-server/src/logging/Logger.ts
@@ -0,0 +1,268 @@
+/**
+ * Logger - Structured logging interface with multiple levels
+ * Based on specs/001-multi-agent-coordination/plan.md logging requirements
+ */
+
+/**
+ * Log levels in order of severity
+ */
+export enum LogLevel {
+ ERROR = 'ERROR',
+ WARN = 'WARN',
+ INFO = 'INFO',
+ DEBUG = 'DEBUG',
+}
+
+/**
+ * Log level numeric values for comparison
+ */
+export const LogLevelValue: Record = {
+ [LogLevel.ERROR]: 40,
+ [LogLevel.WARN]: 30,
+ [LogLevel.INFO]: 20,
+ [LogLevel.DEBUG]: 10,
+};
+
+/**
+ * Log entry metadata
+ */
+export interface LogMetadata {
+ /** Component or module name */
+ component?: string;
+
+ /** Agent ID if applicable */
+ agentId?: string;
+
+ /** Task ID if applicable */
+ taskId?: string;
+
+ /** Request correlation ID */
+ correlationId?: string;
+
+ /** Error object */
+ error?: Error;
+
+ /** Duration in milliseconds */
+ duration?: number;
+
+ /** Custom key-value pairs */
+ [key: string]: unknown;
+}
+
+/**
+ * Log entry structure
+ */
+export interface LogEntry {
+ /** Log level */
+ level: LogLevel;
+
+ /** Log message */
+ message: string;
+
+ /** Timestamp (ISO 8601) */
+ timestamp: string;
+
+ /** Additional metadata */
+ metadata?: LogMetadata;
+}
+
+/**
+ * Logger interface
+ */
+export interface Logger {
+ /**
+ * Get current log level
+ */
+ readonly level: LogLevel;
+
+ /**
+ * Set log level
+ */
+ setLevel(level: LogLevel): void;
+
+ /**
+ * Log an error message
+ */
+ error(message: string, metadata?: LogMetadata): void;
+
+ /**
+ * Log a warning message
+ */
+ warn(message: string, metadata?: LogMetadata): void;
+
+ /**
+ * Log an info message
+ */
+ info(message: string, metadata?: LogMetadata): void;
+
+ /**
+ * Log a debug message
+ */
+ debug(message: string, metadata?: LogMetadata): void;
+
+ /**
+ * Log at a specific level
+ */
+ log(level: LogLevel, message: string, metadata?: LogMetadata): void;
+
+ /**
+ * Check if a log level is enabled
+ */
+ isLevelEnabled(level: LogLevel): boolean;
+
+ /**
+ * Create a child logger with preset metadata
+ */
+ child(metadata: LogMetadata): Logger;
+}
+
+/**
+ * Base logger implementation
+ */
+export abstract class BaseLogger implements Logger {
+ protected _level: LogLevel;
+ protected childMetadata?: LogMetadata;
+
+ constructor(level: LogLevel = LogLevel.INFO, childMetadata?: LogMetadata) {
+ this._level = level;
+ this.childMetadata = childMetadata;
+ }
+
+ get level(): LogLevel {
+ return this._level;
+ }
+
+ setLevel(level: LogLevel): void {
+ this._level = level;
+ }
+
+ error(message: string, metadata?: LogMetadata): void {
+ this.log(LogLevel.ERROR, message, metadata);
+ }
+
+ warn(message: string, metadata?: LogMetadata): void {
+ this.log(LogLevel.WARN, message, metadata);
+ }
+
+ info(message: string, metadata?: LogMetadata): void {
+ this.log(LogLevel.INFO, message, metadata);
+ }
+
+ debug(message: string, metadata?: LogMetadata): void {
+ this.log(LogLevel.DEBUG, message, metadata);
+ }
+
+ log(level: LogLevel, message: string, metadata?: LogMetadata): void {
+ if (!this.isLevelEnabled(level)) {
+ return;
+ }
+
+ const entry: LogEntry = {
+ level,
+ message,
+ timestamp: new Date().toISOString(),
+ metadata: this.mergeMetadata(metadata),
+ };
+
+ this.write(entry);
+ }
+
+ isLevelEnabled(level: LogLevel): boolean {
+ return LogLevelValue[level] >= LogLevelValue[this._level];
+ }
+
+ child(metadata: LogMetadata): Logger {
+ const childMeta = this.mergeMetadata(metadata);
+ return new (this.constructor as new (
+ level: LogLevel,
+ childMetadata?: LogMetadata
+ ) => BaseLogger)(this._level, childMeta);
+ }
+
+ /**
+ * Merge child metadata with provided metadata
+ */
+ protected mergeMetadata(metadata?: LogMetadata): LogMetadata | undefined {
+ if (!this.childMetadata && !metadata) {
+ return undefined;
+ }
+
+ return {
+ ...this.childMetadata,
+ ...metadata,
+ };
+ }
+
+ /**
+ * Abstract method to write log entry (implemented by subclasses)
+ */
+ protected abstract write(entry: LogEntry): void;
+}
+
+/**
+ * Console logger implementation
+ */
+export class ConsoleLogger extends BaseLogger {
+ protected write(entry: LogEntry): void {
+ const method = this.getConsoleMethod(entry.level);
+ method(this.format(entry));
+ }
+
+ /**
+ * Get appropriate console method for log level
+ */
+ private getConsoleMethod(
+ level: LogLevel
+ ): (...args: unknown[]) => void {
+ switch (level) {
+ case LogLevel.ERROR:
+ return console.error.bind(console);
+ case LogLevel.WARN:
+ return console.warn.bind(console);
+ case LogLevel.INFO:
+ return console.info.bind(console);
+ case LogLevel.DEBUG:
+ return console.debug.bind(console);
+ default:
+ return console.log.bind(console);
+ }
+ }
+
+ /**
+ * Format log entry (can be overridden)
+ */
+ protected format(entry: LogEntry): string {
+ return JSON.stringify(entry);
+ }
+}
+
+/**
+ * No-op logger (discards all logs)
+ */
+export class NoopLogger extends BaseLogger {
+ protected write(_entry: LogEntry): void {
+ // No-op
+ }
+}
+
+/**
+ * Create a logger instance
+ */
+export function createLogger(
+ level: LogLevel = LogLevel.INFO,
+ type: 'console' | 'noop' = 'console'
+): Logger {
+ switch (type) {
+ case 'console':
+ return new ConsoleLogger(level);
+ case 'noop':
+ return new NoopLogger(level);
+ default:
+ return new ConsoleLogger(level);
+ }
+}
+
+/**
+ * Default logger instance
+ */
+export const logger = createLogger();
diff --git a/packages/mcp-server/src/protocol/Message.ts b/packages/mcp-server/src/protocol/Message.ts
new file mode 100644
index 0000000..4f65bbf
--- /dev/null
+++ b/packages/mcp-server/src/protocol/Message.ts
@@ -0,0 +1,207 @@
+/**
+ * CoorChat Message Protocol - TypeScript Type Definitions
+ * Based on message-protocol.json schema v1.0
+ */
+
+/**
+ * Message types for agent coordination
+ */
+export enum MessageType {
+ TASK_ASSIGNED = 'task_assigned',
+ TASK_STARTED = 'task_started',
+ TASK_BLOCKED = 'task_blocked',
+ TASK_PROGRESS = 'task_progress',
+ TASK_COMPLETED = 'task_completed',
+ TASK_FAILED = 'task_failed',
+ CAPABILITY_QUERY = 'capability_query',
+ CAPABILITY_RESPONSE = 'capability_response',
+ STATUS_QUERY = 'status_query',
+ STATUS_RESPONSE = 'status_response',
+ ERROR = 'error',
+ HEARTBEAT = 'heartbeat',
+ AGENT_JOINED = 'agent_joined',
+ AGENT_LEFT = 'agent_left',
+}
+
+/**
+ * Delivery status states
+ */
+export enum DeliveryStatus {
+ QUEUED = 'queued',
+ SENDING = 'sending',
+ SENT = 'sent',
+ DELIVERED = 'delivered',
+ ACKNOWLEDGED = 'acknowledged',
+ FAILED = 'failed',
+}
+
+/**
+ * Resource limits for agent capabilities
+ */
+export interface ResourceLimits {
+ apiQuotaPerHour?: number;
+ maxConcurrentTasks?: number;
+ rateLimitPerMinute?: number;
+ memoryLimitMB?: number;
+}
+
+/**
+ * Payload for task_assigned messages
+ */
+export interface TaskAssignedPayload {
+ taskId: string;
+ description: string;
+ dependencies?: string[];
+ githubIssue: string;
+}
+
+/**
+ * Payload for task_progress messages
+ */
+export interface TaskProgressPayload {
+ taskId: string;
+ percentComplete: number;
+ status: string;
+}
+
+/**
+ * Payload for task_completed messages
+ */
+export interface TaskCompletedPayload {
+ taskId: string;
+ result: Record;
+ githubPR?: string | null;
+}
+
+/**
+ * Payload for task_failed messages
+ */
+export interface TaskFailedPayload {
+ taskId: string;
+ error: string;
+ retryable: boolean;
+ stackTrace?: string | null;
+}
+
+/**
+ * Payload for capability_response messages
+ */
+export interface CapabilityResponsePayload {
+ agentId: string;
+ roleType: string;
+ platform: string;
+ environmentType?: string;
+ tools: string[];
+ languages?: string[];
+ apiAccess?: string[];
+ resourceLimits?: ResourceLimits;
+}
+
+/**
+ * Payload for error messages
+ */
+export interface ErrorPayload {
+ code: string;
+ message: string;
+ details?: Record | null;
+}
+
+/**
+ * Union type of all possible payloads
+ */
+export type MessagePayload =
+ | TaskAssignedPayload
+ | TaskProgressPayload
+ | TaskCompletedPayload
+ | TaskFailedPayload
+ | CapabilityResponsePayload
+ | ErrorPayload
+ | Record;
+
+/**
+ * Core message structure for all agent communications
+ */
+export interface Message {
+ /** Semantic version of the protocol (e.g., '1.0') */
+ protocolVersion: string;
+
+ /** Type of message being sent */
+ messageType: MessageType;
+
+ /** UUID of the sending agent */
+ senderId: string;
+
+ /** UUID of the recipient agent (null for broadcast) */
+ recipientId?: string | null;
+
+ /** UUID of the associated task (if applicable) */
+ taskId?: string | null;
+
+ /** Message priority (0=lowest, 10=highest) */
+ priority?: number;
+
+ /** ISO 8601 timestamp when message was created */
+ timestamp: string;
+
+ /** UUID for matching request/response pairs */
+ correlationId?: string | null;
+
+ /** Message-specific data (varies by messageType) */
+ payload?: MessagePayload;
+
+ /** Current delivery state of the message */
+ deliveryStatus?: DeliveryStatus;
+}
+
+/**
+ * Type guard to check if a value is a valid Message
+ */
+export function isMessage(value: unknown): value is Message {
+ if (typeof value !== 'object' || value === null) {
+ return false;
+ }
+
+ const msg = value as Partial;
+
+ return (
+ typeof msg.protocolVersion === 'string' &&
+ typeof msg.messageType === 'string' &&
+ Object.values(MessageType).includes(msg.messageType as MessageType) &&
+ typeof msg.senderId === 'string' &&
+ typeof msg.timestamp === 'string'
+ );
+}
+
+/**
+ * Type guard to check if a message type is task-related
+ */
+export function isTaskMessage(messageType: MessageType): boolean {
+ return [
+ MessageType.TASK_ASSIGNED,
+ MessageType.TASK_STARTED,
+ MessageType.TASK_BLOCKED,
+ MessageType.TASK_PROGRESS,
+ MessageType.TASK_COMPLETED,
+ MessageType.TASK_FAILED,
+ ].includes(messageType);
+}
+
+/**
+ * Type guard to check if a message type is capability-related
+ */
+export function isCapabilityMessage(messageType: MessageType): boolean {
+ return [
+ MessageType.CAPABILITY_QUERY,
+ MessageType.CAPABILITY_RESPONSE,
+ ].includes(messageType);
+}
+
+/**
+ * Type guard to check if a message type is status-related
+ */
+export function isStatusMessage(messageType: MessageType): boolean {
+ return [
+ MessageType.STATUS_QUERY,
+ MessageType.STATUS_RESPONSE,
+ ].includes(messageType);
+}
diff --git a/packages/mcp-server/src/protocol/MessageBuilder.ts b/packages/mcp-server/src/protocol/MessageBuilder.ts
new file mode 100644
index 0000000..71dbd2f
--- /dev/null
+++ b/packages/mcp-server/src/protocol/MessageBuilder.ts
@@ -0,0 +1,353 @@
+/**
+ * MessageBuilder - Fluent API for constructing CoorChat messages
+ */
+
+import { v4 as uuidv4 } from 'uuid';
+import {
+ Message,
+ MessageType,
+ DeliveryStatus,
+ MessagePayload,
+ TaskAssignedPayload,
+ TaskProgressPayload,
+ TaskCompletedPayload,
+ TaskFailedPayload,
+ CapabilityResponsePayload,
+ ErrorPayload,
+} from './Message.js';
+
+/**
+ * Default protocol version
+ */
+const DEFAULT_PROTOCOL_VERSION = '1.0';
+
+/**
+ * Default message priority
+ */
+const DEFAULT_PRIORITY = 5;
+
+/**
+ * Fluent builder for creating Message instances
+ */
+export class MessageBuilder {
+ private message: Partial;
+
+ constructor() {
+ this.message = {
+ protocolVersion: DEFAULT_PROTOCOL_VERSION,
+ priority: DEFAULT_PRIORITY,
+ timestamp: new Date().toISOString(),
+ deliveryStatus: DeliveryStatus.QUEUED,
+ };
+ }
+
+ /**
+ * Set the message type
+ */
+ type(messageType: MessageType): this {
+ this.message.messageType = messageType;
+ return this;
+ }
+
+ /**
+ * Set the sender ID
+ */
+ from(senderId: string): this {
+ this.message.senderId = senderId;
+ return this;
+ }
+
+ /**
+ * Set the recipient ID (null or undefined for broadcast)
+ */
+ to(recipientId: string | null | undefined): this {
+ this.message.recipientId = recipientId;
+ return this;
+ }
+
+ /**
+ * Set the task ID
+ */
+ forTask(taskId: string | null | undefined): this {
+ this.message.taskId = taskId;
+ return this;
+ }
+
+ /**
+ * Set the message priority (0-10)
+ */
+ priority(priority: number): this {
+ this.message.priority = Math.max(0, Math.min(10, priority));
+ return this;
+ }
+
+ /**
+ * Set the correlation ID for request/response matching
+ */
+ correlate(correlationId: string | null | undefined): this {
+ this.message.correlationId = correlationId;
+ return this;
+ }
+
+ /**
+ * Set the protocol version
+ */
+ version(protocolVersion: string): this {
+ this.message.protocolVersion = protocolVersion;
+ return this;
+ }
+
+ /**
+ * Set the message payload
+ */
+ payload(payload: MessagePayload): this {
+ this.message.payload = payload;
+ return this;
+ }
+
+ /**
+ * Set the delivery status
+ */
+ status(deliveryStatus: DeliveryStatus): this {
+ this.message.deliveryStatus = deliveryStatus;
+ return this;
+ }
+
+ /**
+ * Build and return the completed message
+ * @throws Error if required fields are missing
+ */
+ build(): Message {
+ if (!this.message.messageType) {
+ throw new Error('Message type is required');
+ }
+ if (!this.message.senderId) {
+ throw new Error('Sender ID is required');
+ }
+
+ return this.message as Message;
+ }
+
+ /**
+ * Create a task_assigned message
+ */
+ static taskAssigned(
+ senderId: string,
+ recipientId: string,
+ payload: TaskAssignedPayload
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.TASK_ASSIGNED)
+ .from(senderId)
+ .to(recipientId)
+ .forTask(payload.taskId)
+ .priority(7)
+ .payload(payload)
+ .build();
+ }
+
+ /**
+ * Create a task_started message
+ */
+ static taskStarted(
+ senderId: string,
+ taskId: string,
+ payload?: Record
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.TASK_STARTED)
+ .from(senderId)
+ .forTask(taskId)
+ .payload(payload || {})
+ .build();
+ }
+
+ /**
+ * Create a task_progress message
+ */
+ static taskProgress(
+ senderId: string,
+ payload: TaskProgressPayload
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.TASK_PROGRESS)
+ .from(senderId)
+ .forTask(payload.taskId)
+ .payload(payload)
+ .build();
+ }
+
+ /**
+ * Create a task_completed message
+ */
+ static taskCompleted(
+ senderId: string,
+ payload: TaskCompletedPayload
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.TASK_COMPLETED)
+ .from(senderId)
+ .forTask(payload.taskId)
+ .priority(8)
+ .payload(payload)
+ .build();
+ }
+
+ /**
+ * Create a task_failed message
+ */
+ static taskFailed(
+ senderId: string,
+ payload: TaskFailedPayload
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.TASK_FAILED)
+ .from(senderId)
+ .forTask(payload.taskId)
+ .priority(9)
+ .payload(payload)
+ .build();
+ }
+
+ /**
+ * Create a task_blocked message
+ */
+ static taskBlocked(
+ senderId: string,
+ taskId: string,
+ blockedBy: string[],
+ reason: string
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.TASK_BLOCKED)
+ .from(senderId)
+ .forTask(taskId)
+ .priority(7)
+ .payload({ blockedBy, reason })
+ .build();
+ }
+
+ /**
+ * Create a capability_query message
+ */
+ static capabilityQuery(
+ senderId: string,
+ recipientId?: string | null,
+ correlationId?: string
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.CAPABILITY_QUERY)
+ .from(senderId)
+ .to(recipientId)
+ .correlate(correlationId || uuidv4())
+ .payload({})
+ .build();
+ }
+
+ /**
+ * Create a capability_response message
+ */
+ static capabilityResponse(
+ senderId: string,
+ payload: CapabilityResponsePayload,
+ correlationId: string
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.CAPABILITY_RESPONSE)
+ .from(senderId)
+ .correlate(correlationId)
+ .payload(payload)
+ .build();
+ }
+
+ /**
+ * Create a status_query message
+ */
+ static statusQuery(
+ senderId: string,
+ recipientId?: string | null,
+ correlationId?: string
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.STATUS_QUERY)
+ .from(senderId)
+ .to(recipientId)
+ .correlate(correlationId || uuidv4())
+ .payload({})
+ .build();
+ }
+
+ /**
+ * Create a status_response message
+ */
+ static statusResponse(
+ senderId: string,
+ status: Record,
+ correlationId: string
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.STATUS_RESPONSE)
+ .from(senderId)
+ .correlate(correlationId)
+ .payload(status)
+ .build();
+ }
+
+ /**
+ * Create an error message
+ */
+ static error(
+ senderId: string,
+ payload: ErrorPayload,
+ priority: number = 9
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.ERROR)
+ .from(senderId)
+ .priority(priority)
+ .payload(payload)
+ .build();
+ }
+
+ /**
+ * Create a heartbeat message
+ */
+ static heartbeat(senderId: string): Message {
+ return new MessageBuilder()
+ .type(MessageType.HEARTBEAT)
+ .from(senderId)
+ .priority(1)
+ .payload({ timestamp: new Date().toISOString() })
+ .build();
+ }
+
+ /**
+ * Create an agent_joined message
+ */
+ static agentJoined(
+ senderId: string,
+ agentInfo: Record
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.AGENT_JOINED)
+ .from(senderId)
+ .priority(6)
+ .payload(agentInfo)
+ .build();
+ }
+
+ /**
+ * Create an agent_left message
+ */
+ static agentLeft(
+ senderId: string,
+ reason?: string
+ ): Message {
+ return new MessageBuilder()
+ .type(MessageType.AGENT_LEFT)
+ .from(senderId)
+ .priority(6)
+ .payload({ reason: reason || 'Normal disconnect' })
+ .build();
+ }
+}
diff --git a/packages/mcp-server/src/protocol/MessageValidator.ts b/packages/mcp-server/src/protocol/MessageValidator.ts
new file mode 100644
index 0000000..aeec4db
--- /dev/null
+++ b/packages/mcp-server/src/protocol/MessageValidator.ts
@@ -0,0 +1,271 @@
+/**
+ * MessageValidator - JSON Schema-based validation for CoorChat messages
+ */
+
+import Ajv, { ValidateFunction } from 'ajv';
+import addFormats from 'ajv-formats';
+import { Message, MessageType } from './Message.js';
+
+/**
+ * Message protocol JSON schema
+ * Based on specs/001-multi-agent-coordination/contracts/message-protocol.json
+ */
+const messageSchema = {
+ $schema: 'http://json-schema.org/draft-07/schema#',
+ $id: 'https://coorchat.dev/schemas/message-protocol/v1.0.json',
+ title: 'CoorChat Message Protocol',
+ description: 'JSON Schema for Multi-Agent Coordination System message format',
+ type: 'object',
+ required: ['protocolVersion', 'messageType', 'senderId', 'timestamp'],
+ properties: {
+ protocolVersion: {
+ type: 'string',
+ pattern: '^\\d+\\.\\d+$',
+ description: "Semantic version of the protocol (e.g., '1.0')",
+ },
+ messageType: {
+ type: 'string',
+ enum: [
+ 'task_assigned',
+ 'task_started',
+ 'task_blocked',
+ 'task_progress',
+ 'task_completed',
+ 'task_failed',
+ 'capability_query',
+ 'capability_response',
+ 'status_query',
+ 'status_response',
+ 'error',
+ 'heartbeat',
+ 'agent_joined',
+ 'agent_left',
+ ],
+ description: 'Type of message being sent',
+ },
+ senderId: {
+ type: 'string',
+ format: 'uuid',
+ description: 'UUID of the sending agent',
+ },
+ recipientId: {
+ type: ['string', 'null'],
+ format: 'uuid',
+ description: 'UUID of the recipient agent (null for broadcast)',
+ },
+ taskId: {
+ type: ['string', 'null'],
+ format: 'uuid',
+ description: 'UUID of the associated task (if applicable)',
+ },
+ priority: {
+ type: 'integer',
+ minimum: 0,
+ maximum: 10,
+ default: 5,
+ description: 'Message priority (0=lowest, 10=highest)',
+ },
+ timestamp: {
+ type: 'string',
+ format: 'date-time',
+ description: 'ISO 8601 timestamp when message was created',
+ },
+ correlationId: {
+ type: ['string', 'null'],
+ format: 'uuid',
+ description: 'UUID for matching request/response pairs',
+ },
+ payload: {
+ type: 'object',
+ description: 'Message-specific data (varies by messageType)',
+ },
+ deliveryStatus: {
+ type: 'string',
+ enum: ['queued', 'sending', 'sent', 'delivered', 'acknowledged', 'failed'],
+ default: 'queued',
+ description: 'Current delivery state of the message',
+ },
+ },
+};
+
+/**
+ * Validation error details
+ */
+export interface ValidationError {
+ field: string;
+ message: string;
+ value?: unknown;
+}
+
+/**
+ * Validation result
+ */
+export interface ValidationResult {
+ valid: boolean;
+ errors?: ValidationError[];
+}
+
+/**
+ * MessageValidator class for validating protocol compliance
+ */
+export class MessageValidator {
+ private ajv: Ajv;
+ private validateMessage: ValidateFunction;
+
+ constructor() {
+ this.ajv = new Ajv({
+ allErrors: true,
+ strict: false,
+ validateFormats: true,
+ });
+
+ // Add format validators (uuid, date-time, uri, etc.)
+ addFormats(this.ajv);
+
+ // Compile the message schema
+ this.validateMessage = this.ajv.compile(messageSchema);
+ }
+
+ /**
+ * Validate a message against the protocol schema
+ */
+ validate(message: unknown): ValidationResult {
+ const valid = this.validateMessage(message);
+
+ if (valid) {
+ return { valid: true };
+ }
+
+ const errors: ValidationError[] = (this.validateMessage.errors || []).map(
+ (err) => ({
+ field: err.instancePath || err.params?.missingProperty || 'unknown',
+ message: err.message || 'Validation failed',
+ value: err.data,
+ })
+ );
+
+ return { valid: false, errors };
+ }
+
+ /**
+ * Validate and throw on error
+ * @throws Error if validation fails
+ */
+ validateOrThrow(message: unknown): asserts message is Message {
+ const result = this.validate(message);
+
+ if (!result.valid) {
+ const errorDetails = result.errors!
+ .map((err) => `${err.field}: ${err.message}`)
+ .join(', ');
+ throw new Error(`Message validation failed: ${errorDetails}`);
+ }
+ }
+
+ /**
+ * Check if message type requires a taskId
+ */
+ static requiresTaskId(messageType: MessageType): boolean {
+ return [
+ MessageType.TASK_ASSIGNED,
+ MessageType.TASK_STARTED,
+ MessageType.TASK_BLOCKED,
+ MessageType.TASK_PROGRESS,
+ MessageType.TASK_COMPLETED,
+ MessageType.TASK_FAILED,
+ ].includes(messageType);
+ }
+
+ /**
+ * Check if message type requires a correlationId
+ */
+ static requiresCorrelationId(messageType: MessageType): boolean {
+ return [
+ MessageType.CAPABILITY_RESPONSE,
+ MessageType.STATUS_RESPONSE,
+ ].includes(messageType);
+ }
+
+ /**
+ * Validate message type-specific requirements
+ */
+ validateSemantics(message: Message): ValidationResult {
+ const errors: ValidationError[] = [];
+
+ // Check taskId requirement
+ if (MessageValidator.requiresTaskId(message.messageType) && !message.taskId) {
+ errors.push({
+ field: 'taskId',
+ message: `Message type ${message.messageType} requires a taskId`,
+ });
+ }
+
+ // Check correlationId requirement
+ if (
+ MessageValidator.requiresCorrelationId(message.messageType) &&
+ !message.correlationId
+ ) {
+ errors.push({
+ field: 'correlationId',
+ message: `Message type ${message.messageType} requires a correlationId`,
+ });
+ }
+
+ // Check priority range
+ if (message.priority !== undefined && (message.priority < 0 || message.priority > 10)) {
+ errors.push({
+ field: 'priority',
+ message: 'Priority must be between 0 and 10',
+ value: message.priority,
+ });
+ }
+
+ // Validate protocol version format
+ if (!/^\d+\.\d+$/.test(message.protocolVersion)) {
+ errors.push({
+ field: 'protocolVersion',
+ message: 'Protocol version must be in format "major.minor" (e.g., "1.0")',
+ value: message.protocolVersion,
+ });
+ }
+
+ if (errors.length > 0) {
+ return { valid: false, errors };
+ }
+
+ return { valid: true };
+ }
+
+ /**
+ * Perform full validation (schema + semantics)
+ */
+ validateFull(message: unknown): ValidationResult {
+ // First, validate against JSON schema
+ const schemaResult = this.validate(message);
+ if (!schemaResult.valid) {
+ return schemaResult;
+ }
+
+ // Then validate semantic rules
+ return this.validateSemantics(message as Message);
+ }
+
+ /**
+ * Get validation error summary
+ */
+ getErrorSummary(result: ValidationResult): string {
+ if (result.valid) {
+ return 'Valid';
+ }
+
+ return (
+ result.errors?.map((err) => `${err.field}: ${err.message}`).join('; ') ||
+ 'Unknown validation error'
+ );
+ }
+}
+
+/**
+ * Singleton validator instance
+ */
+export const validator = new MessageValidator();
diff --git a/packages/mcp-server/src/protocol/VersionManager.ts b/packages/mcp-server/src/protocol/VersionManager.ts
new file mode 100644
index 0000000..7063a56
--- /dev/null
+++ b/packages/mcp-server/src/protocol/VersionManager.ts
@@ -0,0 +1,304 @@
+/**
+ * VersionManager - Protocol versioning and backward compatibility
+ *
+ * Supports backward compatibility for 1 major version:
+ * - Version 1.x can communicate with version 1.y
+ * - Version 2.x can communicate with version 1.y (with feature degradation)
+ * - Version 3.x cannot communicate with version 1.y
+ */
+
+import { Message } from './Message.js';
+
+/**
+ * Protocol version structure
+ */
+export interface ProtocolVersion {
+ major: number;
+ minor: number;
+}
+
+/**
+ * Version compatibility result
+ */
+export interface CompatibilityResult {
+ compatible: boolean;
+ requiresDowngrade: boolean;
+ targetVersion?: string;
+ reason?: string;
+}
+
+/**
+ * Version feature support
+ */
+export interface VersionFeatures {
+ version: string;
+ supportedMessageTypes: string[];
+ requiredFields: string[];
+ optionalFields: string[];
+ deprecatedFields: string[];
+}
+
+/**
+ * VersionManager class for protocol versioning
+ */
+export class VersionManager {
+ /**
+ * Current protocol version
+ */
+ public static readonly CURRENT_VERSION = '1.0';
+
+ /**
+ * Minimum supported version (backward compatibility limit)
+ */
+ public static readonly MIN_SUPPORTED_VERSION = '1.0';
+
+ /**
+ * Maximum major version difference for compatibility
+ */
+ public static readonly MAX_MAJOR_VERSION_DIFF = 1;
+
+ /**
+ * Version feature matrix
+ */
+ private static readonly VERSION_FEATURES: Map = new Map([
+ [
+ '1.0',
+ {
+ version: '1.0',
+ supportedMessageTypes: [
+ 'task_assigned',
+ 'task_started',
+ 'task_blocked',
+ 'task_progress',
+ 'task_completed',
+ 'task_failed',
+ 'capability_query',
+ 'capability_response',
+ 'status_query',
+ 'status_response',
+ 'error',
+ 'heartbeat',
+ 'agent_joined',
+ 'agent_left',
+ ],
+ requiredFields: ['protocolVersion', 'messageType', 'senderId', 'timestamp'],
+ optionalFields: [
+ 'recipientId',
+ 'taskId',
+ 'priority',
+ 'correlationId',
+ 'payload',
+ 'deliveryStatus',
+ ],
+ deprecatedFields: [],
+ },
+ ],
+ ]);
+
+ /**
+ * Parse version string to components
+ */
+ static parseVersion(version: string): ProtocolVersion {
+ const match = version.match(/^(\d+)\.(\d+)$/);
+ if (!match) {
+ throw new Error(`Invalid version format: ${version}`);
+ }
+
+ return {
+ major: parseInt(match[1], 10),
+ minor: parseInt(match[2], 10),
+ };
+ }
+
+ /**
+ * Format version components to string
+ */
+ static formatVersion(version: ProtocolVersion): string {
+ return `${version.major}.${version.minor}`;
+ }
+
+ /**
+ * Compare two versions
+ * @returns -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
+ */
+ static compareVersions(v1: string, v2: string): number {
+ const ver1 = this.parseVersion(v1);
+ const ver2 = this.parseVersion(v2);
+
+ if (ver1.major !== ver2.major) {
+ return ver1.major < ver2.major ? -1 : 1;
+ }
+
+ if (ver1.minor !== ver2.minor) {
+ return ver1.minor < ver2.minor ? -1 : 1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Check if two versions are compatible
+ */
+ static areVersionsCompatible(
+ localVersion: string,
+ remoteVersion: string
+ ): CompatibilityResult {
+ const local = this.parseVersion(localVersion);
+ const remote = this.parseVersion(remoteVersion);
+
+ // Same version - fully compatible
+ if (local.major === remote.major && local.minor === remote.minor) {
+ return { compatible: true, requiresDowngrade: false };
+ }
+
+ // Same major version - compatible (minor version differences allowed)
+ if (local.major === remote.major) {
+ return {
+ compatible: true,
+ requiresDowngrade: local.minor > remote.minor,
+ targetVersion: remote.minor < local.minor ? remoteVersion : undefined,
+ };
+ }
+
+ // Different major versions - check if within compatibility window
+ const majorDiff = Math.abs(local.major - remote.major);
+ if (majorDiff <= this.MAX_MAJOR_VERSION_DIFF) {
+ // Backward compatibility: newer version can downgrade to older
+ if (local.major > remote.major) {
+ return {
+ compatible: true,
+ requiresDowngrade: true,
+ targetVersion: remoteVersion,
+ reason: `Downgrading from ${localVersion} to ${remoteVersion} for compatibility`,
+ };
+ }
+
+ // Forward compatibility: older version can receive newer messages
+ // (with potential field degradation)
+ return {
+ compatible: true,
+ requiresDowngrade: false,
+ reason: `Receiving messages from newer version ${remoteVersion}, some features may be unavailable`,
+ };
+ }
+
+ // Too many major versions apart - incompatible
+ return {
+ compatible: false,
+ requiresDowngrade: false,
+ reason: `Version ${localVersion} is incompatible with ${remoteVersion} (major version difference: ${majorDiff})`,
+ };
+ }
+
+ /**
+ * Get features supported by a version
+ */
+ static getVersionFeatures(version: string): VersionFeatures | undefined {
+ return this.VERSION_FEATURES.get(version);
+ }
+
+ /**
+ * Check if a message type is supported in a version
+ */
+ static isMessageTypeSupported(version: string, messageType: string): boolean {
+ const features = this.getVersionFeatures(version);
+ return features?.supportedMessageTypes.includes(messageType) ?? false;
+ }
+
+ /**
+ * Downgrade message to target version
+ * Removes fields not supported in target version
+ */
+ static downgradeMessage(message: Message, targetVersion: string): Message {
+ const targetFeatures = this.getVersionFeatures(targetVersion);
+ if (!targetFeatures) {
+ throw new Error(`Unknown target version: ${targetVersion}`);
+ }
+
+ // Create a copy of the message
+ const downgraded = { ...message };
+
+ // Update protocol version
+ downgraded.protocolVersion = targetVersion;
+
+ // Remove deprecated fields
+ for (const field of targetFeatures.deprecatedFields) {
+ delete (downgraded as Record)[field];
+ }
+
+ // Check if message type is supported
+ if (!targetFeatures.supportedMessageTypes.includes(message.messageType)) {
+ throw new Error(
+ `Message type ${message.messageType} is not supported in version ${targetVersion}`
+ );
+ }
+
+ return downgraded;
+ }
+
+ /**
+ * Upgrade message from older version
+ * Adds default values for new fields
+ */
+ static upgradeMessage(message: Message, targetVersion: string): Message {
+ const targetFeatures = this.getVersionFeatures(targetVersion);
+ if (!targetFeatures) {
+ throw new Error(`Unknown target version: ${targetVersion}`);
+ }
+
+ // Create a copy of the message
+ const upgraded = { ...message };
+
+ // Update protocol version
+ upgraded.protocolVersion = targetVersion;
+
+ // Add default values for new optional fields if missing
+ // (In v1.0, all fields are already defined, so this is a placeholder for future versions)
+
+ return upgraded;
+ }
+
+ /**
+ * Negotiate protocol version between two agents
+ * Returns the highest mutually compatible version
+ */
+ static negotiateVersion(localVersion: string, remoteVersion: string): string | null {
+ const compatibility = this.areVersionsCompatible(localVersion, remoteVersion);
+
+ if (!compatibility.compatible) {
+ return null;
+ }
+
+ // Use the lower version for communication
+ return this.compareVersions(localVersion, remoteVersion) <= 0
+ ? localVersion
+ : remoteVersion;
+ }
+
+ /**
+ * Validate version string format
+ */
+ static isValidVersion(version: string): boolean {
+ try {
+ this.parseVersion(version);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ /**
+ * Get version info
+ */
+ static getVersionInfo(): {
+ current: string;
+ minSupported: string;
+ maxMajorDiff: number;
+ } {
+ return {
+ current: this.CURRENT_VERSION,
+ minSupported: this.MIN_SUPPORTED_VERSION,
+ maxMajorDiff: this.MAX_MAJOR_VERSION_DIFF,
+ };
+ }
+}
diff --git a/packages/mcp-server/src/tasks/ConflictResolver.ts b/packages/mcp-server/src/tasks/ConflictResolver.ts
new file mode 100644
index 0000000..9b97947
--- /dev/null
+++ b/packages/mcp-server/src/tasks/ConflictResolver.ts
@@ -0,0 +1,266 @@
+/**
+ * ConflictResolver - Timestamp-based first-come-first-served conflict resolution
+ * Resolves conflicts when multiple agents claim the same task simultaneously
+ */
+
+import type { Task } from './Task.js';
+import type { Agent } from '../agents/Agent.js';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Task claim
+ */
+export interface TaskClaim {
+ /** Task being claimed */
+ taskId: string;
+
+ /** Agent claiming the task */
+ agentId: string;
+
+ /** Timestamp when claim was made */
+ claimedAt: Date;
+
+ /** Correlation ID for idempotency */
+ correlationId?: string;
+}
+
+/**
+ * Conflict resolution result
+ */
+export interface ConflictResolution {
+ /** Winner of the conflict */
+ winner: TaskClaim;
+
+ /** Losers of the conflict */
+ losers: TaskClaim[];
+
+ /** Reason for resolution */
+ reason: string;
+}
+
+/**
+ * Conflict resolver configuration
+ */
+export interface ConflictResolverConfig {
+ /** Logger */
+ logger?: Logger;
+
+ /** Time window for detecting simultaneous claims (ms) */
+ simultaneousWindowMs?: number;
+}
+
+/**
+ * ConflictResolver class
+ */
+export class ConflictResolver {
+ private logger: Logger;
+ private simultaneousWindowMs: number;
+ private pendingClaims: Map; // taskId β claims
+ private seenCorrelationIds: Set; // For idempotency
+
+ constructor(config: ConflictResolverConfig = {}) {
+ this.logger = config.logger || createLogger();
+ this.simultaneousWindowMs = config.simultaneousWindowMs || 1000; // 1 second
+ this.pendingClaims = new Map();
+ this.seenCorrelationIds = new Set();
+ }
+
+ /**
+ * Register a task claim
+ * Returns winner immediately if no conflict, or after resolution window
+ */
+ async registerClaim(claim: TaskClaim): Promise {
+ // Check for duplicate claim (idempotency)
+ if (claim.correlationId && this.seenCorrelationIds.has(claim.correlationId)) {
+ this.logger.debug('Duplicate claim ignored (idempotency)', {
+ taskId: claim.taskId,
+ agentId: claim.agentId,
+ correlationId: claim.correlationId,
+ });
+ return null;
+ }
+
+ // Record correlation ID
+ if (claim.correlationId) {
+ this.seenCorrelationIds.add(claim.correlationId);
+ }
+
+ // Get or create claims list for this task
+ let claims = this.pendingClaims.get(claim.taskId);
+ if (!claims) {
+ claims = [];
+ this.pendingClaims.set(claim.taskId, claims);
+ }
+
+ // Add claim to list
+ claims.push(claim);
+
+ this.logger.debug('Task claim registered', {
+ taskId: claim.taskId,
+ agentId: claim.agentId,
+ claimedAt: claim.claimedAt,
+ totalClaims: claims.length,
+ });
+
+ // Wait for simultaneous window to detect conflicts
+ await this.waitForConflicts(claim.taskId);
+
+ // Resolve conflicts
+ return this.resolveClaims(claim.taskId);
+ }
+
+ /**
+ * Wait for potential simultaneous claims
+ */
+ private async waitForConflicts(taskId: string): Promise {
+ return new Promise((resolve) => {
+ setTimeout(resolve, this.simultaneousWindowMs);
+ });
+ }
+
+ /**
+ * Resolve claims for a task
+ */
+ private resolveClaims(taskId: string): ConflictResolution | null {
+ const claims = this.pendingClaims.get(taskId);
+ if (!claims || claims.length === 0) {
+ return null;
+ }
+
+ // Remove from pending
+ this.pendingClaims.delete(taskId);
+
+ // Single claim - no conflict
+ if (claims.length === 1) {
+ this.logger.debug('No conflict - single claim', {
+ taskId,
+ agentId: claims[0].agentId,
+ });
+ return {
+ winner: claims[0],
+ losers: [],
+ reason: 'No conflict - single claimant',
+ };
+ }
+
+ // Multiple claims - resolve by earliest timestamp
+ const sorted = claims.sort(
+ (a, b) => a.claimedAt.getTime() - b.claimedAt.getTime()
+ );
+
+ const winner = sorted[0];
+ const losers = sorted.slice(1);
+
+ this.logger.info('Conflict resolved', {
+ taskId,
+ totalClaims: claims.length,
+ winner: winner.agentId,
+ losers: losers.map((c) => c.agentId),
+ timeDifference: sorted[sorted.length - 1].claimedAt.getTime() - winner.claimedAt.getTime(),
+ });
+
+ return {
+ winner,
+ losers,
+ reason: `First-come-first-served: ${winner.agentId} claimed at ${winner.claimedAt.toISOString()}`,
+ };
+ }
+
+ /**
+ * Check if a claim would create a conflict
+ */
+ wouldConflict(taskId: string): boolean {
+ const claims = this.pendingClaims.get(taskId);
+ return claims !== undefined && claims.length > 0;
+ }
+
+ /**
+ * Get pending claims for a task
+ */
+ getPendingClaims(taskId: string): TaskClaim[] {
+ return this.pendingClaims.get(taskId) || [];
+ }
+
+ /**
+ * Cancel a claim (e.g., if agent disconnects before resolution)
+ */
+ cancelClaim(taskId: string, agentId: string): boolean {
+ const claims = this.pendingClaims.get(taskId);
+ if (!claims) {
+ return false;
+ }
+
+ const index = claims.findIndex((c) => c.agentId === agentId);
+ if (index !== -1) {
+ claims.splice(index, 1);
+ this.logger.info('Claim cancelled', { taskId, agentId });
+
+ // Remove task from pending if no more claims
+ if (claims.length === 0) {
+ this.pendingClaims.delete(taskId);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all pending claims
+ */
+ clear(): void {
+ this.pendingClaims.clear();
+ this.seenCorrelationIds.clear();
+ this.logger.info('All pending claims cleared');
+ }
+
+ /**
+ * Clean up old correlation IDs (to prevent memory leak)
+ */
+ cleanupCorrelationIds(): void {
+ // In a production system, you'd want to expire these after some time
+ // For now, we'll keep a simple size limit
+ if (this.seenCorrelationIds.size > 10000) {
+ this.seenCorrelationIds.clear();
+ this.logger.info('Correlation ID cache cleared');
+ }
+ }
+
+ /**
+ * Get statistics
+ */
+ getStats(): {
+ pendingTasks: number;
+ totalPendingClaims: number;
+ cachedCorrelationIds: number;
+ } {
+ const totalClaims = Array.from(this.pendingClaims.values()).reduce(
+ (sum, claims) => sum + claims.length,
+ 0
+ );
+
+ return {
+ pendingTasks: this.pendingClaims.size,
+ totalPendingClaims: totalClaims,
+ cachedCorrelationIds: this.seenCorrelationIds.size,
+ };
+ }
+}
+
+/**
+ * Create a task claim
+ */
+export function createTaskClaim(
+ taskId: string,
+ agentId: string,
+ correlationId?: string
+): TaskClaim {
+ return {
+ taskId,
+ agentId,
+ claimedAt: new Date(),
+ correlationId,
+ };
+}
diff --git a/packages/mcp-server/src/tasks/DependencyTracker.ts b/packages/mcp-server/src/tasks/DependencyTracker.ts
new file mode 100644
index 0000000..c16a0cd
--- /dev/null
+++ b/packages/mcp-server/src/tasks/DependencyTracker.ts
@@ -0,0 +1,390 @@
+/**
+ * DependencyTracker - Track task dependencies and notify when dependencies complete
+ * Manages task dependency graph and triggers notifications for unblocked tasks
+ */
+
+import type { Task, TaskStatus } from './Task.js';
+import { TaskStatus as TaskStatusEnum, isTaskTerminal } from './Task.js';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Dependency event
+ */
+export interface DependencyEvent {
+ /** Task that was unblocked */
+ task: Task;
+
+ /** Dependencies that were completed */
+ completedDependencies: string[];
+
+ /** Timestamp */
+ timestamp: Date;
+}
+
+/**
+ * Dependency event handler
+ */
+export type DependencyEventHandler = (event: DependencyEvent) => void | Promise;
+
+/**
+ * Dependency tracker configuration
+ */
+export interface DependencyTrackerConfig {
+ /** Logger */
+ logger?: Logger;
+}
+
+/**
+ * Dependency graph node
+ */
+interface DependencyNode {
+ taskId: string;
+ dependencies: Set; // Task IDs this task depends on
+ dependents: Set; // Task IDs that depend on this task
+ status: TaskStatus;
+}
+
+/**
+ * DependencyTracker class
+ */
+export class DependencyTracker {
+ private logger: Logger;
+ private nodes: Map; // taskId β node
+ private eventHandlers: Set;
+
+ constructor(config: DependencyTrackerConfig = {}) {
+ this.logger = config.logger || createLogger();
+ this.nodes = new Map();
+ this.eventHandlers = new Set();
+ }
+
+ /**
+ * Add task to dependency graph
+ */
+ addTask(task: Task): void {
+ // Create or update node
+ let node = this.nodes.get(task.id);
+ if (!node) {
+ node = {
+ taskId: task.id,
+ dependencies: new Set(task.dependencies),
+ dependents: new Set(),
+ status: task.status,
+ };
+ this.nodes.set(task.id, node);
+ } else {
+ node.dependencies = new Set(task.dependencies);
+ node.status = task.status;
+ }
+
+ // Update reverse dependencies (dependents)
+ for (const depId of task.dependencies) {
+ let depNode = this.nodes.get(depId);
+ if (!depNode) {
+ // Create placeholder node for dependency
+ depNode = {
+ taskId: depId,
+ dependencies: new Set(),
+ dependents: new Set(),
+ status: TaskStatusEnum.AVAILABLE,
+ };
+ this.nodes.set(depId, depNode);
+ }
+ depNode.dependents.add(task.id);
+ }
+
+ this.logger.debug('Task added to dependency tracker', {
+ taskId: task.id,
+ dependencies: task.dependencies,
+ status: task.status,
+ });
+ }
+
+ /**
+ * Update task status
+ */
+ async updateTaskStatus(taskId: string, status: TaskStatus): Promise {
+ const node = this.nodes.get(taskId);
+ if (!node) {
+ this.logger.warn('Task not found in dependency tracker', { taskId });
+ return;
+ }
+
+ const previousStatus = node.status;
+ node.status = status;
+
+ this.logger.debug('Task status updated', {
+ taskId,
+ previousStatus,
+ newStatus: status,
+ });
+
+ // If task is now completed, check if any dependents are unblocked
+ if (status === TaskStatusEnum.COMPLETED) {
+ await this.checkUnblockedDependents(taskId);
+ }
+ }
+
+ /**
+ * Check if completing a task unblocks any dependents
+ */
+ private async checkUnblockedDependents(completedTaskId: string): Promise {
+ const node = this.nodes.get(completedTaskId);
+ if (!node) {
+ return;
+ }
+
+ // Check each dependent
+ for (const dependentId of node.dependents) {
+ const dependentNode = this.nodes.get(dependentId);
+ if (!dependentNode) {
+ continue;
+ }
+
+ // Check if all dependencies are completed
+ const allDepsCompleted = Array.from(dependentNode.dependencies).every(
+ (depId) => {
+ const depNode = this.nodes.get(depId);
+ return depNode?.status === TaskStatusEnum.COMPLETED;
+ }
+ );
+
+ if (allDepsCompleted && dependentNode.status === TaskStatusEnum.AVAILABLE) {
+ // Task is unblocked!
+ const completedDeps = Array.from(dependentNode.dependencies);
+
+ // Reconstruct task for event (simplified)
+ const task: Partial = {
+ id: dependentNode.taskId,
+ status: dependentNode.status,
+ dependencies: completedDeps,
+ };
+
+ await this.notifyHandlers({
+ task: task as Task,
+ completedDependencies: completedDeps,
+ timestamp: new Date(),
+ });
+
+ this.logger.info('Task unblocked', {
+ taskId: dependentId,
+ completedDependencies: completedDeps,
+ });
+ }
+ }
+ }
+
+ /**
+ * Check if a task has all dependencies completed
+ */
+ areDependenciesCompleted(taskId: string): boolean {
+ const node = this.nodes.get(taskId);
+ if (!node) {
+ return false;
+ }
+
+ return Array.from(node.dependencies).every((depId) => {
+ const depNode = this.nodes.get(depId);
+ return depNode?.status === TaskStatusEnum.COMPLETED;
+ });
+ }
+
+ /**
+ * Get blocking dependencies for a task
+ */
+ getBlockingDependencies(taskId: string): string[] {
+ const node = this.nodes.get(taskId);
+ if (!node) {
+ return [];
+ }
+
+ return Array.from(node.dependencies).filter((depId) => {
+ const depNode = this.nodes.get(depId);
+ return depNode?.status !== TaskStatusEnum.COMPLETED;
+ });
+ }
+
+ /**
+ * Get all dependents of a task
+ */
+ getDependents(taskId: string): string[] {
+ const node = this.nodes.get(taskId);
+ if (!node) {
+ return [];
+ }
+
+ return Array.from(node.dependents);
+ }
+
+ /**
+ * Get task dependency chain (recursive)
+ */
+ getDependencyChain(taskId: string, visited: Set = new Set()): string[] {
+ if (visited.has(taskId)) {
+ // Circular dependency detected
+ this.logger.warn('Circular dependency detected', { taskId });
+ return [];
+ }
+
+ visited.add(taskId);
+
+ const node = this.nodes.get(taskId);
+ if (!node) {
+ return [];
+ }
+
+ const chain: string[] = [];
+ for (const depId of node.dependencies) {
+ chain.push(depId);
+ chain.push(...this.getDependencyChain(depId, visited));
+ }
+
+ return chain;
+ }
+
+ /**
+ * Detect circular dependencies
+ */
+ detectCircularDependencies(): string[][] {
+ const cycles: string[][] = [];
+
+ for (const [taskId] of this.nodes) {
+ const visited = new Set();
+ const path: string[] = [];
+
+ const hasCycle = this.detectCycleFromNode(taskId, visited, path);
+ if (hasCycle && path.length > 0) {
+ cycles.push([...path]);
+ }
+ }
+
+ return cycles;
+ }
+
+ /**
+ * Detect cycle from a specific node (DFS)
+ */
+ private detectCycleFromNode(
+ taskId: string,
+ visited: Set,
+ path: string[]
+ ): boolean {
+ if (path.includes(taskId)) {
+ // Found a cycle
+ path.push(taskId);
+ return true;
+ }
+
+ if (visited.has(taskId)) {
+ return false;
+ }
+
+ visited.add(taskId);
+ path.push(taskId);
+
+ const node = this.nodes.get(taskId);
+ if (node) {
+ for (const depId of node.dependencies) {
+ if (this.detectCycleFromNode(depId, visited, path)) {
+ return true;
+ }
+ }
+ }
+
+ path.pop();
+ return false;
+ }
+
+ /**
+ * Remove task from tracker
+ */
+ removeTask(taskId: string): void {
+ const node = this.nodes.get(taskId);
+ if (!node) {
+ return;
+ }
+
+ // Remove from dependents' dependency lists
+ for (const depId of node.dependencies) {
+ const depNode = this.nodes.get(depId);
+ if (depNode) {
+ depNode.dependents.delete(taskId);
+ }
+ }
+
+ // Remove from dependencies' dependent lists
+ for (const dependentId of node.dependents) {
+ const dependentNode = this.nodes.get(dependentId);
+ if (dependentNode) {
+ dependentNode.dependencies.delete(taskId);
+ }
+ }
+
+ this.nodes.delete(taskId);
+ this.logger.debug('Task removed from dependency tracker', { taskId });
+ }
+
+ /**
+ * Clear all tasks
+ */
+ clear(): void {
+ this.nodes.clear();
+ this.logger.info('Dependency tracker cleared');
+ }
+
+ /**
+ * Register dependency event handler
+ */
+ onDependencyResolved(handler: DependencyEventHandler): () => void {
+ this.eventHandlers.add(handler);
+ return () => this.eventHandlers.delete(handler);
+ }
+
+ /**
+ * Notify handlers
+ */
+ private async notifyHandlers(event: DependencyEvent): Promise {
+ const promises = Array.from(this.eventHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in dependency handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Get statistics
+ */
+ getStats(): {
+ totalTasks: number;
+ tasksWithDependencies: number;
+ averageDependencies: number;
+ circularDependencies: number;
+ } {
+ const tasksWithDeps = Array.from(this.nodes.values()).filter(
+ (node) => node.dependencies.size > 0
+ ).length;
+
+ const totalDeps = Array.from(this.nodes.values()).reduce(
+ (sum, node) => sum + node.dependencies.size,
+ 0
+ );
+
+ const avgDeps = this.nodes.size > 0 ? totalDeps / this.nodes.size : 0;
+
+ const cycles = this.detectCircularDependencies();
+
+ return {
+ totalTasks: this.nodes.size,
+ tasksWithDependencies: tasksWithDeps,
+ averageDependencies: Math.round(avgDeps * 100) / 100,
+ circularDependencies: cycles.length,
+ };
+ }
+}
diff --git a/packages/mcp-server/src/tasks/Task.ts b/packages/mcp-server/src/tasks/Task.ts
new file mode 100644
index 0000000..55fc13c
--- /dev/null
+++ b/packages/mcp-server/src/tasks/Task.ts
@@ -0,0 +1,369 @@
+/**
+ * Task - Represents a work item from GitHub repository
+ * Based on specs/001-multi-agent-coordination/data-model.md
+ */
+
+/**
+ * Task status states
+ */
+export enum TaskStatus {
+ AVAILABLE = 'available',
+ ASSIGNED = 'assigned',
+ STARTED = 'started',
+ IN_PROGRESS = 'in_progress',
+ BLOCKED = 'blocked',
+ COMPLETED = 'completed',
+ FAILED = 'failed',
+}
+
+/**
+ * Task entity
+ */
+export interface Task {
+ /** Unique task identifier (UUID v4) */
+ id: string;
+
+ /** Task description */
+ description: string;
+
+ /** Array of assigned agent IDs */
+ assignedAgents: string[];
+
+ /** Task state */
+ status: TaskStatus;
+
+ /** Array of task IDs this task depends on */
+ dependencies: string[];
+
+ /** GitHub issue number */
+ githubIssueId: string;
+
+ /** Full GitHub issue URL */
+ githubIssueUrl: string;
+
+ /** Associated PR URL (optional) */
+ githubPRUrl?: string | null;
+
+ /** When task was created */
+ createdAt: Date;
+
+ /** When task was assigned (optional) */
+ assignedAt?: Date | null;
+
+ /** When work started (optional) */
+ startedAt?: Date | null;
+
+ /** When work finished (optional) */
+ completedAt?: Date | null;
+
+ /** Timestamp for conflict resolution */
+ claimedAt?: Date | null;
+
+ /** Progress percentage (0-100) */
+ percentComplete?: number;
+
+ /** Current status message */
+ statusMessage?: string;
+}
+
+/**
+ * Task creation data
+ */
+export interface TaskCreation {
+ /** Task description */
+ description: string;
+
+ /** Array of task IDs this task depends on */
+ dependencies?: string[];
+
+ /** GitHub issue number */
+ githubIssueId: string;
+
+ /** Full GitHub issue URL */
+ githubIssueUrl: string;
+}
+
+/**
+ * Task update data
+ */
+export interface TaskUpdate {
+ /** Update task status */
+ status?: TaskStatus;
+
+ /** Update assigned agents */
+ assignedAgents?: string[];
+
+ /** Update GitHub PR URL */
+ githubPRUrl?: string | null;
+
+ /** Update assigned timestamp */
+ assignedAt?: Date | null;
+
+ /** Update started timestamp */
+ startedAt?: Date | null;
+
+ /** Update completed timestamp */
+ completedAt?: Date | null;
+
+ /** Update progress percentage */
+ percentComplete?: number;
+
+ /** Update status message */
+ statusMessage?: string;
+}
+
+/**
+ * Task query filter
+ */
+export interface TaskQuery {
+ /** Filter by task status */
+ status?: TaskStatus;
+
+ /** Filter by assigned agent ID */
+ assignedToAgent?: string;
+
+ /** Filter by whether task has dependencies */
+ hasDependencies?: boolean;
+
+ /** Filter by whether dependencies are complete */
+ dependenciesComplete?: boolean;
+
+ /** Filter by GitHub issue ID */
+ githubIssueId?: string;
+
+ /** Filter by creation date range */
+ createdAfter?: Date;
+ createdBefore?: Date;
+}
+
+/**
+ * Validate task object
+ */
+export function validateTask(task: unknown): task is Task {
+ if (typeof task !== 'object' || task === null) {
+ return false;
+ }
+
+ const t = task as Partial;
+
+ return (
+ typeof t.id === 'string' &&
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
+ t.id
+ ) &&
+ typeof t.description === 'string' &&
+ t.description.length > 0 &&
+ t.description.length <= 500 &&
+ Array.isArray(t.assignedAgents) &&
+ t.assignedAgents.every((id) => typeof id === 'string') &&
+ typeof t.status === 'string' &&
+ Object.values(TaskStatus).includes(t.status as TaskStatus) &&
+ Array.isArray(t.dependencies) &&
+ t.dependencies.every((id) => typeof id === 'string') &&
+ typeof t.githubIssueId === 'string' &&
+ typeof t.githubIssueUrl === 'string' &&
+ t.createdAt instanceof Date
+ );
+}
+
+/**
+ * Check if a task matches a query
+ */
+export function matchesTaskQuery(
+ task: Task,
+ query: TaskQuery,
+ allTasks?: Map
+): boolean {
+ if (query.status && task.status !== query.status) {
+ return false;
+ }
+
+ if (query.assignedToAgent && !task.assignedAgents.includes(query.assignedToAgent)) {
+ return false;
+ }
+
+ if (query.hasDependencies !== undefined) {
+ const hasDeps = task.dependencies.length > 0;
+ if (query.hasDependencies !== hasDeps) {
+ return false;
+ }
+ }
+
+ if (query.dependenciesComplete !== undefined && allTasks) {
+ const depsComplete = task.dependencies.every((depId) => {
+ const dep = allTasks.get(depId);
+ return dep?.status === TaskStatus.COMPLETED;
+ });
+ if (query.dependenciesComplete !== depsComplete) {
+ return false;
+ }
+ }
+
+ if (query.githubIssueId && task.githubIssueId !== query.githubIssueId) {
+ return false;
+ }
+
+ if (query.createdAfter && task.createdAt < query.createdAfter) {
+ return false;
+ }
+
+ if (query.createdBefore && task.createdAt > query.createdBefore) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Create a task from creation data
+ */
+export function createTask(id: string, data: TaskCreation): Task {
+ return {
+ id,
+ description: data.description,
+ assignedAgents: [],
+ status: TaskStatus.AVAILABLE,
+ dependencies: data.dependencies || [],
+ githubIssueId: data.githubIssueId,
+ githubIssueUrl: data.githubIssueUrl,
+ githubPRUrl: null,
+ createdAt: new Date(),
+ assignedAt: null,
+ startedAt: null,
+ completedAt: null,
+ claimedAt: null,
+ percentComplete: 0,
+ };
+}
+
+/**
+ * Update a task with partial data
+ */
+export function updateTask(task: Task, update: TaskUpdate): Task {
+ return {
+ ...task,
+ ...(update.status !== undefined && { status: update.status }),
+ ...(update.assignedAgents !== undefined && {
+ assignedAgents: update.assignedAgents,
+ }),
+ ...(update.githubPRUrl !== undefined && { githubPRUrl: update.githubPRUrl }),
+ ...(update.assignedAt !== undefined && { assignedAt: update.assignedAt }),
+ ...(update.startedAt !== undefined && { startedAt: update.startedAt }),
+ ...(update.completedAt !== undefined && { completedAt: update.completedAt }),
+ ...(update.percentComplete !== undefined && {
+ percentComplete: update.percentComplete,
+ }),
+ ...(update.statusMessage !== undefined && {
+ statusMessage: update.statusMessage,
+ }),
+ };
+}
+
+/**
+ * Assign a task to an agent
+ */
+export function assignTask(task: Task, agentId: string): Task {
+ return updateTask(task, {
+ status: TaskStatus.ASSIGNED,
+ assignedAgents: [...task.assignedAgents, agentId],
+ assignedAt: new Date(),
+ });
+}
+
+/**
+ * Start a task
+ */
+export function startTask(task: Task): Task {
+ return updateTask(task, {
+ status: TaskStatus.STARTED,
+ startedAt: new Date(),
+ percentComplete: 0,
+ });
+}
+
+/**
+ * Mark task as in progress
+ */
+export function progressTask(
+ task: Task,
+ percentComplete: number,
+ statusMessage?: string
+): Task {
+ return updateTask(task, {
+ status: TaskStatus.IN_PROGRESS,
+ percentComplete: Math.max(0, Math.min(100, percentComplete)),
+ statusMessage,
+ });
+}
+
+/**
+ * Block a task
+ */
+export function blockTask(task: Task, reason: string): Task {
+ return updateTask(task, {
+ status: TaskStatus.BLOCKED,
+ statusMessage: reason,
+ });
+}
+
+/**
+ * Complete a task
+ */
+export function completeTask(task: Task, githubPRUrl?: string): Task {
+ return updateTask(task, {
+ status: TaskStatus.COMPLETED,
+ completedAt: new Date(),
+ percentComplete: 100,
+ githubPRUrl: githubPRUrl || task.githubPRUrl,
+ });
+}
+
+/**
+ * Fail a task
+ */
+export function failTask(task: Task, error: string): Task {
+ return updateTask(task, {
+ status: TaskStatus.FAILED,
+ completedAt: new Date(),
+ statusMessage: error,
+ });
+}
+
+/**
+ * Check if task is available for assignment
+ */
+export function isTaskAvailable(task: Task, allTasks?: Map): boolean {
+ if (task.status !== TaskStatus.AVAILABLE) {
+ return false;
+ }
+
+ // Check if all dependencies are completed
+ if (allTasks) {
+ return task.dependencies.every((depId) => {
+ const dep = allTasks.get(depId);
+ return dep?.status === TaskStatus.COMPLETED;
+ });
+ }
+
+ // If no task map provided, just check status
+ return task.dependencies.length === 0;
+}
+
+/**
+ * Check if task is in a terminal state
+ */
+export function isTaskTerminal(task: Task): boolean {
+ return [TaskStatus.COMPLETED, TaskStatus.FAILED].includes(task.status);
+}
+
+/**
+ * Get task duration in milliseconds
+ */
+export function getTaskDuration(task: Task): number | null {
+ if (!task.startedAt) {
+ return null;
+ }
+
+ const endTime = task.completedAt || new Date();
+ return endTime.getTime() - task.startedAt.getTime();
+}
diff --git a/packages/mcp-server/src/tasks/TaskQueue.ts b/packages/mcp-server/src/tasks/TaskQueue.ts
new file mode 100644
index 0000000..dcbfb3c
--- /dev/null
+++ b/packages/mcp-server/src/tasks/TaskQueue.ts
@@ -0,0 +1,483 @@
+/**
+ * TaskQueue - FIFO queue with task assignment logic
+ * Manages available tasks and assigns them to agents based on capabilities
+ */
+
+import type { Task, TaskStatus } from './Task.js';
+import { isTaskAvailable, assignTask, TaskStatus as TaskStatusEnum } from './Task.js';
+import type { Agent } from '../agents/Agent.js';
+import { isAgentAvailable } from '../agents/Agent.js';
+import type { Logger } from '../logging/Logger.js';
+import { createLogger } from '../logging/Logger.js';
+
+/**
+ * Task assignment event
+ */
+export interface TaskAssignmentEvent {
+ task: Task;
+ agent: Agent;
+ timestamp: Date;
+}
+
+/**
+ * Task assignment handler
+ */
+export type TaskAssignmentHandler = (event: TaskAssignmentEvent) => void | Promise;
+
+/**
+ * Task lifecycle event types
+ */
+export type TaskLifecycleEventType =
+ | 'task_assigned'
+ | 'task_started'
+ | 'task_blocked'
+ | 'task_progress'
+ | 'task_completed'
+ | 'task_failed';
+
+/**
+ * Task lifecycle event
+ */
+export interface TaskLifecycleEvent {
+ type: TaskLifecycleEventType;
+ task: Task;
+ agent?: Agent;
+ metadata?: Record;
+ timestamp: Date;
+}
+
+/**
+ * Task lifecycle handler
+ */
+export type TaskLifecycleHandler = (event: TaskLifecycleEvent) => void | Promise;
+
+/**
+ * Task queue configuration
+ */
+export interface TaskQueueConfig {
+ /** Logger */
+ logger?: Logger;
+
+ /** Maximum queue size */
+ maxQueueSize?: number;
+}
+
+/**
+ * TaskQueue class
+ */
+export class TaskQueue {
+ private queue: Task[];
+ private assignedTasks: Map; // taskId β Task
+ private logger: Logger;
+ private maxQueueSize: number;
+ private assignmentHandlers: Set;
+ private lifecycleHandlers: Set;
+
+ constructor(config: TaskQueueConfig = {}) {
+ this.queue = [];
+ this.assignedTasks = new Map();
+ this.logger = config.logger || createLogger();
+ this.maxQueueSize = config.maxQueueSize || 1000;
+ this.assignmentHandlers = new Set();
+ this.lifecycleHandlers = new Set();
+ }
+
+ /**
+ * Add task to queue
+ */
+ enqueue(task: Task, allTasks?: Map): void {
+ // Check if task is already in queue or assigned
+ if (this.isTaskInQueue(task.id) || this.assignedTasks.has(task.id)) {
+ this.logger.warn('Task already in queue or assigned', { taskId: task.id });
+ return;
+ }
+
+ // Check queue size limit
+ if (this.queue.length >= this.maxQueueSize) {
+ throw new Error(`Queue is full (max: ${this.maxQueueSize})`);
+ }
+
+ // Check if task is available
+ if (!isTaskAvailable(task, allTasks)) {
+ this.logger.warn('Task is not available for assignment', {
+ taskId: task.id,
+ status: task.status,
+ });
+ return;
+ }
+
+ this.queue.push(task);
+ this.logger.info('Task added to queue', {
+ taskId: task.id,
+ queueSize: this.queue.length,
+ });
+ }
+
+ /**
+ * Remove task from queue (FIFO)
+ */
+ dequeue(): Task | undefined {
+ const task = this.queue.shift();
+ if (task) {
+ this.logger.debug('Task dequeued', {
+ taskId: task.id,
+ queueSize: this.queue.length,
+ });
+ }
+ return task;
+ }
+
+ /**
+ * Peek at next task without removing
+ */
+ peek(): Task | undefined {
+ return this.queue[0];
+ }
+
+ /**
+ * Assign next available task to an agent
+ */
+ async assignNext(agent: Agent, allTasks?: Map): Promise {
+ // Check if agent is available
+ if (!isAgentAvailable(agent)) {
+ this.logger.warn('Agent is not available', {
+ agentId: agent.id,
+ status: agent.status,
+ currentTask: agent.currentTask,
+ });
+ return null;
+ }
+
+ // Find first task that matches agent capabilities
+ const taskIndex = this.queue.findIndex((task) => {
+ return (
+ isTaskAvailable(task, allTasks) &&
+ this.isTaskSuitableForAgent(task, agent)
+ );
+ });
+
+ if (taskIndex === -1) {
+ this.logger.debug('No suitable tasks available for agent', {
+ agentId: agent.id,
+ queueSize: this.queue.length,
+ });
+ return null;
+ }
+
+ // Remove task from queue
+ const [task] = this.queue.splice(taskIndex, 1);
+
+ // Assign task to agent
+ const assignedTask = assignTask(task, agent.id);
+
+ // Store in assigned tasks
+ this.assignedTasks.set(assignedTask.id, assignedTask);
+
+ // Notify handlers
+ await this.notifyAssignmentHandlers({
+ task: assignedTask,
+ agent,
+ timestamp: new Date(),
+ });
+
+ this.logger.info('Task assigned to agent', {
+ taskId: assignedTask.id,
+ agentId: agent.id,
+ queueSize: this.queue.length,
+ });
+
+ return assignedTask;
+ }
+
+ /**
+ * Check if task is suitable for agent (based on capabilities)
+ */
+ private isTaskSuitableForAgent(task: Task, agent: Agent): boolean {
+ // Basic suitability check
+ // Can be extended with more sophisticated matching logic
+
+ // For now, all tasks are suitable for all agents
+ // In the future, could match task requirements with agent capabilities
+ return true;
+ }
+
+ /**
+ * Mark task as completed (remove from assigned)
+ */
+ complete(taskId: string): void {
+ if (this.assignedTasks.has(taskId)) {
+ this.assignedTasks.delete(taskId);
+ this.logger.info('Task marked as completed', { taskId });
+ }
+ }
+
+ /**
+ * Return task to queue (unassign)
+ */
+ unassign(taskId: string): void {
+ const task = this.assignedTasks.get(taskId);
+ if (task) {
+ this.assignedTasks.delete(taskId);
+
+ // Reset task assignment
+ const unassignedTask: Task = {
+ ...task,
+ assignedAgents: [],
+ status: TaskStatusEnum.AVAILABLE,
+ assignedAt: null,
+ };
+
+ this.queue.unshift(unassignedTask); // Add to front of queue
+ this.logger.info('Task returned to queue', {
+ taskId,
+ queueSize: this.queue.length,
+ });
+ }
+ }
+
+ /**
+ * Check if task is in queue
+ */
+ isTaskInQueue(taskId: string): boolean {
+ return this.queue.some((task) => task.id === taskId);
+ }
+
+ /**
+ * Get task from queue by ID
+ */
+ getTaskById(taskId: string): Task | undefined {
+ return (
+ this.queue.find((task) => task.id === taskId) ||
+ this.assignedTasks.get(taskId)
+ );
+ }
+
+ /**
+ * Get all tasks in queue
+ */
+ getAllTasks(): Task[] {
+ return [...this.queue];
+ }
+
+ /**
+ * Get all assigned tasks
+ */
+ getAssignedTasks(): Task[] {
+ return Array.from(this.assignedTasks.values());
+ }
+
+ /**
+ * Get queue size
+ */
+ size(): number {
+ return this.queue.length;
+ }
+
+ /**
+ * Get assigned tasks count
+ */
+ assignedCount(): number {
+ return this.assignedTasks.size;
+ }
+
+ /**
+ * Clear queue
+ */
+ clear(): void {
+ this.queue = [];
+ this.assignedTasks.clear();
+ this.logger.info('Queue cleared');
+ }
+
+ /**
+ * Remove specific task from queue
+ */
+ remove(taskId: string): boolean {
+ const index = this.queue.findIndex((task) => task.id === taskId);
+ if (index !== -1) {
+ this.queue.splice(index, 1);
+ this.logger.info('Task removed from queue', {
+ taskId,
+ queueSize: this.queue.length,
+ });
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Register assignment handler
+ */
+ onAssignment(handler: TaskAssignmentHandler): () => void {
+ this.assignmentHandlers.add(handler);
+ return () => this.assignmentHandlers.delete(handler);
+ }
+
+ /**
+ * Notify assignment handlers
+ */
+ private async notifyAssignmentHandlers(event: TaskAssignmentEvent): Promise {
+ const promises = Array.from(this.assignmentHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in assignment handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Get queue statistics
+ */
+ getStats(): {
+ queueSize: number;
+ assignedCount: number;
+ totalProcessed: number;
+ } {
+ return {
+ queueSize: this.queue.length,
+ assignedCount: this.assignedTasks.size,
+ totalProcessed: this.assignedTasks.size, // Simplified; could track separately
+ };
+ }
+
+ /**
+ * Register lifecycle event handler
+ */
+ onLifecycle(handler: TaskLifecycleHandler): () => void {
+ this.lifecycleHandlers.add(handler);
+ return () => this.lifecycleHandlers.delete(handler);
+ }
+
+ /**
+ * Emit task lifecycle event
+ */
+ async emitLifecycleEvent(
+ type: TaskLifecycleEventType,
+ task: Task,
+ agent?: Agent,
+ metadata?: Record
+ ): Promise {
+ const event: TaskLifecycleEvent = {
+ type,
+ task,
+ agent,
+ metadata,
+ timestamp: new Date(),
+ };
+
+ await this.notifyLifecycleHandlers(event);
+ }
+
+ /**
+ * Notify lifecycle handlers
+ */
+ private async notifyLifecycleHandlers(event: TaskLifecycleEvent): Promise {
+ const promises = Array.from(this.lifecycleHandlers).map(async (handler) => {
+ try {
+ await handler(event);
+ } catch (error) {
+ this.logger.error('Error in lifecycle handler', {
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Mark task as started
+ */
+ async markStarted(taskId: string, agent: Agent): Promise {
+ const task = this.assignedTasks.get(taskId);
+ if (task) {
+ await this.emitLifecycleEvent('task_started', task, agent);
+ this.logger.info('Task started', { taskId, agentId: agent.id });
+ }
+ }
+
+ /**
+ * Mark task as blocked
+ */
+ async markBlocked(
+ taskId: string,
+ agent: Agent,
+ reason: string,
+ blockedBy: string[]
+ ): Promise {
+ const task = this.assignedTasks.get(taskId);
+ if (task) {
+ await this.emitLifecycleEvent('task_blocked', task, agent, { reason, blockedBy });
+ this.logger.info('Task blocked', { taskId, agentId: agent.id, reason });
+ }
+ }
+
+ /**
+ * Update task progress
+ */
+ async updateProgress(
+ taskId: string,
+ agent: Agent,
+ percentComplete: number,
+ status: string
+ ): Promise {
+ const task = this.assignedTasks.get(taskId);
+ if (task) {
+ await this.emitLifecycleEvent('task_progress', task, agent, {
+ percentComplete,
+ status,
+ });
+ this.logger.debug('Task progress updated', {
+ taskId,
+ agentId: agent.id,
+ percentComplete,
+ });
+ }
+ }
+
+ /**
+ * Mark task as completed
+ */
+ async markCompleted(
+ taskId: string,
+ agent: Agent,
+ result?: Record
+ ): Promise {
+ const task = this.assignedTasks.get(taskId);
+ if (task) {
+ await this.emitLifecycleEvent('task_completed', task, agent, { result });
+ this.complete(taskId); // Remove from assigned
+ this.logger.info('Task completed', { taskId, agentId: agent.id });
+ }
+ }
+
+ /**
+ * Mark task as failed
+ */
+ async markFailed(
+ taskId: string,
+ agent: Agent,
+ error: string,
+ retryable: boolean
+ ): Promise {
+ const task = this.assignedTasks.get(taskId);
+ if (task) {
+ await this.emitLifecycleEvent('task_failed', task, agent, { error, retryable });
+
+ if (retryable) {
+ // Return task to queue for retry
+ this.unassign(taskId);
+ } else {
+ // Remove from assigned
+ this.complete(taskId);
+ }
+
+ this.logger.warn('Task failed', { taskId, agentId: agent.id, error, retryable });
+ }
+ }
+}
diff --git a/packages/mcp-server/tests/integration/agent-task-coordination.test.ts b/packages/mcp-server/tests/integration/agent-task-coordination.test.ts
new file mode 100644
index 0000000..56aefdf
--- /dev/null
+++ b/packages/mcp-server/tests/integration/agent-task-coordination.test.ts
@@ -0,0 +1,320 @@
+/**
+ * Integration Test: Agent Task Coordination
+ * Tests the complete workflow from GitHub issue β task assignment β agent coordination
+ */
+
+import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import { v4 as uuidv4 } from 'uuid';
+import { Agent, AgentStatus, createAgent } from '../../src/agents/Agent.js';
+import { Capability, Platform } from '../../src/agents/Capability.js';
+import { AgentRegistry } from '../../src/agents/AgentRegistry.js';
+import { RoleManager } from '../../src/agents/RoleManager.js';
+import { Task, TaskStatus, createTask } from '../../src/tasks/Task.js';
+import { TaskQueue } from '../../src/tasks/TaskQueue.js';
+import { DependencyTracker } from '../../src/tasks/DependencyTracker.js';
+import { MessageBuilder } from '../../src/protocol/MessageBuilder.js';
+import { MessageType } from '../../src/protocol/Message.js';
+
+describe('Agent Task Coordination', () => {
+ let agentRegistry: AgentRegistry;
+ let roleManager: RoleManager;
+ let taskQueue: TaskQueue;
+ let dependencyTracker: DependencyTracker;
+ let developerAgent: Agent;
+ let testerAgent: Agent;
+
+ beforeEach(() => {
+ // Initialize components
+ agentRegistry = new AgentRegistry({ enableTimeoutChecking: false });
+ roleManager = new RoleManager();
+ taskQueue = new TaskQueue();
+ dependencyTracker = new DependencyTracker();
+
+ // Create test agents
+ const developerCapability: Capability = {
+ agentId: uuidv4(),
+ roleType: 'developer',
+ platform: 'Linux' as Platform,
+ tools: ['git', 'npm', 'docker', 'typescript'],
+ languages: ['TypeScript', 'JavaScript'],
+ };
+
+ const testerCapability: Capability = {
+ agentId: uuidv4(),
+ roleType: 'tester',
+ platform: 'Linux' as Platform,
+ tools: ['jest', 'playwright', 'docker'],
+ languages: ['TypeScript', 'JavaScript'],
+ };
+
+ developerAgent = createAgent(developerCapability.agentId, {
+ role: 'developer',
+ platform: 'Linux' as Platform,
+ environment: 'GitHub Actions',
+ capabilities: developerCapability,
+ });
+ developerAgent.status = AgentStatus.CONNECTED;
+
+ testerAgent = createAgent(testerCapability.agentId, {
+ role: 'tester',
+ platform: 'Linux' as Platform,
+ environment: 'GitHub Actions',
+ capabilities: testerCapability,
+ });
+ testerAgent.status = AgentStatus.CONNECTED;
+ });
+
+ afterEach(() => {
+ agentRegistry.clear();
+ taskQueue.clear();
+ dependencyTracker.clear();
+ });
+
+ it('should register agents successfully', async () => {
+ await agentRegistry.add(developerAgent);
+ await agentRegistry.add(testerAgent);
+
+ expect(agentRegistry.count()).toBe(2);
+ expect(agentRegistry.getById(developerAgent.id)).toBeDefined();
+ expect(agentRegistry.getById(testerAgent.id)).toBeDefined();
+ });
+
+ it('should assign task to available agent', async () => {
+ await agentRegistry.add(developerAgent);
+
+ // Create a task
+ const task = createTask(uuidv4(), {
+ description: 'Implement user authentication',
+ githubIssueId: '42',
+ githubIssueUrl: 'https://github.com/org/repo/issues/42',
+ });
+
+ // Add to queue
+ taskQueue.enqueue(task);
+
+ // Assign to developer agent
+ const assignedTask = await taskQueue.assignNext(developerAgent);
+
+ expect(assignedTask).toBeDefined();
+ expect(assignedTask?.id).toBe(task.id);
+ expect(assignedTask?.assignedAgents).toContain(developerAgent.id);
+ expect(assignedTask?.status).toBe(TaskStatus.ASSIGNED);
+ });
+
+ it('should emit task lifecycle events', async () => {
+ const lifecycleEvents: string[] = [];
+
+ taskQueue.onLifecycle((event) => {
+ lifecycleEvents.push(event.type);
+ });
+
+ await agentRegistry.add(developerAgent);
+
+ const task = createTask(uuidv4(), {
+ description: 'Fix authentication bug',
+ githubIssueId: '43',
+ githubIssueUrl: 'https://github.com/org/repo/issues/43',
+ });
+
+ taskQueue.enqueue(task);
+ const assignedTask = await taskQueue.assignNext(developerAgent);
+
+ expect(assignedTask).toBeDefined();
+
+ // Simulate task lifecycle
+ await taskQueue.markStarted(assignedTask!.id, developerAgent);
+ await taskQueue.updateProgress(assignedTask!.id, developerAgent, 50, 'In progress');
+ await taskQueue.markCompleted(assignedTask!.id, developerAgent, { success: true });
+
+ expect(lifecycleEvents).toContain('task_started');
+ expect(lifecycleEvents).toContain('task_progress');
+ expect(lifecycleEvents).toContain('task_completed');
+ });
+
+ it('should handle task dependencies correctly', async () => {
+ // Create task chain: task2 depends on task1
+ const task1 = createTask(uuidv4(), {
+ description: 'Task 1',
+ githubIssueId: '1',
+ githubIssueUrl: 'https://github.com/org/repo/issues/1',
+ });
+
+ const task2 = createTask(uuidv4(), {
+ description: 'Task 2 (depends on Task 1)',
+ dependencies: [task1.id],
+ githubIssueId: '2',
+ githubIssueUrl: 'https://github.com/org/repo/issues/2',
+ });
+
+ // Add to dependency tracker
+ dependencyTracker.addTask(task1);
+ dependencyTracker.addTask(task2);
+
+ // Task 2 should be blocked initially
+ expect(dependencyTracker.areDependenciesCompleted(task2.id)).toBe(false);
+ expect(dependencyTracker.getBlockingDependencies(task2.id)).toContain(task1.id);
+
+ // Complete task 1
+ await dependencyTracker.updateTaskStatus(task1.id, TaskStatus.COMPLETED);
+
+ // Task 2 should now be unblocked
+ expect(dependencyTracker.areDependenciesCompleted(task2.id)).toBe(true);
+ expect(dependencyTracker.getBlockingDependencies(task2.id)).toHaveLength(0);
+ });
+
+ it('should notify when dependencies are resolved', async () => {
+ let unblockedTaskId: string | undefined;
+
+ dependencyTracker.onDependencyResolved((event) => {
+ unblockedTaskId = event.task.id;
+ });
+
+ const task1 = createTask(uuidv4(), {
+ description: 'Task 1',
+ githubIssueId: '1',
+ githubIssueUrl: 'https://github.com/org/repo/issues/1',
+ });
+
+ const task2 = createTask(uuidv4(), {
+ description: 'Task 2',
+ dependencies: [task1.id],
+ githubIssueId: '2',
+ githubIssueUrl: 'https://github.com/org/repo/issues/2',
+ });
+
+ dependencyTracker.addTask(task1);
+ dependencyTracker.addTask(task2);
+
+ // Complete task 1
+ await dependencyTracker.updateTaskStatus(task1.id, TaskStatus.COMPLETED);
+
+ // Wait for async notification
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ expect(unblockedTaskId).toBe(task2.id);
+ });
+
+ it('should build protocol messages correctly', () => {
+ const taskId = uuidv4();
+ const agentId = developerAgent.id;
+
+ // Test task_assigned message
+ const assignedMsg = MessageBuilder.taskAssigned(
+ agentId,
+ testerAgent.id,
+ {
+ taskId,
+ description: 'Test task',
+ githubIssue: 'https://github.com/org/repo/issues/1',
+ }
+ );
+
+ expect(assignedMsg.messageType).toBe(MessageType.TASK_ASSIGNED);
+ expect(assignedMsg.senderId).toBe(agentId);
+ expect(assignedMsg.recipientId).toBe(testerAgent.id);
+ expect(assignedMsg.taskId).toBe(taskId);
+ expect(assignedMsg.payload).toBeDefined();
+
+ // Test task_completed message
+ const completedMsg = MessageBuilder.taskCompleted(agentId, {
+ taskId,
+ result: { success: true },
+ githubPR: 'https://github.com/org/repo/pull/1',
+ });
+
+ expect(completedMsg.messageType).toBe(MessageType.TASK_COMPLETED);
+ expect(completedMsg.senderId).toBe(agentId);
+ expect(completedMsg.taskId).toBe(taskId);
+ });
+
+ it('should manage agent roles correctly', () => {
+ // Predefined roles should exist
+ expect(roleManager.hasRole('developer')).toBe(true);
+ expect(roleManager.hasRole('tester')).toBe(true);
+ expect(roleManager.hasRole('architect')).toBe(true);
+
+ // Should be able to register custom role
+ const customRole = roleManager.registerCustomRole(
+ 'ml-engineer',
+ 'Machine Learning Engineer',
+ {
+ tools: ['python', 'tensorflow', 'jupyter'],
+ languages: ['Python'],
+ }
+ );
+
+ expect(customRole.name).toBe('ml-engineer');
+ expect(customRole.type).toBe('custom');
+ expect(roleManager.hasRole('ml-engineer')).toBe(true);
+ });
+
+ it('should suggest roles based on capabilities', () => {
+ const suggestions = roleManager.suggestRoles({
+ tools: ['git', 'npm', 'docker'],
+ languages: ['TypeScript', 'JavaScript'],
+ });
+
+ // Should suggest developer role (has matching tools/languages)
+ const roleNames = suggestions.map((r) => r.name);
+ expect(roleNames).toContain('developer');
+ });
+
+ it('should handle complete workflow: GitHub issue β task β agent β completion', async () => {
+ // Step 1: Register agents
+ await agentRegistry.add(developerAgent);
+ await agentRegistry.add(testerAgent);
+
+ // Step 2: Create task from GitHub issue
+ const task = createTask(uuidv4(), {
+ description: 'Implement user authentication feature',
+ githubIssueId: '42',
+ githubIssueUrl: 'https://github.com/org/repo/issues/42',
+ });
+
+ // Step 3: Add task to queue
+ taskQueue.enqueue(task);
+
+ // Step 4: Assign task to developer agent
+ const assignedTask = await taskQueue.assignNext(developerAgent);
+ expect(assignedTask).toBeDefined();
+
+ // Step 5: Developer starts work
+ await taskQueue.markStarted(assignedTask!.id, developerAgent);
+
+ // Step 6: Developer reports progress
+ await taskQueue.updateProgress(assignedTask!.id, developerAgent, 50, 'Implementing auth logic');
+
+ // Step 7: Developer completes task
+ await taskQueue.markCompleted(assignedTask!.id, developerAgent, {
+ pullRequest: 'https://github.com/org/repo/pull/100',
+ });
+
+ // Verify task is no longer in assigned tasks
+ expect(taskQueue.getTaskById(assignedTask!.id)).toBeUndefined();
+ });
+
+ it('should handle agent timeout detection', async () => {
+ const registry = new AgentRegistry({
+ enableTimeoutChecking: true,
+ timeoutMs: 1000, // 1 second
+ });
+
+ let timedOutAgent: Agent | undefined;
+
+ registry.onEvent((event) => {
+ if (event.type === 'agent_timeout') {
+ timedOutAgent = event.agent;
+ }
+ });
+
+ await registry.add(developerAgent);
+
+ // Wait for timeout
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+
+ expect(timedOutAgent).toBeDefined();
+ expect(timedOutAgent?.id).toBe(developerAgent.id);
+
+ registry.destroy();
+ });
+});
diff --git a/packages/mcp-server/tests/integration/secure-communication.test.ts b/packages/mcp-server/tests/integration/secure-communication.test.ts
new file mode 100644
index 0000000..e60d72a
--- /dev/null
+++ b/packages/mcp-server/tests/integration/secure-communication.test.ts
@@ -0,0 +1,329 @@
+/**
+ * Integration Test: Secure Agent Communication
+ * Tests authentication, encryption, and security features
+ */
+
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { TokenGenerator, generateChannelToken, hashToken } from '../../src/config/TokenGenerator.js';
+import type { ChannelConfig } from '../../src/channels/base/Channel.js';
+import { ChannelAdapter } from '../../src/channels/base/ChannelAdapter.js';
+import { Message, MessageType } from '../../src/protocol/Message.js';
+import { MessageBuilder } from '../../src/protocol/MessageBuilder.js';
+
+describe('Secure Communication', () => {
+ describe('TokenGenerator', () => {
+ it('should generate secure random tokens', () => {
+ const token1 = TokenGenerator.generate();
+ const token2 = TokenGenerator.generate();
+
+ expect(token1).toBeDefined();
+ expect(token2).toBeDefined();
+ expect(token1).not.toBe(token2); // Should be unique
+ expect(token1.length).toBeGreaterThanOrEqual(32); // Minimum length
+ });
+
+ it('should generate channel tokens with correct format', () => {
+ const token = TokenGenerator.generateChannelToken();
+
+ expect(token).toMatch(/^cct_[a-f0-9]+$/);
+ expect(token.length).toBeGreaterThan(16);
+ });
+
+ it('should generate API tokens with correct format', () => {
+ const token = TokenGenerator.generateAPIToken();
+
+ expect(token).toMatch(/^cca_[a-zA-Z0-9_-]+$/);
+ expect(token.length).toBeGreaterThan(16);
+ });
+
+ it('should validate token format correctly', () => {
+ const validToken = 'cct_' + 'a'.repeat(32);
+ const shortToken = 'cct_abc';
+ const invalidChars = 'cct_abc$@!';
+
+ expect(TokenGenerator.validateFormat(validToken, { prefix: 'cct_' })).toBe(true);
+ expect(TokenGenerator.validateFormat(shortToken, { minLength: 16 })).toBe(false);
+ expect(TokenGenerator.validateFormat(invalidChars)).toBe(false);
+ });
+
+ it('should hash tokens consistently', () => {
+ const token = 'test_token_123';
+ const hash1 = TokenGenerator.hash(token);
+ const hash2 = TokenGenerator.hash(token);
+
+ expect(hash1).toBe(hash2);
+ expect(hash1).toHaveLength(64); // SHA-256 produces 64 hex characters
+ expect(hash1).toMatch(/^[a-f0-9]+$/);
+ });
+
+ it('should produce different hashes for different tokens', () => {
+ const token1 = 'token_one';
+ const token2 = 'token_two';
+ const hash1 = TokenGenerator.hash(token1);
+ const hash2 = TokenGenerator.hash(token2);
+
+ expect(hash1).not.toBe(hash2);
+ });
+ });
+
+ describe('Channel Authentication', () => {
+ class TestChannel extends ChannelAdapter {
+ protected async doConnect(): Promise {
+ // Mock implementation
+ }
+
+ protected async doDisconnect(): Promise {
+ // Mock implementation
+ }
+
+ protected async doSendMessage(message: Message): Promise {
+ // Mock implementation
+ }
+
+ protected async doPing(): Promise {
+ // Mock implementation
+ }
+
+ // Expose protected methods for testing
+ public testVerifyToken(token: string): boolean {
+ return this.verifyToken(token);
+ }
+
+ public testGetAuthToken(): string {
+ return this.getAuthToken();
+ }
+ }
+
+ it('should reject channels with invalid tokens', () => {
+ const invalidConfigs = [
+ { token: '' }, // Empty token
+ { token: 'short' }, // Too short
+ { token: '12345' }, // Less than 16 chars
+ ];
+
+ for (const params of invalidConfigs) {
+ expect(() => {
+ new TestChannel({
+ type: 'test',
+ token: params.token,
+ connectionParams: {},
+ } as ChannelConfig);
+ }).toThrow('Invalid or missing authentication token');
+ }
+ });
+
+ it('should accept channels with valid tokens', () => {
+ const validToken = generateChannelToken();
+
+ expect(() => {
+ new TestChannel({
+ type: 'test',
+ token: validToken,
+ connectionParams: {},
+ } as ChannelConfig);
+ }).not.toThrow();
+ });
+
+ it('should verify tokens using timing-safe comparison', () => {
+ const validToken = generateChannelToken();
+ const channel = new TestChannel({
+ type: 'test',
+ token: validToken,
+ connectionParams: {},
+ } as ChannelConfig);
+
+ // Valid token should pass
+ expect(channel.testVerifyToken(validToken)).toBe(true);
+
+ // Invalid tokens should fail
+ expect(channel.testVerifyToken('wrong_token')).toBe(false);
+ expect(channel.testVerifyToken('')).toBe(false);
+ expect(channel.testVerifyToken(validToken + 'extra')).toBe(false);
+ });
+
+ it('should provide auth token for connections', () => {
+ const token = generateChannelToken();
+ const channel = new TestChannel({
+ type: 'test',
+ token,
+ connectionParams: {},
+ } as ChannelConfig);
+
+ expect(channel.testGetAuthToken()).toBe(token);
+ });
+ });
+
+ describe('Message Security', () => {
+ it('should include authentication metadata in messages', () => {
+ const senderId = 'agent-123';
+ const message = MessageBuilder.heartbeat(senderId);
+
+ expect(message.senderId).toBe(senderId);
+ expect(message.timestamp).toBeDefined();
+ expect(message.protocolVersion).toBeDefined();
+ });
+
+ it('should validate message integrity', () => {
+ const message = MessageBuilder.taskAssigned(
+ 'sender-1',
+ 'recipient-1',
+ {
+ taskId: 'task-1',
+ description: 'Test task',
+ githubIssue: 'https://github.com/org/repo/issues/1',
+ }
+ );
+
+ // Message should have all required fields
+ expect(message.protocolVersion).toBeDefined();
+ expect(message.messageType).toBe(MessageType.TASK_ASSIGNED);
+ expect(message.senderId).toBe('sender-1');
+ expect(message.recipientId).toBe('recipient-1');
+ expect(message.timestamp).toBeDefined();
+ });
+
+ it('should prevent message tampering detection', () => {
+ const message = MessageBuilder.heartbeat('agent-1');
+ const originalTimestamp = message.timestamp;
+
+ // Simulate tampering
+ const tamperedMessage = {
+ ...message,
+ timestamp: new Date(Date.now() + 1000000).toISOString(),
+ };
+
+ // Original timestamp should differ from tampered
+ expect(tamperedMessage.timestamp).not.toBe(originalTimestamp);
+ });
+ });
+
+ describe('Token Security Best Practices', () => {
+ it('should generate tokens with sufficient entropy', () => {
+ // Generate multiple tokens and ensure uniqueness
+ const tokens = new Set();
+ const count = 100;
+
+ for (let i = 0; i < count; i++) {
+ tokens.add(generateChannelToken());
+ }
+
+ // All tokens should be unique
+ expect(tokens.size).toBe(count);
+ });
+
+ it('should support different token encodings', () => {
+ const hexToken = TokenGenerator.generate({ encoding: 'hex' });
+ const base64Token = TokenGenerator.generate({ encoding: 'base64' });
+ const base64urlToken = TokenGenerator.generate({ encoding: 'base64url' });
+
+ expect(hexToken).toMatch(/^[a-f0-9]+$/);
+ expect(base64Token).toMatch(/^[A-Za-z0-9+/=]+$/);
+ expect(base64urlToken).toMatch(/^[A-Za-z0-9_-]+$/);
+ });
+
+ it('should generate nonces for replay protection', () => {
+ const nonce1 = TokenGenerator.generateNonce();
+ const nonce2 = TokenGenerator.generateNonce();
+
+ expect(nonce1).not.toBe(nonce2);
+ expect(nonce1).toHaveLength(32); // 16 bytes = 32 hex chars
+ });
+
+ it('should generate batch tokens efficiently', () => {
+ const count = 50;
+ const tokens = TokenGenerator.generateBatch(count);
+
+ expect(tokens).toHaveLength(count);
+ expect(new Set(tokens).size).toBe(count); // All unique
+ });
+ });
+
+ describe('Encryption Support', () => {
+ it('should warn when using unencrypted connections', () => {
+ // This test verifies that channels log warnings for insecure connections
+ // Actual implementation is in channel-specific code
+
+ const insecureUrls = [
+ 'http://example.com/hub', // HTTP instead of HTTPS
+ 'redis://localhost:6379', // Redis without TLS
+ ];
+
+ // In production, these should trigger warnings
+ insecureUrls.forEach(url => {
+ expect(url.startsWith('https://') || url.startsWith('rediss://')).toBe(false);
+ });
+ });
+
+ it('should support secure connection URLs', () => {
+ const secureUrls = [
+ 'https://example.com/hub', // HTTPS
+ 'rediss://localhost:6379', // Redis with TLS
+ 'wss://example.com/ws', // WebSocket Secure
+ ];
+
+ secureUrls.forEach(url => {
+ const isSecure = url.startsWith('https://') ||
+ url.startsWith('rediss://') ||
+ url.startsWith('wss://');
+ expect(isSecure).toBe(true);
+ });
+ });
+ });
+
+ describe('Authentication Edge Cases', () => {
+ it('should handle null/undefined tokens gracefully', () => {
+ expect(() => {
+ TokenGenerator.validateFormat(null as any);
+ }).not.toThrow();
+
+ expect(TokenGenerator.validateFormat(undefined as any)).toBe(false);
+ });
+
+ it('should reject tokens with whitespace', () => {
+ const tokensWithWhitespace = [
+ 'token with spaces',
+ 'token\twith\ttabs',
+ 'token\nwith\nnewlines',
+ ' leading-space',
+ 'trailing-space ',
+ ];
+
+ tokensWithWhitespace.forEach(token => {
+ expect(TokenGenerator.validateFormat(token)).toBe(false);
+ });
+ });
+
+ it('should handle very long tokens', () => {
+ const longToken = 'a'.repeat(1000);
+ const hash = TokenGenerator.hash(longToken);
+
+ expect(hash).toHaveLength(64); // SHA-256 always produces same length
+ });
+ });
+
+ describe('Security Headers and Metadata', () => {
+ it('should include security-relevant fields in messages', () => {
+ const message = MessageBuilder.error(
+ 'agent-1',
+ {
+ code: 'AUTH_FAILED',
+ message: 'Authentication failed',
+ }
+ );
+
+ // Security-relevant fields should be present
+ expect(message.senderId).toBeDefined();
+ expect(message.timestamp).toBeDefined();
+ expect(message.protocolVersion).toBeDefined();
+ expect(message.messageType).toBe(MessageType.ERROR);
+ expect(message.priority).toBeDefined();
+ });
+
+ it('should support correlation IDs for request tracking', () => {
+ const correlationId = TokenGenerator.generateNonce();
+ const query = MessageBuilder.statusQuery('agent-1', null, correlationId);
+
+ expect(query.correlationId).toBe(correlationId);
+ });
+ });
+});
diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json
new file mode 100644
index 0000000..28fd0ee
--- /dev/null
+++ b/packages/mcp-server/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "lib": ["ES2022"],
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "tests"]
+}
diff --git a/packages/relay-server/CoorChat.RelayServer.sln b/packages/relay-server/CoorChat.RelayServer.sln
new file mode 100644
index 0000000..4cfdc57
--- /dev/null
+++ b/packages/relay-server/CoorChat.RelayServer.sln
@@ -0,0 +1,54 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E3F9E7A-1234-4567-89AB-CDEF01234567}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9F4EADEF-5678-9ABC-DEF0-123456789ABC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoorChat.RelayServer.Api", "src\CoorChat.RelayServer.Api\CoorChat.RelayServer.Api.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoorChat.RelayServer.Core", "src\CoorChat.RelayServer.Core\CoorChat.RelayServer.Core.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoorChat.RelayServer.Data", "src\CoorChat.RelayServer.Data\CoorChat.RelayServer.Data.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoorChat.RelayServer.Tests.Unit", "tests\CoorChat.RelayServer.Tests.Unit\CoorChat.RelayServer.Tests.Unit.csproj", "{D4E5F6A7-B8C9-0123-DEF1-234567890123}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoorChat.RelayServer.Tests.Integration", "tests\CoorChat.RelayServer.Tests.Integration\CoorChat.RelayServer.Tests.Integration.csproj", "{E5F6A7B8-C9D0-1234-EF12-345678901234}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {8E3F9E7A-1234-4567-89AB-CDEF01234567}
+ {B2C3D4E5-F6A7-8901-BCDE-F12345678901} = {8E3F9E7A-1234-4567-89AB-CDEF01234567}
+ {C3D4E5F6-A7B8-9012-CDEF-123456789012} = {8E3F9E7A-1234-4567-89AB-CDEF01234567}
+ {D4E5F6A7-B8C9-0123-DEF1-234567890123} = {9F4EADEF-5678-9ABC-DEF0-123456789ABC}
+ {E5F6A7B8-C9D0-1234-EF12-345678901234} = {9F4EADEF-5678-9ABC-DEF0-123456789ABC}
+ EndGlobalSection
+EndGlobal
diff --git a/packages/relay-server/Dockerfile b/packages/relay-server/Dockerfile
new file mode 100644
index 0000000..ab02b0a
--- /dev/null
+++ b/packages/relay-server/Dockerfile
@@ -0,0 +1,47 @@
+# Multi-stage build for CoorChat Relay Server
+FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
+WORKDIR /src
+
+# Copy solution and project files
+COPY CoorChat.RelayServer.sln ./
+COPY src/CoorChat.RelayServer.Api/CoorChat.RelayServer.Api.csproj src/CoorChat.RelayServer.Api/
+COPY src/CoorChat.RelayServer.Core/CoorChat.RelayServer.Core.csproj src/CoorChat.RelayServer.Core/
+COPY src/CoorChat.RelayServer.Data/CoorChat.RelayServer.Data.csproj src/CoorChat.RelayServer.Data/
+COPY tests/CoorChat.RelayServer.Tests.Unit/CoorChat.RelayServer.Tests.Unit.csproj tests/CoorChat.RelayServer.Tests.Unit/
+COPY tests/CoorChat.RelayServer.Tests.Integration/CoorChat.RelayServer.Tests.Integration.csproj tests/CoorChat.RelayServer.Tests.Integration/
+
+# Restore dependencies
+RUN dotnet restore
+
+# Copy source code
+COPY src/ src/
+COPY tests/ tests/
+
+# Build and publish
+WORKDIR /src/src/CoorChat.RelayServer.Api
+RUN dotnet build -c Release --no-restore && \
+ dotnet publish -c Release -o /app/publish --no-build
+
+# Runtime image
+FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
+WORKDIR /app
+
+# Create non-root user
+RUN addgroup -g 1001 -S coorchat && \
+ adduser -S coorchat -u 1001 && \
+ chown -R coorchat:coorchat /app
+
+USER coorchat
+
+# Copy published application
+COPY --from=build /app/publish .
+
+# Expose ports
+EXPOSE 80
+EXPOSE 443
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:80/health || exit 1
+
+ENTRYPOINT ["dotnet", "CoorChat.RelayServer.Api.dll"]
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Api/CoorChat.RelayServer.Api.csproj b/packages/relay-server/src/CoorChat.RelayServer.Api/CoorChat.RelayServer.Api.csproj
new file mode 100644
index 0000000..e3bafb0
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Api/CoorChat.RelayServer.Api.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Linux
+ CoorChat.RelayServer.Api
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Api/Middleware/AuthenticationMiddleware.cs b/packages/relay-server/src/CoorChat.RelayServer.Api/Middleware/AuthenticationMiddleware.cs
new file mode 100644
index 0000000..4337cbf
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Api/Middleware/AuthenticationMiddleware.cs
@@ -0,0 +1,74 @@
+using Microsoft.AspNetCore.Http;
+using CoorChat.RelayServer.Core.Services;
+using System.Threading.Tasks;
+
+namespace CoorChat.RelayServer.Api.Middleware
+{
+ ///
+ /// Authentication middleware for validating shared tokens
+ ///
+ public class AuthenticationMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly IAuthenticationService _authService;
+
+ public AuthenticationMiddleware(
+ RequestDelegate next,
+ IAuthenticationService authService)
+ {
+ _next = next;
+ _authService = authService;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ // Skip authentication for health check endpoint
+ if (context.Request.Path.StartsWithSegments("/health"))
+ {
+ await _next(context);
+ return;
+ }
+
+ // Extract token from Authorization header
+ var authHeader = context.Request.Headers["Authorization"].ToString();
+ if (string.IsNullOrEmpty(authHeader))
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await context.Response.WriteAsync("Missing Authorization header");
+ return;
+ }
+
+ // Parse Bearer token
+ var token = authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
+ ? authHeader.Substring("Bearer ".Length).Trim()
+ : authHeader;
+
+ // Validate token
+ var isValid = await _authService.ValidateTokenAsync(token);
+ if (!isValid)
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await context.Response.WriteAsync("Invalid authentication token");
+ return;
+ }
+
+ // Store token in context for downstream use
+ context.Items["AuthToken"] = token;
+
+ // Continue to next middleware
+ await _next(context);
+ }
+ }
+
+ ///
+ /// Extension methods for AuthenticationMiddleware
+ ///
+ public static class AuthenticationMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseTokenAuthentication(
+ this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Api/Program.cs b/packages/relay-server/src/CoorChat.RelayServer.Api/Program.cs
new file mode 100644
index 0000000..04cd2ca
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Api/Program.cs
@@ -0,0 +1,22 @@
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container
+builder.Services.AddControllers();
+builder.Services.AddSignalR();
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+app.UseAuthorization();
+app.MapControllers();
+
+app.Run();
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Core/CoorChat.RelayServer.Core.csproj b/packages/relay-server/src/CoorChat.RelayServer.Core/CoorChat.RelayServer.Core.csproj
new file mode 100644
index 0000000..bb445bd
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Core/CoorChat.RelayServer.Core.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+ CoorChat.RelayServer.Core
+
+
+
+
+
+
+
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Core/Services/AuthenticationService.cs b/packages/relay-server/src/CoorChat.RelayServer.Core/Services/AuthenticationService.cs
new file mode 100644
index 0000000..c2f0cbb
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Core/Services/AuthenticationService.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace CoorChat.RelayServer.Core.Services
+{
+ ///
+ /// Authentication service for validating shared tokens
+ ///
+ public class AuthenticationService : IAuthenticationService
+ {
+ private readonly IConfiguration _configuration;
+ private readonly ILogger _logger;
+ private readonly string _validTokenHash;
+
+ public AuthenticationService(
+ IConfiguration configuration,
+ ILogger logger)
+ {
+ _configuration = configuration;
+ _logger = logger;
+
+ // Load valid token from configuration
+ var validToken = _configuration["Authentication:SharedToken"]
+ ?? throw new InvalidOperationException("Authentication:SharedToken not configured");
+
+ // Store hash of valid token for timing-safe comparison
+ _validTokenHash = HashToken(validToken);
+
+ _logger.LogInformation("Authentication service initialized");
+ }
+
+ ///
+ /// Validate an authentication token
+ ///
+ public Task ValidateTokenAsync(string token)
+ {
+ if (string.IsNullOrWhiteSpace(token))
+ {
+ _logger.LogWarning("Empty token provided");
+ return Task.FromResult(false);
+ }
+
+ // Minimum token length check
+ if (token.Length < 16)
+ {
+ _logger.LogWarning("Token too short: {Length} characters", token.Length);
+ return Task.FromResult(false);
+ }
+
+ try
+ {
+ // Hash provided token
+ var providedHash = HashToken(token);
+
+ // Timing-safe comparison
+ var isValid = TimingSafeEqual(providedHash, _validTokenHash);
+
+ if (!isValid)
+ {
+ _logger.LogWarning("Invalid token provided");
+ }
+
+ return Task.FromResult(isValid);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating token");
+ return Task.FromResult(false);
+ }
+ }
+
+ ///
+ /// Generate a new authentication token
+ ///
+ public string GenerateToken()
+ {
+ // Generate 32 bytes (256 bits) of random data
+ var bytes = new byte[32];
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(bytes);
+ }
+
+ // Convert to hex string with prefix
+ return "cct_" + Convert.ToHexString(bytes).ToLowerInvariant();
+ }
+
+ ///
+ /// Hash a token for secure storage
+ ///
+ public string HashToken(string token)
+ {
+ using var sha256 = SHA256.Create();
+ var bytes = Encoding.UTF8.GetBytes(token);
+ var hash = sha256.ComputeHash(bytes);
+ return Convert.ToHexString(hash).ToLowerInvariant();
+ }
+
+ ///
+ /// Timing-safe string comparison
+ ///
+ private bool TimingSafeEqual(string a, string b)
+ {
+ if (a == null || b == null)
+ return false;
+
+ if (a.Length != b.Length)
+ return false;
+
+ var result = 0;
+ for (int i = 0; i < a.Length; i++)
+ {
+ result |= a[i] ^ b[i];
+ }
+
+ return result == 0;
+ }
+ }
+}
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Core/Services/IAuthenticationService.cs b/packages/relay-server/src/CoorChat.RelayServer.Core/Services/IAuthenticationService.cs
new file mode 100644
index 0000000..c414e21
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Core/Services/IAuthenticationService.cs
@@ -0,0 +1,30 @@
+using System.Threading.Tasks;
+
+namespace CoorChat.RelayServer.Core.Services
+{
+ ///
+ /// Interface for authentication service
+ ///
+ public interface IAuthenticationService
+ {
+ ///
+ /// Validate an authentication token
+ ///
+ /// Token to validate
+ /// True if token is valid, false otherwise
+ Task ValidateTokenAsync(string token);
+
+ ///
+ /// Generate a new authentication token
+ ///
+ /// New authentication token
+ string GenerateToken();
+
+ ///
+ /// Hash a token for secure storage
+ ///
+ /// Token to hash
+ /// Hashed token
+ string HashToken(string token);
+ }
+}
diff --git a/packages/relay-server/src/CoorChat.RelayServer.Data/CoorChat.RelayServer.Data.csproj b/packages/relay-server/src/CoorChat.RelayServer.Data/CoorChat.RelayServer.Data.csproj
new file mode 100644
index 0000000..32e9830
--- /dev/null
+++ b/packages/relay-server/src/CoorChat.RelayServer.Data/CoorChat.RelayServer.Data.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+ CoorChat.RelayServer.Data
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/relay-server/tests/CoorChat.RelayServer.Tests.Integration/CoorChat.RelayServer.Tests.Integration.csproj b/packages/relay-server/tests/CoorChat.RelayServer.Tests.Integration/CoorChat.RelayServer.Tests.Integration.csproj
new file mode 100644
index 0000000..dde4c65
--- /dev/null
+++ b/packages/relay-server/tests/CoorChat.RelayServer.Tests.Integration/CoorChat.RelayServer.Tests.Integration.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+ true
+ CoorChat.RelayServer.Tests.Integration
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/packages/relay-server/tests/CoorChat.RelayServer.Tests.Unit/CoorChat.RelayServer.Tests.Unit.csproj b/packages/relay-server/tests/CoorChat.RelayServer.Tests.Unit/CoorChat.RelayServer.Tests.Unit.csproj
new file mode 100644
index 0000000..7e9e038
--- /dev/null
+++ b/packages/relay-server/tests/CoorChat.RelayServer.Tests.Unit/CoorChat.RelayServer.Tests.Unit.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+ true
+ CoorChat.RelayServer.Tests.Unit
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
diff --git a/quick-start.ps1 b/quick-start.ps1
new file mode 100644
index 0000000..7475dad
--- /dev/null
+++ b/quick-start.ps1
@@ -0,0 +1,229 @@
+# CoorChat Quick Start Script (PowerShell)
+# Automates local installation and setup
+
+$ErrorActionPreference = "Stop"
+
+Write-Host "π CoorChat Quick Start" -ForegroundColor Cyan
+Write-Host "=======================" -ForegroundColor Cyan
+Write-Host ""
+
+# Check prerequisites
+Write-Host "π Checking prerequisites..." -ForegroundColor Yellow
+
+if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
+ Write-Host "β Node.js not found. Please install Node.js 18+" -ForegroundColor Red
+ exit 1
+}
+
+if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
+ Write-Host "β npm not found. Please install npm" -ForegroundColor Red
+ exit 1
+}
+
+$dockerAvailable = $false
+if (Get-Command docker -ErrorAction SilentlyContinue) {
+ $dockerAvailable = $true
+} else {
+ Write-Host "β οΈ Docker not found. You'll need to install Redis manually." -ForegroundColor Yellow
+}
+
+Write-Host "β
Prerequisites checked" -ForegroundColor Green
+Write-Host ""
+
+# Navigate to MCP server
+Set-Location packages\mcp-server
+
+# Install dependencies
+Write-Host "π¦ Installing dependencies..." -ForegroundColor Yellow
+npm install
+Write-Host "β
Dependencies installed" -ForegroundColor Green
+Write-Host ""
+
+# Build project
+Write-Host "π¨ Building project..." -ForegroundColor Yellow
+npm run build
+Write-Host "β
Project built" -ForegroundColor Green
+Write-Host ""
+
+# Generate secure token
+Write-Host "π Generating secure token..." -ForegroundColor Yellow
+$TOKEN = node -e "console.log('cct_' + require('crypto').randomBytes(32).toString('hex'))"
+Write-Host "β
Token generated" -ForegroundColor Green
+Write-Host ""
+Write-Host "Your secure token: " -NoNewline
+Write-Host $TOKEN -ForegroundColor Yellow
+Write-Host "β οΈ Save this token! You'll need it for all agents." -ForegroundColor Yellow
+Write-Host ""
+
+# Save token to .env file
+$envContent = @"
+# CoorChat Configuration
+# Generated: $(Get-Date)
+
+# Shared authentication token (use same token for all agents)
+SHARED_TOKEN=$TOKEN
+
+# Channel configuration (redis, discord, or signalr)
+CHANNEL_TYPE=redis
+REDIS_HOST=localhost
+REDIS_PORT=6379
+
+# Agent configuration
+AGENT_ID=agent-claude-1
+AGENT_ROLE=developer
+
+# Optional: GitHub integration
+# GITHUB_TOKEN=ghp_your_token_here
+# GITHUB_OWNER=your-org
+# GITHUB_REPO=your-repo
+
+# Logging
+LOG_LEVEL=info
+"@
+
+Set-Content -Path ".env" -Value $envContent
+Write-Host "β
Configuration saved to .env" -ForegroundColor Green
+Write-Host ""
+
+# Setup channel
+Write-Host "π‘ Setting up communication channel..." -ForegroundColor Yellow
+Write-Host "Choose your channel type:"
+Write-Host " 1) Redis (Recommended - requires Docker)"
+Write-Host " 2) Discord (Easy - requires Discord bot)"
+Write-Host " 3) SignalR (Advanced - requires relay server)"
+Write-Host ""
+$channelChoice = Read-Host "Enter choice [1-3]"
+
+switch ($channelChoice) {
+ "1" {
+ if ($dockerAvailable) {
+ Write-Host "Starting Redis container..." -ForegroundColor Yellow
+ try {
+ docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine 2>$null
+ } catch {
+ Write-Host "Redis container already exists, starting it..." -ForegroundColor Yellow
+ docker start coorchat-redis
+ }
+ Write-Host "β
Redis started on localhost:6379" -ForegroundColor Green
+ } else {
+ Write-Host "β Docker not available. Please install Docker or choose another channel." -ForegroundColor Red
+ exit 1
+ }
+ }
+ "2" {
+ Write-Host ""
+ Write-Host "Discord Setup Instructions:" -ForegroundColor Cyan
+ Write-Host "1. Go to https://discord.com/developers/applications"
+ Write-Host "2. Create a new application"
+ Write-Host "3. Go to 'Bot' β 'Add Bot'"
+ Write-Host "4. Copy the bot token"
+ Write-Host "5. Enable 'Message Content Intent'"
+ Write-Host "6. Invite bot to your server"
+ Write-Host "7. Create a channel and copy its ID"
+ Write-Host ""
+ $discordToken = Read-Host "Enter Discord bot token"
+ $channelId = Read-Host "Enter Discord channel ID"
+
+ # Update .env
+ $envContent = $envContent -replace "CHANNEL_TYPE=redis", "CHANNEL_TYPE=discord"
+ $envContent += "`nDISCORD_BOT_TOKEN=$discordToken"
+ $envContent += "`nDISCORD_CHANNEL_ID=$channelId"
+ Set-Content -Path ".env" -Value $envContent
+ Write-Host "β
Discord configured" -ForegroundColor Green
+ }
+ "3" {
+ Write-Host "Starting SignalR relay server..." -ForegroundColor Yellow
+ Set-Location ..\..\packages\relay-server
+ docker build -t coorchat-relay .
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "β Failed to build relay server" -ForegroundColor Red
+ exit 1
+ }
+ docker run -d --name coorchat-relay -p 5001:5001 -e "Authentication__SharedToken=$TOKEN" coorchat-relay
+ Set-Location ..\..\packages\mcp-server
+
+ # Update .env
+ $envContent = $envContent -replace "CHANNEL_TYPE=redis", "CHANNEL_TYPE=signalr"
+ $envContent += "`nSIGNALR_HUB_URL=https://localhost:5001/agentHub"
+ Set-Content -Path ".env" -Value $envContent
+ Write-Host "β
SignalR relay server started" -ForegroundColor Green
+ }
+ default {
+ Write-Host "Invalid choice" -ForegroundColor Red
+ exit 1
+ }
+}
+
+Write-Host ""
+
+# Run tests
+Write-Host "π§ͺ Running tests..." -ForegroundColor Yellow
+npm test -- --run
+if ($LASTEXITCODE -ne 0) {
+ Write-Host "β οΈ Some tests failed, but installation is complete" -ForegroundColor Yellow
+}
+Write-Host ""
+
+# Generate Claude Desktop config
+Write-Host "π Generating Claude Desktop configuration..." -ForegroundColor Yellow
+
+$repoPath = (Get-Location).Path | Split-Path -Parent | Split-Path -Parent
+$mcpServerPath = Join-Path $repoPath "packages\mcp-server\dist\index.js"
+$mcpServerPath = $mcpServerPath -replace "\\", "\\"
+
+$claudeConfig = @"
+{
+ "mcpServers": {
+ "coorchat": {
+ "command": "node",
+ "args": [
+ "$mcpServerPath"
+ ],
+ "env": {
+ "CHANNEL_TYPE": "redis",
+ "REDIS_HOST": "localhost",
+ "REDIS_PORT": "6379",
+ "SHARED_TOKEN": "$TOKEN",
+ "AGENT_ID": "agent-claude-1",
+ "AGENT_ROLE": "developer",
+ "LOG_LEVEL": "info"
+ }
+ }
+ }
+}
+"@
+
+Set-Content -Path "claude_desktop_config.json" -Value $claudeConfig
+Write-Host "β
Claude Desktop config generated" -ForegroundColor Green
+Write-Host ""
+
+# Installation complete
+Write-Host "π Installation Complete!" -ForegroundColor Cyan
+Write-Host "=======================" -ForegroundColor Cyan
+Write-Host ""
+Write-Host "Next Steps:" -ForegroundColor Cyan
+Write-Host ""
+Write-Host "1. Copy the Claude Desktop configuration to:"
+$claudeConfigPath = "$env:APPDATA\Claude\claude_desktop_config.json"
+Write-Host " $claudeConfigPath" -ForegroundColor Yellow
+Write-Host ""
+Write-Host " Configuration file created at:"
+Write-Host " .\claude_desktop_config.json" -ForegroundColor Yellow
+Write-Host ""
+Write-Host "2. Restart Claude Desktop"
+Write-Host ""
+Write-Host "3. Test the MCP server in Claude:"
+Write-Host " Ask: 'Can you check if coorchat is connected?'"
+Write-Host ""
+Write-Host "Useful Commands:" -ForegroundColor Cyan
+Write-Host ""
+Write-Host " Start agents manually:"
+Write-Host " npm run cli -- agent start --role developer" -ForegroundColor Yellow
+Write-Host ""
+Write-Host " Monitor coordination:"
+Write-Host " npm run cli -- monitor" -ForegroundColor Yellow
+Write-Host ""
+Write-Host " View logs:"
+Write-Host " npm run cli -- logs" -ForegroundColor Yellow
+Write-Host ""
+Write-Host "Happy coordinating! π€" -ForegroundColor Green
diff --git a/quick-start.sh b/quick-start.sh
new file mode 100644
index 0000000..485712f
--- /dev/null
+++ b/quick-start.sh
@@ -0,0 +1,233 @@
+#!/bin/bash
+# CoorChat Quick Start Script
+# Automates local installation and setup
+
+set -e # Exit on error
+
+echo "π CoorChat Quick Start"
+echo "======================="
+echo ""
+
+# Colors for output
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+# Check prerequisites
+echo "π Checking prerequisites..."
+
+if ! command -v node &> /dev/null; then
+ echo -e "${RED}β Node.js not found. Please install Node.js 18+${NC}"
+ exit 1
+fi
+
+if ! command -v npm &> /dev/null; then
+ echo -e "${RED}β npm not found. Please install npm${NC}"
+ exit 1
+fi
+
+if ! command -v docker &> /dev/null; then
+ echo -e "${YELLOW}β οΈ Docker not found. You'll need to install Redis manually.${NC}"
+ DOCKER_AVAILABLE=false
+else
+ DOCKER_AVAILABLE=true
+fi
+
+echo -e "${GREEN}β
Prerequisites checked${NC}"
+echo ""
+
+# Navigate to MCP server
+cd packages/mcp-server
+
+# Install dependencies
+echo "π¦ Installing dependencies..."
+npm install
+echo -e "${GREEN}β
Dependencies installed${NC}"
+echo ""
+
+# Build project
+echo "π¨ Building project..."
+npm run build
+echo -e "${GREEN}β
Project built${NC}"
+echo ""
+
+# Generate secure token
+echo "π Generating secure token..."
+TOKEN=$(node -e "console.log('cct_' + require('crypto').randomBytes(32).toString('hex'))")
+echo -e "${GREEN}β
Token generated${NC}"
+echo ""
+echo -e "${BLUE}Your secure token: ${YELLOW}${TOKEN}${NC}"
+echo -e "${YELLOW}β οΈ Save this token! You'll need it for all agents.${NC}"
+echo ""
+
+# Save token to .env file
+cat > .env << EOF
+# CoorChat Configuration
+# Generated: $(date)
+
+# Shared authentication token (use same token for all agents)
+SHARED_TOKEN=${TOKEN}
+
+# Channel configuration (redis, discord, or signalr)
+CHANNEL_TYPE=redis
+REDIS_HOST=localhost
+REDIS_PORT=6379
+
+# Agent configuration
+AGENT_ID=agent-claude-1
+AGENT_ROLE=developer
+
+# Optional: GitHub integration
+# GITHUB_TOKEN=ghp_your_token_here
+# GITHUB_OWNER=your-org
+# GITHUB_REPO=your-repo
+
+# Logging
+LOG_LEVEL=info
+EOF
+
+echo -e "${GREEN}β
Configuration saved to .env${NC}"
+echo ""
+
+# Setup channel
+echo "π‘ Setting up communication channel..."
+echo "Choose your channel type:"
+echo " 1) Redis (Recommended - requires Docker)"
+echo " 2) Discord (Easy - requires Discord bot)"
+echo " 3) SignalR (Advanced - requires relay server)"
+echo ""
+read -p "Enter choice [1-3]: " channel_choice
+
+case $channel_choice in
+ 1)
+ if [ "$DOCKER_AVAILABLE" = true ]; then
+ echo "Starting Redis container..."
+ docker run -d --name coorchat-redis -p 6379:6379 redis:7-alpine 2>/dev/null || {
+ echo -e "${YELLOW}Redis container already exists, starting it...${NC}"
+ docker start coorchat-redis
+ }
+ echo -e "${GREEN}β
Redis started on localhost:6379${NC}"
+ else
+ echo -e "${RED}β Docker not available. Please install Docker or choose another channel.${NC}"
+ exit 1
+ fi
+ ;;
+ 2)
+ echo ""
+ echo "Discord Setup Instructions:"
+ echo "1. Go to https://discord.com/developers/applications"
+ echo "2. Create a new application"
+ echo "3. Go to 'Bot' β 'Add Bot'"
+ echo "4. Copy the bot token"
+ echo "5. Enable 'Message Content Intent'"
+ echo "6. Invite bot to your server"
+ echo "7. Create a channel and copy its ID"
+ echo ""
+ read -p "Enter Discord bot token: " discord_token
+ read -p "Enter Discord channel ID: " channel_id
+
+ # Update .env
+ sed -i "s/CHANNEL_TYPE=redis/CHANNEL_TYPE=discord/" .env
+ echo "DISCORD_BOT_TOKEN=${discord_token}" >> .env
+ echo "DISCORD_CHANNEL_ID=${channel_id}" >> .env
+ echo -e "${GREEN}β
Discord configured${NC}"
+ ;;
+ 3)
+ echo "Starting SignalR relay server..."
+ cd ../../packages/relay-server
+ docker build -t coorchat-relay . || {
+ echo -e "${RED}β Failed to build relay server${NC}"
+ exit 1
+ }
+ docker run -d --name coorchat-relay -p 5001:5001 \
+ -e "Authentication__SharedToken=${TOKEN}" \
+ coorchat-relay
+ cd ../../packages/mcp-server
+
+ # Update .env
+ sed -i "s/CHANNEL_TYPE=redis/CHANNEL_TYPE=signalr/" .env
+ echo "SIGNALR_HUB_URL=https://localhost:5001/agentHub" >> .env
+ echo -e "${GREEN}β
SignalR relay server started${NC}"
+ ;;
+ *)
+ echo -e "${RED}Invalid choice${NC}"
+ exit 1
+ ;;
+esac
+
+echo ""
+
+# Run tests
+echo "π§ͺ Running tests..."
+npm test -- --run || {
+ echo -e "${YELLOW}β οΈ Some tests failed, but installation is complete${NC}"
+}
+echo ""
+
+# Generate Claude Desktop config
+echo "π Generating Claude Desktop configuration..."
+
+REPO_PATH=$(cd ../.. && pwd)
+MCP_SERVER_PATH="${REPO_PATH}/packages/mcp-server/dist/index.js"
+
+cat > claude_desktop_config.json << EOF
+{
+ "mcpServers": {
+ "coorchat": {
+ "command": "node",
+ "args": [
+ "${MCP_SERVER_PATH}"
+ ],
+ "env": {
+ "CHANNEL_TYPE": "redis",
+ "REDIS_HOST": "localhost",
+ "REDIS_PORT": "6379",
+ "SHARED_TOKEN": "${TOKEN}",
+ "AGENT_ID": "agent-claude-1",
+ "AGENT_ROLE": "developer",
+ "LOG_LEVEL": "info"
+ }
+ }
+ }
+}
+EOF
+
+echo -e "${GREEN}β
Claude Desktop config generated${NC}"
+echo ""
+
+# Installation complete
+echo "π Installation Complete!"
+echo "======================="
+echo ""
+echo -e "${BLUE}Next Steps:${NC}"
+echo ""
+echo "1. Copy the Claude Desktop configuration:"
+if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+ CLAUDE_CONFIG_PATH="%APPDATA%\\Claude\\claude_desktop_config.json"
+else
+ CLAUDE_CONFIG_PATH="~/.claude/claude_desktop_config.json"
+fi
+echo -e " ${YELLOW}${CLAUDE_CONFIG_PATH}${NC}"
+echo ""
+echo " Configuration file created at:"
+echo -e " ${YELLOW}./claude_desktop_config.json${NC}"
+echo ""
+echo "2. Restart Claude Desktop"
+echo ""
+echo "3. Test the MCP server in Claude:"
+echo " Ask: 'Can you check if coorchat is connected?'"
+echo ""
+echo -e "${BLUE}Useful Commands:${NC}"
+echo ""
+echo " Start agents manually:"
+echo -e " ${YELLOW}npm run cli -- agent start --role developer${NC}"
+echo ""
+echo " Monitor coordination:"
+echo -e " ${YELLOW}npm run cli -- monitor${NC}"
+echo ""
+echo " View logs:"
+echo -e " ${YELLOW}npm run cli -- logs${NC}"
+echo ""
+echo -e "${GREEN}Happy coordinating! π€${NC}"
diff --git a/specs/001-multi-agent-coordination/checklists/requirements.md b/specs/001-multi-agent-coordination/checklists/requirements.md
new file mode 100644
index 0000000..38d7914
--- /dev/null
+++ b/specs/001-multi-agent-coordination/checklists/requirements.md
@@ -0,0 +1,48 @@
+# Specification Quality Checklist: Multi-Agent Coordination System
+
+**Purpose**: Validate specification completeness and quality before proceeding to planning
+**Created**: 2026-02-14
+**Feature**: [spec.md](../spec.md)
+
+## Content Quality
+
+- [x] No implementation details (languages, frameworks, APIs)
+- [x] Focused on user value and business needs
+- [x] Written for non-technical stakeholders
+- [x] All mandatory sections completed
+
+## Requirement Completeness
+
+- [x] No [NEEDS CLARIFICATION] markers remain
+- [x] Requirements are testable and unambiguous
+- [x] Success criteria are measurable
+- [x] Success criteria are technology-agnostic (no implementation details)
+- [x] All acceptance scenarios are defined
+- [x] Edge cases are identified
+- [x] Scope is clearly bounded
+- [x] Dependencies and assumptions identified
+
+## Feature Readiness
+
+- [x] All functional requirements have clear acceptance criteria
+- [x] User scenarios cover primary flows
+- [x] Feature meets measurable outcomes defined in Success Criteria
+- [x] No implementation details leak into specification
+
+## Notes
+
+- **RESOLVED**: FR-004 clarification resolved - system will support all three channel types (Discord, SignalR, Redis cache) with pluggable architecture
+- **ADDED**: Cross-platform support requirements (FR-016 through FR-021) for Linux, macOS, Windows, and CI/CD environments
+- **ADDED**: Agent capability awareness and discovery requirements (FR-018, FR-19)
+- **ADDED**: Structured message protocol requirements (FR-023) for standardized agent communication
+- **ADDED**: Agent capability registration protocol (FR-024) for autonomous agent onboarding
+- **ADDED**: Task lifecycle events (FR-25) for coordinated task management
+- **ADDED**: MCP command interface with visual feedback (FR-026)
+- **ADDED**: Extensible agent roles (FR-001, FR-027) - users can define custom agent types on the fly
+- **ADDED**: New User Story 5: Agent Onboarding and Self-Management (Priority P2)
+- **ADDED**: Agent Capability entity for structured capability metadata
+- All checklist items now pass validation
+- Specification is well-structured with clear user stories, prioritization, and testable requirements
+- Success criteria are appropriately technology-agnostic and measurable
+- Pluggable architecture supports future extensibility for additional channels and plugins
+- Agent roles are now extensible - not limited to predefined list
diff --git a/specs/001-multi-agent-coordination/contracts/capability-schema.json b/specs/001-multi-agent-coordination/contracts/capability-schema.json
new file mode 100644
index 0000000..e48e616
--- /dev/null
+++ b/specs/001-multi-agent-coordination/contracts/capability-schema.json
@@ -0,0 +1,134 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://coorchat.dev/schemas/capability/v1.0.json",
+ "title": "Agent Capability Schema",
+ "description": "JSON Schema for agent capability registration and discovery",
+ "type": "object",
+ "required": ["agentId", "roleType", "platform", "tools"],
+ "properties": {
+ "agentId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier for the agent"
+ },
+ "roleType": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 50,
+ "description": "Agent role (extensible: developer, tester, architect, or custom)",
+ "examples": ["developer", "tester", "architect", "frontend", "backend", "infrastructure", "security-auditor", "documentation-writer"]
+ },
+ "platform": {
+ "type": "string",
+ "enum": ["Linux", "macOS", "Windows"],
+ "description": "Operating system platform"
+ },
+ "environmentType": {
+ "type": "string",
+ "description": "Execution environment",
+ "examples": ["local", "GitHub Actions", "Azure DevOps", "AWS CodeBuild", "GitLab CI", "CircleCI"]
+ },
+ "tools": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "minItems": 1,
+ "description": "Available commands, CLIs, or APIs the agent can use",
+ "examples": [["git", "npm", "docker"], ["pytest", "jest", "selenium"], ["aws-cli", "terraform", "kubectl"]]
+ },
+ "languages": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "description": "Programming languages the agent can work with",
+ "examples": [["TypeScript", "JavaScript", "Python"], ["C#", "Java", "Go"]]
+ },
+ "apiAccess": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": "External APIs the agent has access to",
+ "examples": [["GitHub API", "Stripe API", "Twilio API"], ["OpenAI API", "Anthropic API"]]
+ },
+ "resourceLimits": {
+ "$ref": "#/definitions/resourceLimits"
+ },
+ "customMetadata": {
+ "type": "object",
+ "additionalProperties": true,
+ "description": "Custom capability metadata for specialized agent types"
+ }
+ },
+ "definitions": {
+ "resourceLimits": {
+ "type": "object",
+ "description": "Resource constraints and quotas for the agent",
+ "properties": {
+ "apiQuotaPerHour": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Maximum API calls per hour"
+ },
+ "maxConcurrentTasks": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 10,
+ "default": 1,
+ "description": "Maximum number of simultaneous tasks"
+ },
+ "rateLimitPerMinute": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Maximum requests per minute"
+ },
+ "memoryLimitMB": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Memory constraint in megabytes"
+ }
+ }
+ }
+ },
+ "examples": [
+ {
+ "agentId": "550e8400-e29b-41d4-a716-446655440000",
+ "roleType": "developer",
+ "platform": "Linux",
+ "environmentType": "GitHub Actions",
+ "tools": ["git", "npm", "docker", "typescript", "jest"],
+ "languages": ["TypeScript", "JavaScript", "Python"],
+ "apiAccess": ["GitHub API", "npm Registry"],
+ "resourceLimits": {
+ "apiQuotaPerHour": 5000,
+ "maxConcurrentTasks": 2,
+ "rateLimitPerMinute": 60,
+ "memoryLimitMB": 2048
+ }
+ },
+ {
+ "agentId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
+ "roleType": "tester",
+ "platform": "Windows",
+ "environmentType": "local",
+ "tools": ["playwright", "jest", "selenium"],
+ "languages": ["JavaScript", "TypeScript"],
+ "apiAccess": ["BrowserStack API"],
+ "resourceLimits": {
+ "apiQuotaPerHour": 1000,
+ "maxConcurrentTasks": 3,
+ "rateLimitPerMinute": 30,
+ "memoryLimitMB": 4096
+ },
+ "customMetadata": {
+ "browserSupport": ["chrome", "firefox", "safari"],
+ "testTypes": ["unit", "integration", "e2e"]
+ }
+ }
+ ]
+}
diff --git a/specs/001-multi-agent-coordination/contracts/mcp-commands.yaml b/specs/001-multi-agent-coordination/contracts/mcp-commands.yaml
new file mode 100644
index 0000000..0d5da6b
--- /dev/null
+++ b/specs/001-multi-agent-coordination/contracts/mcp-commands.yaml
@@ -0,0 +1,291 @@
+# MCP Commands Specification
+# CoorChat Multi-Agent Coordination System
+
+version: "1.0"
+description: "MCP command interface for configuring and managing the CoorChat coordination system"
+
+commands:
+ configure:
+ description: "Initialize or update channel configuration"
+ usage: "/coorchat configure [--channel-type ] [--retention ]"
+ parameters:
+ - name: channel-type
+ type: enum
+ required: false
+ values: [discord, signalr, redis, relay]
+ description: "Channel type to configure (prompts if not provided)"
+
+ - name: retention
+ type: integer
+ required: false
+ default: 30
+ description: "Message history retention in days"
+
+ - name: config-file
+ type: string
+ required: false
+ default: ".coorchat/config.json"
+ description: "Path to configuration file"
+
+ interactive: true
+ prompts:
+ - step: 1
+ message: "Select channel type:"
+ options:
+ - value: discord
+ label: "Discord"
+ description: "Use Discord for coordination (requires bot token)"
+ - value: signalr
+ label: "SignalR"
+ description: "Use SignalR hub (requires hub URL)"
+ - value: redis
+ label: "Redis"
+ description: "Use Redis pub/sub (requires Redis connection)"
+ - value: relay
+ label: "CoorChat Relay Server"
+ description: "Use self-hosted relay server"
+
+ - step: 2
+ message: "Enter channel connection details:"
+ conditional: true
+ fields:
+ discord:
+ - name: bot-token
+ prompt: "Discord Bot Token"
+ env-var: "DISCORD_BOT_TOKEN"
+ secret: true
+ - name: guild-id
+ prompt: "Discord Guild (Server) ID"
+ - name: channel-id
+ prompt: "Discord Channel ID"
+
+ signalr:
+ - name: hub-url
+ prompt: "SignalR Hub URL"
+ example: "https://your-server.com/agenthub"
+ - name: access-token
+ prompt: "Access Token"
+ env-var: "SIGNALR_TOKEN"
+ secret: true
+
+ redis:
+ - name: host
+ prompt: "Redis Host"
+ default: "localhost"
+ - name: port
+ prompt: "Redis Port"
+ default: 6379
+ - name: password
+ prompt: "Redis Password (optional)"
+ env-var: "REDIS_PASSWORD"
+ secret: true
+ optional: true
+ - name: channel-name
+ prompt: "Redis Pub/Sub Channel Name"
+ default: "coorchat"
+
+ relay:
+ - name: server-url
+ prompt: "Relay Server URL"
+ example: "https://relay.coorchat.dev"
+ - name: channel-id
+ prompt: "Channel ID"
+ - name: access-token
+ prompt: "Access Token"
+ env-var: "RELAY_TOKEN"
+ secret: true
+
+ - step: 3
+ message: "Enter GitHub repository details:"
+ fields:
+ - name: github-token
+ prompt: "GitHub Personal Access Token"
+ env-var: "GITHUB_TOKEN"
+ secret: true
+ - name: repository-url
+ prompt: "Repository URL"
+ example: "https://github.com/owner/repo"
+ - name: webhook-secret
+ prompt: "Webhook Secret (optional, for webhook mode)"
+ env-var: "GITHUB_WEBHOOK_SECRET"
+ secret: true
+ optional: true
+
+ - step: 4
+ message: "Configure retention and logging:"
+ fields:
+ - name: retention-days
+ prompt: "Message retention (days)"
+ default: 30
+ - name: log-level
+ prompt: "Log level"
+ options: [ERROR, WARN, INFO, DEBUG]
+ default: INFO
+
+ output:
+ success: |
+ β
Configuration saved to {config-file}
+
+ Channel: {channel-type}
+ Retention: {retention-days} days
+ Log Level: {log-level}
+
+ Next: Run `/coorchat join` to connect an agent
+
+ error: |
+ β Configuration failed: {error-message}
+
+ join:
+ description: "Join an agent to the coordination channel"
+ usage: "/coorchat join [--role ] [--agent-id ]"
+ parameters:
+ - name: role
+ type: string
+ required: false
+ description: "Agent role type (prompts if not provided)"
+
+ - name: agent-id
+ type: uuid
+ required: false
+ description: "Agent UUID (generates if not provided)"
+
+ - name: capabilities
+ type: string
+ required: false
+ description: "Path to capabilities JSON file"
+
+ interactive: true
+ prompts:
+ - step: 1
+ message: "Select agent role:"
+ options:
+ - value: developer
+ label: "Developer"
+ - value: tester
+ label: "Tester"
+ - value: architect
+ label: "Architect"
+ - value: frontend
+ label: "Frontend Developer"
+ - value: backend
+ label: "Backend Developer"
+ - value: infrastructure
+ label: "Infrastructure Engineer"
+ - value: custom
+ label: "Custom Role"
+ prompt-for-value: true
+
+ - step: 2
+ message: "Agent capabilities will be auto-detected. Override?"
+ fields:
+ - name: override-capabilities
+ prompt: "Provide custom capabilities file path (or press Enter to skip)"
+ optional: true
+
+ output:
+ success: |
+ β
Agent joined successfully
+
+ Agent ID: {agent-id}
+ Role: {role}
+ Platform: {detected-platform}
+ Environment: {detected-environment}
+
+ Connected to: {channel-type} channel
+ Status: Connected
+
+ error: |
+ β Failed to join channel: {error-message}
+
+ status:
+ description: "Display current coordination channel status"
+ usage: "/coorchat status [--verbose]"
+ parameters:
+ - name: verbose
+ type: boolean
+ required: false
+ default: false
+ description: "Show detailed information"
+
+ output:
+ format: "text-ui"
+ template: |
+ ββ CoorChat Status βββββββββββββββββββββββββββ
+ β Channel: {channel-type}/{channel-name} β
+ β Connected Agents: {agent-count} β
+ ββββββββββββββββββββββββββββββββββββββββββββββ€
+ β β {agent-1-role} [{platform-1}] Task#{n}β
+ β β {agent-2-role} [{platform-2}] Idle β
+ β β {agent-3-role} [{platform-3}] Offline β
+ β ... β
+ ββββββββββββββββββββββββββββββββββββββββββββββ€
+ β Active Tasks: {active-task-count} β
+ β Messages Today: {message-count} β
+ β Uptime: {uptime} β
+ ββββββββββββββββββββββββββββββββββββββββββββββ
+
+ capabilities:
+ description: "List capabilities of connected agents"
+ usage: "/coorchat capabilities [--agent-id