commit fe432d4a09b6d34f4075443b8fdc08dc590231fd Author: Danijel Date: Tue Mar 17 23:45:42 2026 +0100 feat: add SolidTime MCP server for time tracking integration Implements a Model Context Protocol server that exposes SolidTime's time tracking API as 22+ tools for use with Claude, Cursor, and other MCP-compatible clients. Supports stdio and HTTP transport modes, Docker deployment, and self-hosted SolidTime instances. Co-Authored-By: Claude Opus 4.6 (1M context) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f627f6f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +dist +.env +.env.* +!.env.example +.git +.history +.mcp.json +*.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b24ecc3 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# ============================================================================= +# STDIO MODE (local, via `npm run dev` or `npm start`) +# ============================================================================= +# These are read from the server's environment when running in stdio mode. +SOLIDTIME_API_TOKEN=your-api-token-here +SOLIDTIME_ORGANIZATION_ID=your-organization-uuid-here +# Optional — defaults to https://app.solidtime.io +# SOLIDTIME_API_URL=https://your-instance.example.com +# Optional — IANA timezone name for displaying and accepting local times (e.g. Europe/Berlin, America/New_York) +# Without this, all times are shown and expected in UTC. +# SOLIDTIME_TIMEZONE=Europe/Berlin + +# ============================================================================= +# HTTP MODE (cloud, via `npm run start:http` or Docker) +# ============================================================================= +# In HTTP mode, API token and org ID come from MCP client headers, +# NOT from environment variables. Only server-level config goes here. +# +# PORT=3000 +# SOLIDTIME_API_URL=https://app.solidtime.io diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2ef9d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +*.tsbuildinfo +test-all-tools.sh diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a4a2d60 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "es5", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ebf7c5e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM node:22-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts + +COPY tsconfig.json ./ +COPY src/ ./src/ +RUN npx tsc + +FROM node:22-alpine + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force + +COPY --from=builder /app/dist ./dist + +ENV PORT=3000 +EXPOSE 3000 + +CMD ["node", "dist/http-server.js"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3dc791e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Manuel Ernst + +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..3fcc15d --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# solidtime-mcp-server + +[![CI](https://github.com/SwamiRama/solidtime-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/SwamiRama/solidtime-mcp-server/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![npm](https://img.shields.io/npm/v/solidtime-mcp-server)](https://www.npmjs.com/package/solidtime-mcp-server) + +MCP server for [SolidTime](https://www.solidtime.io/) — the open-source time tracking app. Start/stop timers, manage time entries, projects, clients, tags, and tasks directly from Claude, Cursor, or any MCP-compatible client. + +## Features + +- **22 tools** covering time entries, projects, clients, tags, tasks, and user info +- **Start/stop timers** with automatic active-timer detection +- **Aggregated reports** grouped by day, week, project, client, and more +- **Auto member_id resolution** — no manual configuration needed +- **Actionable error messages** — every error tells you what to do next +- **Zero external dependencies** beyond the MCP SDK (uses native `fetch`) +- Works with self-hosted SolidTime instances and the hosted version + +## Quick Start + +### Using npx (no install) + +```bash +npx solidtime-mcp-server +``` + +### Install globally + +```bash +npm install -g solidtime-mcp-server +``` + +### Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `SOLIDTIME_API_TOKEN` | Yes | — | Your SolidTime API token | +| `SOLIDTIME_ORGANIZATION_ID` | Yes | — | Your organization UUID | +| `SOLIDTIME_API_URL` | No | `https://app.solidtime.io` | Base URL for self-hosted instances | + +Get your API token from **SolidTime > Settings > API**. + +## Claude Desktop Configuration + +Add to your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "solidtime": { + "command": "npx", + "args": ["-y", "solidtime-mcp-server"], + "env": { + "SOLIDTIME_API_TOKEN": "your-token-here", + "SOLIDTIME_ORGANIZATION_ID": "your-org-uuid-here", + "SOLIDTIME_API_URL": "https://your-instance.example.com" + } + } + } +} +``` + +## Claude Code Configuration + +Add to your `.mcp.json`: + +```json +{ + "mcpServers": { + "solidtime": { + "command": "npx", + "args": ["-y", "solidtime-mcp-server"], + "env": { + "SOLIDTIME_API_TOKEN": "your-token-here", + "SOLIDTIME_ORGANIZATION_ID": "your-org-uuid-here", + "SOLIDTIME_API_URL": "https://your-instance.example.com" + } + } + } +} +``` + +## Tools + +### Time Entries (8 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_start_timer` | Start a running timer (checks for existing active timer first) | +| `solidtime_stop_timer` | Stop the active timer | +| `solidtime_get_active_timer` | Get the currently running timer | +| `solidtime_list_time_entries` | List entries with filters (date range, project, client, tags, billable) | +| `solidtime_create_time_entry` | Create a completed entry with start and end times | +| `solidtime_update_time_entry` | Update any field on an existing entry | +| `solidtime_delete_time_entry` | Permanently delete an entry | +| `solidtime_get_time_entry_report` | Aggregated report by day/week/month/project/client/etc. | + +### Projects (4 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_projects` | List all projects (filter by archived status) | +| `solidtime_create_project` | Create a project with name, color, billable rate | +| `solidtime_update_project` | Update project fields | +| `solidtime_delete_project` | Permanently delete a project | + +### Clients (3 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_clients` | List all clients (filter by archived status) | +| `solidtime_create_client` | Create a client | +| `solidtime_update_client` | Update a client's name | + +### Tags (3 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_tags` | List all tags | +| `solidtime_create_tag` | Create a tag | +| `solidtime_update_tag` | Update a tag's name | + +### Tasks (3 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_tasks` | List tasks (filter by project, done status) | +| `solidtime_create_task` | Create a task within a project | +| `solidtime_update_task` | Update task name, done status, or estimated time | + +### Users (1 tool) + +| Tool | Description | +|------|-------------| +| `solidtime_get_current_user` | Get your user profile and resolved member ID | + +## Usage Examples + +**Start tracking time:** +> "Start a timer for the website redesign project" + +**Log completed work:** +> "Create a time entry for today 9:00-11:30 on the API project, tagged as development" + +**Get a weekly report:** +> "Show me a report of this week's hours grouped by project" + +**Check what's running:** +> "Is there a timer running?" + +## Troubleshooting + +### "Authentication failed" +Your `SOLIDTIME_API_TOKEN` is invalid or expired. Generate a new one in SolidTime under Settings > API. + +### "Permission denied" +Your token doesn't have access to the specified organization. Verify `SOLIDTIME_ORGANIZATION_ID`. + +### "Cannot reach SolidTime" +Check that `SOLIDTIME_API_URL` is correct and the instance is accessible. For self-hosted: ensure the URL includes the protocol (e.g., `https://solidtime.example.com`). + +### "Could not find member for user" +The authenticated user is not a member of the specified organization. Check `SOLIDTIME_ORGANIZATION_ID`. + +## Development + +```bash +git clone https://github.com/SwamiRama/solidtime-mcp-server.git +cd solidtime-mcp-server +npm install +npm run dev # Run with tsx (dev mode) +npm run build # Compile TypeScript +npm run lint # ESLint +npm run typecheck # Type checking +npm run inspector # Test with MCP Inspector +``` + +## License + +MIT diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..13e739f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +services: + solidtime-mcp: + build: . + ports: + - "${PORT:-3000}:3000" + environment: + - PORT=3000 + # Optional: default SolidTime API URL for all sessions. + # Clients can override this via the x-solidtime-api-url header. + - SOLIDTIME_API_URL=${SOLIDTIME_API_URL:-https://app.solidtime.io} + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5a079b9 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,18 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + ignores: ["dist/"], + }, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_" }, + ], + }, + } +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c05ef9d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3117 @@ +{ + "name": "solidtime-mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "solidtime-mcp-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.6.1", + "dotenv": "^17.3.1", + "zod": "^3.23.8" + }, + "bin": { + "solidtime-mcp-server": "dist/index.js" + }, + "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/node": "^22.10.0", + "eslint": "^9.0.0", + "prettier": "^3.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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/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/js": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.10", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz", + "integrity": "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", + "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/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/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "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/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": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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/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/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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-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-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/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/hono": { + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.4.tgz", + "integrity": "sha512-ooiZW1Xy8rQ4oELQ++otI2T9DsKpV0M6c6cO6JGx4RTfav9poFFLlet9UMXHZnoM1yG0HWGlQLswBGX3RZmHtg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-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-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/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/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/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/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/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": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/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-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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/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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d5c4864 --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "solidtime-mcp-server", + "version": "1.0.0", + "description": "MCP server for SolidTime time tracking — start/stop timers, manage entries, projects, clients, tags, and tasks", + "author": "Manuel", + "license": "MIT", + "type": "module", + "bin": { + "solidtime-mcp-server": "dist/index.js" + }, + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "dev": "tsx src/index.ts", + "dev:http": "tsx src/http-server.ts", + "start": "node dist/index.js", + "start:http": "node dist/http-server.js", + "lint": "eslint src/", + "format": "prettier --write src/", + "format:check": "prettier --check src/", + "typecheck": "tsc --noEmit", + "prepare": "npm run build", + "inspector": "npx @modelcontextprotocol/inspector node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.6.1", + "dotenv": "^17.3.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/node": "^22.10.0", + "eslint": "^9.0.0", + "prettier": "^3.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "solidtime", + "time-tracking", + "ai", + "claude" + ], + "repository": { + "type": "git", + "url": "https://github.com/SwamiRama/solidtime-mcp-server" + } +} diff --git a/src/api-client.ts b/src/api-client.ts new file mode 100644 index 0000000..d4ce80a --- /dev/null +++ b/src/api-client.ts @@ -0,0 +1,138 @@ +import { DEFAULT_API_URL } from "./constants.js"; + +export class SolidTimeApiError extends Error { + constructor( + public status: number, + public body: unknown + ) { + super(getErrorMessage(status, body)); + this.name = "SolidTimeApiError"; + } +} + +function getErrorMessage(status: number, body: unknown): string { + const detail = typeof body === "object" && body !== null ? JSON.stringify(body) : String(body); + + switch (status) { + case 401: + return ( + "Authentication failed. Possible causes:\n" + + " 1. Your SOLIDTIME_API_TOKEN may be expired or revoked — generate a new one in Settings > API.\n" + + " 2. Self-hosted: Passport keys may be missing — run 'php artisan passport:keys' on the server.\n" + + " 3. Self-hosted: Verify SOLIDTIME_API_URL points to the correct instance." + ); + case 403: + return "Permission denied. Your token may lack access to this organization."; + case 404: + return "Resource not found. Use the list tools (e.g. solidtime_list_projects) to find valid IDs."; + case 422: + return `Validation error: ${formatValidationErrors(body)}`; + case 429: + return "Rate limited. Wait a moment and try again."; + default: + if (status >= 500) return `SolidTime server error (${status}). Try again later.`; + return `API error ${status}: ${detail}`; + } +} + +function formatValidationErrors(body: unknown): string { + if (typeof body !== "object" || body === null) return String(body); + const obj = body as Record; + if (obj.errors && typeof obj.errors === "object") { + return Object.entries(obj.errors as Record) + .map(([field, msgs]) => `${field}: ${msgs.join(", ")}`) + .join("; "); + } + if (obj.message) return String(obj.message); + return JSON.stringify(body); +} + +export class ApiClient { + private baseUrl: string; + private token: string; + + constructor(baseUrl: string | undefined, token: string) { + this.baseUrl = (baseUrl || DEFAULT_API_URL).replace(/\/$/, ""); + this.token = token.trim(); + } + + private async request(method: string, path: string, body?: unknown): Promise { + const url = path.startsWith("/api/v1") + ? `${this.baseUrl}${path}` + : `${this.baseUrl}/api/v1${path}`; + + const headers: Record = { + Authorization: `Bearer ${this.token}`, + Accept: "application/json", + }; + + if (body !== undefined) { + headers["Content-Type"] = "application/json"; + } + + let response: Response; + try { + response = await fetch(url, { + method, + headers, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + } catch (err) { + throw new Error( + `Cannot reach SolidTime at ${this.baseUrl}. Check the SOLIDTIME_API_URL setting. (${err})` + ); + } + + if (response.status === 204) { + return undefined as T; + } + + let responseBody: unknown; + try { + responseBody = await response.json(); + } catch { + responseBody = await response.text().catch(() => ""); + } + + if (!response.ok) { + throw new SolidTimeApiError(response.status, responseBody); + } + + return responseBody as T; + } + + get(path: string): Promise { + return this.request("GET", path); + } + + async getOrNull(path: string): Promise { + try { + return await this.request("GET", path); + } catch (err) { + if (err instanceof SolidTimeApiError && err.status === 404) { + return null; + } + throw err; + } + } + + post(path: string, body: unknown): Promise { + return this.request("POST", path, body); + } + + put(path: string, body: unknown): Promise { + return this.request("PUT", path, body); + } + + patch(path: string, body: unknown): Promise { + return this.request("PATCH", path, body); + } + + delete(path: string): Promise { + return this.request("DELETE", path); + } + + deleteWithBody(path: string, body: unknown): Promise { + return this.request("DELETE", path, body); + } +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..b151832 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,58 @@ +export const DEFAULT_API_URL = "https://app.solidtime.io"; + +export const API_PATHS = { + me: "/users/me", + myMemberships: "/users/me/memberships", + myTimeEntries: "/users/me/time-entries", + activeTimer: "/users/me/time-entries/active", + organization: (orgId: string) => `/api/v1/organizations/${orgId}`, + members: (orgId: string) => `/api/v1/organizations/${orgId}/members`, + member: (orgId: string, memberId: string) => + `/api/v1/organizations/${orgId}/members/${memberId}`, + memberInvitePlaceholder: (orgId: string, memberId: string) => + `/api/v1/organizations/${orgId}/members/${memberId}/invite-placeholder`, + memberMakePlaceholder: (orgId: string, memberId: string) => + `/api/v1/organizations/${orgId}/members/${memberId}/make-placeholder`, + timeEntries: (orgId: string) => `/api/v1/organizations/${orgId}/time-entries`, + timeEntry: (orgId: string, id: string) => `/api/v1/organizations/${orgId}/time-entries/${id}`, + timeEntryReport: (orgId: string) => `/api/v1/organizations/${orgId}/time-entries/aggregate`, + projects: (orgId: string) => `/api/v1/organizations/${orgId}/projects`, + project: (orgId: string, id: string) => `/api/v1/organizations/${orgId}/projects/${id}`, + projectMembers: (orgId: string, projectId: string) => + `/api/v1/organizations/${orgId}/projects/${projectId}/project-members`, + projectMember: (orgId: string, projectMemberId: string) => + `/api/v1/organizations/${orgId}/project-members/${projectMemberId}`, + clients: (orgId: string) => `/api/v1/organizations/${orgId}/clients`, + client: (orgId: string, id: string) => `/api/v1/organizations/${orgId}/clients/${id}`, + tags: (orgId: string) => `/api/v1/organizations/${orgId}/tags`, + tag: (orgId: string, id: string) => `/api/v1/organizations/${orgId}/tags/${id}`, + tasks: (orgId: string) => `/api/v1/organizations/${orgId}/tasks`, + task: (orgId: string, id: string) => `/api/v1/organizations/${orgId}/tasks/${id}`, + invitations: (orgId: string) => `/api/v1/organizations/${orgId}/invitations`, + invitation: (orgId: string, invitationId: string) => + `/api/v1/organizations/${orgId}/invitations/${invitationId}`, + invitationResend: (orgId: string, invitationId: string) => + `/api/v1/organizations/${orgId}/invitations/${invitationId}/resend`, +} as const; + +export const GROUP_BY_VALUES = [ + "day", + "week", + "month", + "year", + "user", + "project", + "task", + "client", + "billable", + "description", + "tag", +] as const; + +export type GroupBy = (typeof GROUP_BY_VALUES)[number]; + +export const PAGINATION = { + defaultLimit: 50, + maxLimit: 500, + defaultPage: 1, +} as const; diff --git a/src/formatting.ts b/src/formatting.ts new file mode 100644 index 0000000..3d02522 --- /dev/null +++ b/src/formatting.ts @@ -0,0 +1,78 @@ +export function formatDuration(seconds: number): string { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + if (h > 0 && m > 0) return `${h}h ${m}m`; + if (h > 0) return `${h}h`; + if (m > 0) return `${m}m`; + return `${seconds}s`; +} + +export function formatCurrency(cents: number): string { + const amount = cents / 100; + return amount.toLocaleString("en-US", { + style: "currency", + currency: "EUR", + minimumFractionDigits: 2, + }); +} + +export function formatDateTime(utcString: string, timezone?: string): string { + if (!timezone) { + return utcString.replace("T", " ").replace("Z", " UTC"); + } + const date = new Date(utcString); + const parts = new Intl.DateTimeFormat("en-CA", { + timeZone: timezone, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + timeZoneName: "short", + }).formatToParts(date); + const p: Record = {}; + for (const part of parts) { + if (part.type !== "literal") p[part.type] = part.value; + } + return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}:${p.second} ${p.timeZoneName}`; +} + +// Convert a time string to UTC. If the string already has a timezone suffix (Z or ±HH:MM) +// it is normalised and returned as-is. If it has no suffix it is treated as local time in +// the given timezone and converted to UTC. +export function toUTC(timeStr: string, timezone?: string): string { + if (!timeStr) return timeStr; + if (timeStr.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(timeStr)) { + return new Date(timeStr).toISOString().replace(/\.\d{3}Z$/, "Z"); + } + if (!timezone) return timeStr + "Z"; + + // Temporarily treat the naive string as UTC to get a Date object, then figure out + // what the local clock shows at that UTC moment and compute the real offset. + const naiveUTC = new Date(timeStr + "Z"); + const parts = new Intl.DateTimeFormat("en-CA", { + timeZone: timezone, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }).formatToParts(naiveUTC); + const p: Record = {}; + for (const part of parts) { + if (part.type !== "literal") p[part.type] = part.value; + } + const localAtNaive = new Date( + `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}:${p.second}Z` + ); + const offsetMs = localAtNaive.getTime() - naiveUTC.getTime(); + return new Date(naiveUTC.getTime() - offsetMs).toISOString().replace(/\.\d{3}Z$/, "Z"); +} + +export function nowUTC(): string { + return new Date().toISOString().replace(/\.\d{3}Z$/, "Z"); +} diff --git a/src/http-server.ts b/src/http-server.ts new file mode 100644 index 0000000..56544e7 --- /dev/null +++ b/src/http-server.ts @@ -0,0 +1,182 @@ +import { randomUUID } from "node:crypto"; +import http from "node:http"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; +import { createServer } from "./server.js"; + +const PORT = parseInt(process.env.PORT || "3000", 10); +const DEFAULT_SOLIDTIME_API_URL = process.env.SOLIDTIME_API_URL; + +interface Session { + transport: StreamableHTTPServerTransport; + createdAt: number; +} + +const sessions = new Map(); + +// Clean up stale sessions every 10 minutes +setInterval( + () => { + const now = Date.now(); + const maxAge = 30 * 60 * 1000; // 30 minutes + for (const [id, session] of sessions) { + if (now - session.createdAt > maxAge) { + session.transport.close().catch(() => {}); + sessions.delete(id); + } + } + }, + 10 * 60 * 1000 +); + +function extractConfig(req: http.IncomingMessage) { + const apiToken = req.headers["x-solidtime-api-token"] as string | undefined; + const organizationId = req.headers["x-solidtime-organization-id"] as string | undefined; + const apiUrl = + (req.headers["x-solidtime-api-url"] as string | undefined) || DEFAULT_SOLIDTIME_API_URL; + const timezone = req.headers["x-solidtime-timezone"] as string | undefined; + + if (!apiToken) { + throw new Error( + "Missing required header: x-solidtime-api-token. " + + "Configure it in your MCP client settings." + ); + } + + return { apiToken, organizationId, apiUrl, timezone }; +} + +function setCorsHeaders(res: http.ServerResponse) { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); + res.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, mcp-session-id, x-solidtime-api-token, x-solidtime-organization-id, x-solidtime-api-url, x-solidtime-timezone" + ); + res.setHeader("Access-Control-Expose-Headers", "mcp-session-id"); +} + +const httpServer = http.createServer(async (req, res) => { + setCorsHeaders(res); + + if (req.method === "OPTIONS") { + res.writeHead(204); + res.end(); + return; + } + + if (req.url === "/health") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "ok", sessions: sessions.size })); + return; + } + + if (req.url !== "/mcp") { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not found. Use /mcp for MCP protocol or /health for health check." })); + return; + } + + const sessionId = req.headers["mcp-session-id"] as string | undefined; + + try { + // GET = SSE stream listener, DELETE = close session + if (req.method === "GET" || req.method === "DELETE") { + if (!sessionId || !sessions.has(sessionId)) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Invalid or missing session" })); + return; + } + + const session = sessions.get(sessionId)!; + + if (req.method === "DELETE") { + await session.transport.close(); + sessions.delete(sessionId); + res.writeHead(204); + res.end(); + return; + } + + await session.transport.handleRequest(req, res); + return; + } + + if (req.method === "POST") { + const body = await new Promise((resolve, reject) => { + let data = ""; + req.on("data", (chunk: Buffer) => (data += chunk.toString())); + req.on("end", () => resolve(data)); + req.on("error", reject); + }); + const parsedBody = JSON.parse(body); + + if (!sessionId) { + // Must be an initialization request + const messages = Array.isArray(parsedBody) ? parsedBody : [parsedBody]; + const isInit = messages.some(isInitializeRequest); + + if (!isInit) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + error: "Missing mcp-session-id header for non-initialization request", + }) + ); + return; + } + + // Extract credentials from headers and create a per-session MCP server + const config = extractConfig(req); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + }); + const mcpServer = await createServer(config); + await mcpServer.connect(transport); + + const sid = transport.sessionId!; + sessions.set(sid, { transport, createdAt: Date.now() }); + + transport.onclose = () => { + sessions.delete(sid); + }; + + await transport.handleRequest(req, res, parsedBody); + } else { + // Existing session + const session = sessions.get(sessionId); + if (!session) { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Session not found. It may have expired." })); + return; + } + // Refresh session timestamp + session.createdAt = Date.now(); + await session.transport.handleRequest(req, res, parsedBody); + } + return; + } + + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Method not allowed" })); + } catch (err) { + console.error("Request error:", err); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + error: err instanceof Error ? err.message : "Internal server error", + }) + ); + } + } +}); + +httpServer.listen(PORT, "0.0.0.0", () => { + console.log(`SolidTime MCP HTTP server listening on port ${PORT}`); + console.log(` MCP endpoint: http://0.0.0.0:${PORT}/mcp`); + console.log(` Health check: http://0.0.0.0:${PORT}/health`); + if (DEFAULT_SOLIDTIME_API_URL) { + console.log(` Default API URL: ${DEFAULT_SOLIDTIME_API_URL}`); + } +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f35b216 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +import "dotenv/config"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createServer } from "./server.js"; + +async function main() { + const apiToken = process.env.SOLIDTIME_API_TOKEN; + const organizationId = process.env.SOLIDTIME_ORGANIZATION_ID; + const apiUrl = process.env.SOLIDTIME_API_URL; + const timezone = process.env.SOLIDTIME_TIMEZONE; + + if (!apiToken) { + console.error("Error: SOLIDTIME_API_TOKEN environment variable is required."); + console.error("Get your API token from your SolidTime instance under Settings > API."); + process.exit(1); + } + + const server = await createServer({ apiToken, organizationId, apiUrl, timezone }); + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/src/schemas.ts b/src/schemas.ts new file mode 100644 index 0000000..2187648 --- /dev/null +++ b/src/schemas.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; + +export const coerceBoolean = z.preprocess((val) => { + if (typeof val === "string") { + if (val === "true") return true; + if (val === "false") return false; + } + return val; +}, z.boolean()); + +export const coerceUuidArray = z.preprocess((val) => { + if (typeof val === "string") { + try { + return JSON.parse(val); + } catch { + return val; + } + } + return val; +}, z.array(z.string().uuid())); diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..07c380d --- /dev/null +++ b/src/server.ts @@ -0,0 +1,107 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ApiClient, SolidTimeApiError } from "./api-client.js"; +import { API_PATHS, DEFAULT_API_URL } from "./constants.js"; +import { registerUserTools } from "./tools/users.js"; +import { registerTimeEntryTools } from "./tools/time-entries.js"; +import { registerProjectTools } from "./tools/projects.js"; +import { registerClientTools } from "./tools/clients.js"; +import { registerTagTools } from "./tools/tags.js"; +import { registerTaskTools } from "./tools/tasks.js"; +import { registerMemberTools } from "./tools/members.js"; +import { registerOrganizationTools } from "./tools/organizations.js"; +import { registerProjectMemberTools } from "./tools/project-members.js"; +import { registerInvitationTools } from "./tools/invitations.js"; +import type { User, UserMembership } from "./types.js"; + +interface ServerConfig { + apiToken: string; + organizationId?: string; + apiUrl?: string; + timezone?: string; +} + +export async function createServer(config: ServerConfig) { + const apiUrl = config.apiUrl || DEFAULT_API_URL; + const api = new ApiClient(apiUrl, config.apiToken); + + console.error(`SolidTime MCP: Connecting to ${apiUrl}...`); + + // Fetch user and memberships in parallel + let userResponse: { data: User }; + try { + userResponse = await api.get<{ data: User }>(API_PATHS.me); + } catch (err) { + if (err instanceof SolidTimeApiError && err.status === 401) { + throw new Error( + `Authentication failed against ${apiUrl}.\n` + + ` Token: ${config.apiToken.substring(0, 10)}...(${config.apiToken.length} chars)\n` + + ` Possible fixes:\n` + + ` 1. Generate a new API token in Settings > API.\n` + + ` 2. Self-hosted: run 'php artisan passport:keys' on the server.\n` + + ` 3. Verify SOLIDTIME_API_URL is correct (currently: ${apiUrl}).` + ); + } + throw err; + } + const user = userResponse.data; + + const membershipsResponse = await api.get<{ data: UserMembership[] }>(API_PATHS.myMemberships); + const memberships = membershipsResponse.data ?? []; + + if (memberships.length === 0) { + throw new Error(`User ${user.email} has no organization memberships.`); + } + + let membership: UserMembership; + if (config.organizationId) { + const found = memberships.find((m) => m.organization.id === config.organizationId); + if (!found) { + const available = memberships + .map((m) => ` ${m.organization.id} (${m.organization.name})`) + .join("\n"); + throw new Error( + `Organization ${config.organizationId} not found. Your memberships:\n${available}\n` + + `Update your organization ID setting.` + ); + } + membership = found; + } else if (memberships.length === 1) { + membership = memberships[0]; + console.error(`SolidTime MCP: Auto-selected organization "${membership.organization.name}" (${membership.organization.id})`); + } else { + const available = memberships + .map((m) => ` ${m.organization.id} (${m.organization.name})`) + .join("\n"); + throw new Error( + `Multiple organizations found. Set organization ID to one of:\n${available}` + ); + } + + const orgId = membership.organization.id; + const memberId = membership.id; + const getMemberId = () => memberId; + const timezone = config.timezone; + + console.error( + `SolidTime MCP: Authenticated as ${user.name} (${user.email}), org "${membership.organization.name}", member ${memberId}` + + (timezone ? `, timezone ${timezone}` : "") + ); + + const server = new McpServer({ + name: "solidtime", + version: "1.0.0", + }); + + registerUserTools(server, api, getMemberId); + registerTimeEntryTools(server, api, orgId, getMemberId, timezone); + registerProjectTools(server, api, orgId); + registerClientTools(server, api, orgId); + registerTagTools(server, api, orgId); + registerTaskTools(server, api, orgId); + registerMemberTools(server, api, orgId); + registerOrganizationTools(server, api, orgId); + registerProjectMemberTools(server, api, orgId); + registerInvitationTools(server, api, orgId); + + return server; +} diff --git a/src/tools/clients.ts b/src/tools/clients.ts new file mode 100644 index 0000000..b0fd1e8 --- /dev/null +++ b/src/tools/clients.ts @@ -0,0 +1,124 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import type { Client, PaginatedResponse } from "../types.js"; + +export function registerClientTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_clients", + { + title: "List Clients", + description: "List all clients in the organization. Optionally filter by archived status.", + inputSchema: { + archived: z.enum(["true", "false"]).optional().describe("Filter by archived status"), + }, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const query = new URLSearchParams(); + if (params.archived) query.set("archived", params.archived); + const qs = query.toString(); + const path = qs ? `${API_PATHS.clients(orgId)}?${qs}` : API_PATHS.clients(orgId); + + const result = await api.get>(path); + const clients = result.data ?? []; + if (clients.length === 0) { + return { content: [{ type: "text", text: "No clients found." }] }; + } + + const lines = clients.map( + (c) => `ID: ${c.id}\nName: ${c.name}\nArchived: ${c.is_archived ? "Yes" : "No"}` + ); + lines.unshift(`Found ${clients.length} clients:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_create_client", + { + title: "Create Client", + description: "Create a new client.", + inputSchema: { + name: z.string().min(1).describe("Client name"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.post<{ data: Client }>(API_PATHS.clients(orgId), { + name: params.name, + }); + return { + content: [ + { + type: "text", + text: `Client created.\n\nID: ${result.data.id}\nName: ${result.data.name}`, + }, + ], + }; + } + ); + + server.registerTool( + "solidtime_update_client", + { + title: "Update Client", + description: "Update a client's name.", + inputSchema: { + id: z.string().uuid().describe("Client UUID to update"), + name: z.string().min(1).describe("New client name"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.put<{ data: Client }>(API_PATHS.client(orgId, params.id), { + name: params.name, + }); + return { + content: [ + { + type: "text", + text: `Client updated.\n\nID: ${result.data.id}\nName: ${result.data.name}`, + }, + ], + }; + } + ); + + server.registerTool( + "solidtime_delete_client", + { + title: "Delete Client", + description: "Permanently delete a client. This cannot be undone.", + inputSchema: { + id: z.string().uuid().describe("Client UUID to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.client(orgId, params.id)); + return { content: [{ type: "text", text: `Client ${params.id} deleted.` }] }; + } + ); +} diff --git a/src/tools/invitations.ts b/src/tools/invitations.ts new file mode 100644 index 0000000..c91cf02 --- /dev/null +++ b/src/tools/invitations.ts @@ -0,0 +1,112 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import type { Invitation, PaginatedResponse } from "../types.js"; + +export function registerInvitationTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_invitations", + { + title: "List Invitations", + description: "List all pending invitations for the organization.", + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const result = await api.get>( + API_PATHS.invitations(orgId) + ); + const invitations = result.data ?? []; + if (invitations.length === 0) { + return { content: [{ type: "text", text: "No pending invitations." }] }; + } + + const lines = invitations.map((inv) => formatInvitation(inv)); + lines.unshift(`Found ${invitations.length} invitations:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_create_invitation", + { + title: "Create Invitation", + description: "Invite a new user to the organization by email.", + inputSchema: { + email: z.string().email().describe("Email address to invite"), + role: z.string().describe("Role to assign (e.g. admin, manager, employee)"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.post<{ data: Invitation }>(API_PATHS.invitations(orgId), { + email: params.email, + role: params.role, + }); + return { + content: [ + { + type: "text", + text: `Invitation sent.\n\n${formatInvitation(result.data)}`, + }, + ], + }; + } + ); + + server.registerTool( + "solidtime_resend_invitation", + { + title: "Resend Invitation", + description: "Resend an existing invitation email.", + inputSchema: { + id: z.string().uuid().describe("Invitation UUID to resend"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + await api.post(API_PATHS.invitationResend(orgId, params.id), {}); + return { content: [{ type: "text", text: `Invitation ${params.id} resent.` }] }; + } + ); + + server.registerTool( + "solidtime_delete_invitation", + { + title: "Delete Invitation", + description: "Delete a pending invitation.", + inputSchema: { + id: z.string().uuid().describe("Invitation UUID to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.invitation(orgId, params.id)); + return { content: [{ type: "text", text: `Invitation ${params.id} deleted.` }] }; + } + ); +} + +function formatInvitation(inv: Invitation): string { + return [`ID: ${inv.id}`, `Email: ${inv.email}`, `Role: ${inv.role}`].join("\n"); +} diff --git a/src/tools/members.ts b/src/tools/members.ts new file mode 100644 index 0000000..344f80e --- /dev/null +++ b/src/tools/members.ts @@ -0,0 +1,150 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import { formatCurrency } from "../formatting.js"; +import type { Member, PaginatedResponse } from "../types.js"; + +export function registerMemberTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_members", + { + title: "List Members", + description: + "List all members of the organization, including their roles and billable rates.", + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const result = await api.get>(API_PATHS.members(orgId)); + const members = result.data ?? []; + if (members.length === 0) { + return { content: [{ type: "text", text: "No members found." }] }; + } + + const lines = members.map((m) => formatMember(m)); + lines.unshift(`Found ${members.length} members:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_update_member", + { + title: "Update Member", + description: "Update a member's role or billable rate.", + inputSchema: { + id: z.string().uuid().describe("Member UUID to update"), + role: z.string().optional().describe("New role (e.g. admin, manager, employee, placeholder)"), + billable_rate: z + .number() + .int() + .min(0) + .optional() + .describe("New billable rate in cents (e.g. 15000 = EUR 150.00)"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = {}; + if (params.role !== undefined) body.role = params.role; + if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate; + + const result = await api.put<{ data: Member }>( + API_PATHS.member(orgId, params.id), + body + ); + return { + content: [{ type: "text", text: `Member updated.\n\n${formatMember(result.data)}` }], + }; + } + ); + + server.registerTool( + "solidtime_remove_member", + { + title: "Remove Member", + description: "Remove a member from the organization. This cannot be undone.", + inputSchema: { + id: z.string().uuid().describe("Member UUID to remove"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.member(orgId, params.id)); + return { content: [{ type: "text", text: `Member ${params.id} removed.` }] }; + } + ); + + server.registerTool( + "solidtime_invite_placeholder_member", + { + title: "Invite Placeholder Member", + description: + "Send an invitation to a placeholder member so they can create an account and take over their time entries.", + inputSchema: { + id: z.string().uuid().describe("Placeholder member UUID to invite"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + await api.post(API_PATHS.memberInvitePlaceholder(orgId, params.id), {}); + return { + content: [{ type: "text", text: `Invitation sent to placeholder member ${params.id}.` }], + }; + } + ); + + server.registerTool( + "solidtime_make_placeholder_member", + { + title: "Make Placeholder Member", + description: + "Convert a real member into a placeholder member. The user will lose access but their time entries are preserved.", + inputSchema: { + id: z.string().uuid().describe("Member UUID to convert to placeholder"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.post(API_PATHS.memberMakePlaceholder(orgId, params.id), {}); + return { + content: [{ type: "text", text: `Member ${params.id} converted to placeholder.` }], + }; + } + ); +} + +function formatMember(m: Member): string { + const parts = [ + `ID: ${m.id}`, + `User ID: ${m.user_id}`, + `Role: ${m.role}`, + ]; + if (m.billable_rate !== null) parts.push(`Billable Rate: ${formatCurrency(m.billable_rate)}/h`); + return parts.join("\n"); +} diff --git a/src/tools/organizations.ts b/src/tools/organizations.ts new file mode 100644 index 0000000..e41b3f0 --- /dev/null +++ b/src/tools/organizations.ts @@ -0,0 +1,80 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import { formatCurrency } from "../formatting.js"; +import type { Organization } from "../types.js"; + +export function registerOrganizationTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_get_organization", + { + title: "Get Organization", + description: "Get the current organization's details including name, currency, and billable rate.", + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const result = await api.get<{ data: Organization }>(API_PATHS.organization(orgId)); + return { + content: [{ type: "text", text: formatOrganization(result.data) }], + }; + } + ); + + server.registerTool( + "solidtime_update_organization", + { + title: "Update Organization", + description: "Update the organization's name, currency, or billable rate.", + inputSchema: { + name: z.string().min(1).optional().describe("New organization name"), + currency: z.string().min(3).max(3).optional().describe("New currency code (e.g. EUR, USD)"), + billable_rate: z + .number() + .int() + .min(0) + .optional() + .describe("New default billable rate in cents (e.g. 15000 = EUR 150.00)"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = {}; + if (params.name !== undefined) body.name = params.name; + if (params.currency !== undefined) body.currency = params.currency; + if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate; + + const result = await api.put<{ data: Organization }>( + API_PATHS.organization(orgId), + body + ); + return { + content: [ + { type: "text", text: `Organization updated.\n\n${formatOrganization(result.data)}` }, + ], + }; + } + ); +} + +function formatOrganization(org: Organization): string { + const parts = [ + `ID: ${org.id}`, + `Name: ${org.name}`, + `Currency: ${org.currency}`, + ]; + if (org.billable_rate !== null) { + parts.push(`Default Billable Rate: ${formatCurrency(org.billable_rate)}/h`); + } + return parts.join("\n"); +} diff --git a/src/tools/project-members.ts b/src/tools/project-members.ts new file mode 100644 index 0000000..11500be --- /dev/null +++ b/src/tools/project-members.ts @@ -0,0 +1,147 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import { formatCurrency } from "../formatting.js"; +import type { ProjectMember, PaginatedResponse } from "../types.js"; + +export function registerProjectMemberTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_project_members", + { + title: "List Project Members", + description: "List all members assigned to a specific project.", + inputSchema: { + project_id: z.string().uuid().describe("Project UUID"), + }, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.get>( + API_PATHS.projectMembers(orgId, params.project_id) + ); + const members = result.data ?? []; + if (members.length === 0) { + return { content: [{ type: "text", text: "No project members found." }] }; + } + + const lines = members.map((m) => formatProjectMember(m)); + lines.unshift(`Found ${members.length} project members:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_add_project_member", + { + title: "Add Project Member", + description: "Add a member to a project with an optional custom billable rate.", + inputSchema: { + project_id: z.string().uuid().describe("Project UUID"), + member_id: z.string().uuid().describe("Member UUID to add"), + billable_rate: z + .number() + .int() + .min(0) + .optional() + .describe("Custom billable rate in cents for this member on this project"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = { + member_id: params.member_id, + }; + if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate; + + const result = await api.post<{ data: ProjectMember }>( + API_PATHS.projectMembers(orgId, params.project_id), + body + ); + return { + content: [ + { type: "text", text: `Project member added.\n\n${formatProjectMember(result.data)}` }, + ], + }; + } + ); + + server.registerTool( + "solidtime_update_project_member", + { + title: "Update Project Member", + description: "Update a project member's billable rate.", + inputSchema: { + id: z.string().uuid().describe("Project member UUID to update"), + billable_rate: z + .number() + .int() + .min(0) + .describe("New billable rate in cents for this project member"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.put<{ data: ProjectMember }>( + API_PATHS.projectMember(orgId, params.id), + { billable_rate: params.billable_rate } + ); + return { + content: [ + { + type: "text", + text: `Project member updated.\n\n${formatProjectMember(result.data)}`, + }, + ], + }; + } + ); + + server.registerTool( + "solidtime_remove_project_member", + { + title: "Remove Project Member", + description: "Remove a member from a project.", + inputSchema: { + id: z.string().uuid().describe("Project member UUID to remove"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.projectMember(orgId, params.id)); + return { content: [{ type: "text", text: `Project member ${params.id} removed.` }] }; + } + ); +} + +function formatProjectMember(pm: ProjectMember): string { + const parts = [ + `ID: ${pm.id}`, + `Member ID: ${pm.member_id}`, + `Project ID: ${pm.project_id}`, + ]; + if (pm.billable_rate !== null) { + parts.push(`Billable Rate: ${formatCurrency(pm.billable_rate)}/h`); + } + return parts.join("\n"); +} diff --git a/src/tools/projects.ts b/src/tools/projects.ts new file mode 100644 index 0000000..b44dd7d --- /dev/null +++ b/src/tools/projects.ts @@ -0,0 +1,178 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import { coerceBoolean } from "../schemas.js"; +import { formatCurrency, formatDuration } from "../formatting.js"; +import type { Project, PaginatedResponse } from "../types.js"; + +export function registerProjectTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_projects", + { + title: "List Projects", + description: "List all projects in the organization. Optionally filter by archived status.", + inputSchema: { + archived: z.enum(["true", "false"]).optional().describe("Filter by archived status"), + }, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const query = new URLSearchParams(); + if (params.archived) query.set("archived", params.archived); + const qs = query.toString(); + const path = qs ? `${API_PATHS.projects(orgId)}?${qs}` : API_PATHS.projects(orgId); + + const result = await api.get>(path); + const projects = result.data ?? []; + if (projects.length === 0) { + return { content: [{ type: "text", text: "No projects found." }] }; + } + + const lines = projects.map((p) => formatProject(p)); + lines.unshift(`Found ${projects.length} projects:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_create_project", + { + title: "Create Project", + description: "Create a new project.", + inputSchema: { + name: z.string().min(1).describe("Project name"), + color: z + .string() + .regex(/^#[0-9a-f]{6}$/) + .describe("Hex color, lowercase (e.g. #4caf50)"), + is_billable: coerceBoolean.describe("Whether time tracked to this project is billable"), + client_id: z.string().uuid().describe("Client UUID to assign (required)"), + billable_rate: z + .number() + .int() + .min(0) + .optional() + .describe("Billable rate in cents (e.g. 15000 = EUR 150.00)"), + estimated_time: z.number().int().min(0).optional().describe("Estimated time in seconds"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = { + name: params.name, + color: params.color, + is_billable: params.is_billable, + client_id: params.client_id, + }; + if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate; + if (params.estimated_time !== undefined) body.estimated_time = params.estimated_time; + + const result = await api.post<{ data: Project }>(API_PATHS.projects(orgId), body); + return { + content: [{ type: "text", text: `Project created.\n\n${formatProject(result.data)}` }], + }; + } + ); + + server.registerTool( + "solidtime_update_project", + { + title: "Update Project", + description: "Update an existing project. Only provided fields will be changed.", + inputSchema: { + id: z.string().uuid().describe("Project UUID to update"), + name: z.string().min(1).optional().describe("New project name"), + color: z + .string() + .regex(/^#[0-9a-f]{6}$/) + .optional() + .describe("New hex color, lowercase (e.g. #4caf50)"), + is_billable: coerceBoolean.optional().describe("New billable status"), + is_archived: coerceBoolean.optional().describe("Archive or unarchive"), + client_id: z.string().uuid().optional().describe("New client UUID"), + billable_rate: z.number().int().min(0).optional().describe("New billable rate in cents"), + estimated_time: z + .number() + .int() + .min(0) + .optional() + .describe("New estimated time in seconds"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + // SolidTime API requires all fields on PUT, so fetch current state first + const current = await api.get<{ data: Project }>(API_PATHS.project(orgId, params.id)); + const project = current.data; + + const body: Record = { + name: params.name ?? project.name, + color: params.color ?? project.color, + is_billable: params.is_billable ?? project.is_billable, + client_id: params.client_id ?? project.client_id, + }; + if (params.is_archived !== undefined) body.is_archived = params.is_archived; + if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate; + else if (project.billable_rate !== null) body.billable_rate = project.billable_rate; + if (params.estimated_time !== undefined) body.estimated_time = params.estimated_time; + else if (project.estimated_time !== null) body.estimated_time = project.estimated_time; + + const result = await api.put<{ data: Project }>(API_PATHS.project(orgId, params.id), body); + return { + content: [{ type: "text", text: `Project updated.\n\n${formatProject(result.data)}` }], + }; + } + ); + + server.registerTool( + "solidtime_delete_project", + { + title: "Delete Project", + description: "Permanently delete a project. This cannot be undone.", + inputSchema: { + id: z.string().uuid().describe("Project UUID to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.project(orgId, params.id)); + return { content: [{ type: "text", text: `Project ${params.id} deleted.` }] }; + } + ); +} + +function formatProject(p: Project): string { + const parts = [ + `ID: ${p.id}`, + `Name: ${p.name}`, + `Color: ${p.color}`, + `Billable: ${p.is_billable ? "Yes" : "No"}`, + `Archived: ${p.is_archived ? "Yes" : "No"}`, + ]; + if (p.billable_rate !== null) parts.push(`Rate: ${formatCurrency(p.billable_rate)}/h`); + if (p.client_id) parts.push(`Client ID: ${p.client_id}`); + if (p.estimated_time !== null) parts.push(`Estimated: ${formatDuration(p.estimated_time)}`); + if (p.spent_time > 0) parts.push(`Spent: ${formatDuration(p.spent_time)}`); + return parts.join("\n"); +} diff --git a/src/tools/tags.ts b/src/tools/tags.ts new file mode 100644 index 0000000..ede3e4a --- /dev/null +++ b/src/tools/tags.ts @@ -0,0 +1,112 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import type { Tag, PaginatedResponse } from "../types.js"; + +export function registerTagTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_tags", + { + title: "List Tags", + description: "List all tags in the organization.", + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const result = await api.get>(API_PATHS.tags(orgId)); + const tags = result.data ?? []; + if (tags.length === 0) { + return { content: [{ type: "text", text: "No tags found." }] }; + } + + const lines = tags.map((t) => `ID: ${t.id}\nName: ${t.name}`); + lines.unshift(`Found ${tags.length} tags:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_create_tag", + { + title: "Create Tag", + description: "Create a new tag.", + inputSchema: { + name: z.string().min(1).describe("Tag name"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.post<{ data: Tag }>(API_PATHS.tags(orgId), { name: params.name }); + return { + content: [ + { + type: "text", + text: `Tag created.\n\nID: ${result.data.id}\nName: ${result.data.name}`, + }, + ], + }; + } + ); + + server.registerTool( + "solidtime_update_tag", + { + title: "Update Tag", + description: "Update a tag's name.", + inputSchema: { + id: z.string().uuid().describe("Tag UUID to update"), + name: z.string().min(1).describe("New tag name"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const result = await api.put<{ data: Tag }>(API_PATHS.tag(orgId, params.id), { + name: params.name, + }); + return { + content: [ + { + type: "text", + text: `Tag updated.\n\nID: ${result.data.id}\nName: ${result.data.name}`, + }, + ], + }; + } + ); + + server.registerTool( + "solidtime_delete_tag", + { + title: "Delete Tag", + description: "Permanently delete a tag. This cannot be undone.", + inputSchema: { + id: z.string().uuid().describe("Tag UUID to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.tag(orgId, params.id)); + return { content: [{ type: "text", text: `Tag ${params.id} deleted.` }] }; + } + ); +} diff --git a/src/tools/tasks.ts b/src/tools/tasks.ts new file mode 100644 index 0000000..8b235a3 --- /dev/null +++ b/src/tools/tasks.ts @@ -0,0 +1,144 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import { coerceBoolean } from "../schemas.js"; +import { formatDuration } from "../formatting.js"; +import type { Task, PaginatedResponse } from "../types.js"; + +export function registerTaskTools(server: McpServer, api: ApiClient, orgId: string) { + server.registerTool( + "solidtime_list_tasks", + { + title: "List Tasks", + description: "List tasks. Optionally filter by project or done status.", + inputSchema: { + project_id: z.string().uuid().optional().describe("Filter by project UUID"), + done: z.enum(["true", "false"]).optional().describe("Filter by done status"), + }, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const query = new URLSearchParams(); + if (params.project_id) query.set("project_id", params.project_id); + if (params.done) query.set("done", params.done); + const qs = query.toString(); + const path = qs ? `${API_PATHS.tasks(orgId)}?${qs}` : API_PATHS.tasks(orgId); + + const result = await api.get>(path); + const tasks = result.data ?? []; + if (tasks.length === 0) { + return { content: [{ type: "text", text: "No tasks found." }] }; + } + + const lines = tasks.map((t) => formatTask(t)); + lines.unshift(`Found ${tasks.length} tasks:\n`); + return { content: [{ type: "text", text: lines.join("\n\n") }] }; + } + ); + + server.registerTool( + "solidtime_create_task", + { + title: "Create Task", + description: "Create a new task within a project.", + inputSchema: { + name: z.string().min(1).describe("Task name"), + project_id: z.string().uuid().describe("Project UUID this task belongs to"), + estimated_time: z.number().int().min(0).optional().describe("Estimated time in seconds"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = { + name: params.name, + project_id: params.project_id, + }; + if (params.estimated_time !== undefined) body.estimated_time = params.estimated_time; + + const result = await api.post<{ data: Task }>(API_PATHS.tasks(orgId), body); + return { + content: [{ type: "text", text: `Task created.\n\n${formatTask(result.data)}` }], + }; + } + ); + + server.registerTool( + "solidtime_update_task", + { + title: "Update Task", + description: "Update a task's name, done status, or estimated time.", + inputSchema: { + id: z.string().uuid().describe("Task UUID to update"), + name: z.string().min(1).optional().describe("New task name"), + is_done: coerceBoolean.optional().describe("Mark task as done or not done"), + estimated_time: z + .number() + .int() + .min(0) + .optional() + .describe("New estimated time in seconds"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = {}; + if (params.name !== undefined) body.name = params.name; + if (params.is_done !== undefined) body.is_done = params.is_done; + if (params.estimated_time !== undefined) body.estimated_time = params.estimated_time; + + const result = await api.put<{ data: Task }>(API_PATHS.task(orgId, params.id), body); + return { + content: [{ type: "text", text: `Task updated.\n\n${formatTask(result.data)}` }], + }; + } + ); + + server.registerTool( + "solidtime_delete_task", + { + title: "Delete Task", + description: "Permanently delete a task. This cannot be undone.", + inputSchema: { + id: z.string().uuid().describe("Task UUID to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.task(orgId, params.id)); + return { content: [{ type: "text", text: `Task ${params.id} deleted.` }] }; + } + ); +} + +function formatTask(t: Task): string { + const parts = [ + `ID: ${t.id}`, + `Name: ${t.name}`, + `Done: ${t.is_done ? "Yes" : "No"}`, + `Project ID: ${t.project_id}`, + ]; + if (t.estimated_time !== null) parts.push(`Estimated: ${formatDuration(t.estimated_time)}`); + if (t.spent_time > 0) parts.push(`Spent: ${formatDuration(t.spent_time)}`); + return parts.join("\n"); +} diff --git a/src/tools/time-entries.ts b/src/tools/time-entries.ts new file mode 100644 index 0000000..c0e4c84 --- /dev/null +++ b/src/tools/time-entries.ts @@ -0,0 +1,478 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS, GROUP_BY_VALUES, PAGINATION } from "../constants.js"; +import { coerceBoolean, coerceUuidArray } from "../schemas.js"; +import { formatDuration, formatCurrency, formatDateTime, toUTC, nowUTC } from "../formatting.js"; +import type { TimeEntry, TimeEntryReport, PaginatedResponse } from "../types.js"; + +export function registerTimeEntryTools( + server: McpServer, + api: ApiClient, + orgId: string, + getMemberId: () => string, + timezone?: string +) { + const tz = timezone; + const tzNote = tz + ? ` Times are displayed and accepted in ${tz}. Provide times without a timezone suffix (e.g. 2026-03-05T11:45:00) to use local time, or append Z for UTC.` + : " Times are in UTC."; + async function getActiveTimer(): Promise { + const response = await api.getOrNull<{ data: TimeEntry }>(API_PATHS.activeTimer); + return response?.data ?? null; + } + + // --- Get Active Timer --- + server.registerTool( + "solidtime_get_active_timer", + { + title: "Get Active Timer", + description: "Get the currently running timer, or null if no timer is active.", + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const entry = await getActiveTimer(); + if (!entry) { + return { content: [{ type: "text", text: "No active timer." }] }; + } + return { content: [{ type: "text", text: formatTimeEntry(entry, tz) }] }; + } + ); + + // --- Start Timer --- + server.registerTool( + "solidtime_start_timer", + { + title: "Start Timer", + description: + "Start a new running timer. Checks for an existing active timer first. Use solidtime_stop_timer to stop it later.", + inputSchema: { + project_id: z.string().uuid().optional().describe("Project UUID"), + task_id: z.string().uuid().optional().describe("Task UUID"), + description: z.string().max(5000).optional().describe("What you're working on"), + billable: coerceBoolean.optional().describe("Whether this time is billable"), + tag_ids: coerceUuidArray.optional().describe("Array of tag UUIDs to attach"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const existing = await getActiveTimer(); + if (existing) { + return { + content: [ + { + type: "text", + text: `A timer is already running (started ${formatDateTime(existing.start, tz)}). Stop it first with solidtime_stop_timer.\n\n${formatTimeEntry(existing, tz)}`, + }, + ], + }; + } + + const body: Record = { + member_id: getMemberId(), + start: nowUTC(), + billable: params.billable ?? false, + }; + if (params.project_id) body.project_id = params.project_id; + if (params.task_id) body.task_id = params.task_id; + if (params.description) body.description = params.description; + if (params.tag_ids) body.tags = params.tag_ids; + + const result = await api.post<{ data: TimeEntry }>(API_PATHS.timeEntries(orgId), body); + return { + content: [{ type: "text", text: `Timer started.\n\n${formatTimeEntry(result.data, tz)}` }], + }; + } + ); + + // --- Stop Timer --- + server.registerTool( + "solidtime_stop_timer", + { + title: "Stop Timer", + description: "Stop the currently running timer by setting end time to now.", + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const existing = await getActiveTimer(); + if (!existing) { + return { content: [{ type: "text", text: "No active timer to stop." }] }; + } + + const result = await api.put<{ data: TimeEntry }>(API_PATHS.timeEntry(orgId, existing.id), { + end: nowUTC(), + }); + return { + content: [{ type: "text", text: `Timer stopped.\n\n${formatTimeEntry(result.data, tz)}` }], + }; + } + ); + + // --- List Time Entries --- + server.registerTool( + "solidtime_list_time_entries", + { + title: "List Time Entries", + description: + `List time entries with optional filters. Returns newest first. Use limit/offset for pagination.${tzNote}`, + inputSchema: { + start: z + .string() + .optional() + .describe( + "Filter: only entries starting after this datetime (e.g. 2026-03-01T00:00:00 for local or 2026-03-01T00:00:00Z for UTC)" + ), + end: z.string().optional().describe("Filter: only entries ending before this datetime"), + active: z.enum(["true", "false"]).optional().describe("Filter by active/inactive"), + billable: z.enum(["true", "false"]).optional().describe("Filter by billable status"), + project_ids: coerceUuidArray.optional().describe("Filter by project UUIDs"), + client_ids: coerceUuidArray.optional().describe("Filter by client UUIDs"), + task_ids: coerceUuidArray.optional().describe("Filter by task UUIDs"), + tag_ids: coerceUuidArray.optional().describe("Filter by tag UUIDs"), + limit: z + .number() + .int() + .min(1) + .max(PAGINATION.maxLimit) + .optional() + .describe( + `Number of entries to return (1-${PAGINATION.maxLimit}, default ${PAGINATION.defaultLimit})` + ), + offset: z.number().int().min(0).optional().describe("Offset for pagination (default 0)"), + }, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const query = new URLSearchParams(); + if (params.start) query.set("start", toUTC(params.start, tz)); + if (params.end) query.set("end", toUTC(params.end, tz)); + if (params.active) query.set("active", params.active); + if (params.billable) query.set("billable", params.billable); + if (params.project_ids) + params.project_ids.forEach((id) => query.append("filter[project_ids][]", id)); + if (params.client_ids) + params.client_ids.forEach((id) => query.append("filter[client_ids][]", id)); + if (params.task_ids) params.task_ids.forEach((id) => query.append("filter[task_ids][]", id)); + if (params.tag_ids) params.tag_ids.forEach((id) => query.append("filter[tag_ids][]", id)); + + const limit = params.limit ?? PAGINATION.defaultLimit; + const offset = params.offset ?? 0; + query.set("limit", String(limit)); + query.set("offset", String(offset)); + + const path = `${API_PATHS.timeEntries(orgId)}?${query.toString()}`; + const result = await api.get>(path); + + const entries = result.data ?? []; + if (entries.length === 0) { + return { content: [{ type: "text", text: "No time entries found." }] }; + } + + const hasMore = entries.length === limit; + const lines = entries.map((e, i) => `${i + 1 + offset}. ${formatTimeEntry(e, tz)}`); + if (hasMore) { + lines.push( + `\n--- Showing ${entries.length} entries. Use offset=${offset + limit} to see more. ---` + ); + } + lines.unshift(`Found ${entries.length} time entries:\n`); + + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + // --- Create Time Entry --- + server.registerTool( + "solidtime_create_time_entry", + { + title: "Create Time Entry", + description: + `Create a completed time entry with start and end times. For running timers, use solidtime_start_timer instead.${tzNote}`, + inputSchema: { + start: z.string().describe("Start time (e.g. 2026-03-03T09:00:00 for local or 2026-03-03T09:00:00Z for UTC)"), + end: z.string().describe("End time (e.g. 2026-03-03T10:30:00 for local or 2026-03-03T10:30:00Z for UTC)"), + project_id: z.string().uuid().optional().describe("Project UUID"), + task_id: z.string().uuid().optional().describe("Task UUID"), + description: z.string().max(5000).optional().describe("What was done"), + billable: coerceBoolean + .optional() + .describe("Whether this time is billable (default false)"), + tag_ids: coerceUuidArray.optional().describe("Array of tag UUIDs to attach"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = { + member_id: getMemberId(), + start: toUTC(params.start, tz), + end: toUTC(params.end, tz), + billable: params.billable ?? false, + }; + if (params.project_id) body.project_id = params.project_id; + if (params.task_id) body.task_id = params.task_id; + if (params.description) body.description = params.description; + if (params.tag_ids) body.tags = params.tag_ids; + + const result = await api.post<{ data: TimeEntry }>(API_PATHS.timeEntries(orgId), body); + return { + content: [{ type: "text", text: `Time entry created.\n\n${formatTimeEntry(result.data, tz)}` }], + }; + } + ); + + // --- Update Time Entry --- + server.registerTool( + "solidtime_update_time_entry", + { + title: "Update Time Entry", + description: `Update an existing time entry. Only provided fields will be changed.${tzNote}`, + inputSchema: { + id: z.string().uuid().describe("Time entry UUID to update"), + start: z.string().optional().describe("New start time (no suffix = local, Z suffix = UTC)"), + end: z.string().optional().describe("New end time (no suffix = local, Z suffix = UTC)"), + project_id: z.string().uuid().optional().describe("New project UUID"), + task_id: z.string().uuid().optional().describe("New task UUID"), + description: z.string().max(5000).optional().describe("New description"), + billable: coerceBoolean.optional().describe("New billable status"), + tag_ids: z + .array(z.string().uuid()) + .optional() + .describe("New tag UUIDs (replaces existing tags)"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = {}; + if (params.start !== undefined) body.start = toUTC(params.start, tz); + if (params.end !== undefined) body.end = toUTC(params.end, tz); + if (params.project_id !== undefined) body.project_id = params.project_id; + if (params.task_id !== undefined) body.task_id = params.task_id; + if (params.description !== undefined) body.description = params.description; + if (params.billable !== undefined) body.billable = params.billable; + if (params.tag_ids !== undefined) body.tags = params.tag_ids; + + const result = await api.put<{ data: TimeEntry }>( + API_PATHS.timeEntry(orgId, params.id), + body + ); + return { + content: [{ type: "text", text: `Time entry updated.\n\n${formatTimeEntry(result.data, tz)}` }], + }; + } + ); + + // --- Delete Time Entry --- + server.registerTool( + "solidtime_delete_time_entry", + { + title: "Delete Time Entry", + description: "Permanently delete a time entry. This cannot be undone.", + inputSchema: { + id: z.string().uuid().describe("Time entry UUID to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.delete(API_PATHS.timeEntry(orgId, params.id)); + return { content: [{ type: "text", text: `Time entry ${params.id} deleted.` }] }; + } + ); + + // --- Time Entry Report --- + server.registerTool( + "solidtime_get_time_entry_report", + { + title: "Time Entry Report", + description: + "Get aggregated time entry data grouped by a dimension (day, week, month, year, project, client, task, user, billable, description, tag). Optionally add a sub-grouping.", + inputSchema: { + group_by: z.enum(GROUP_BY_VALUES).describe("Primary grouping dimension"), + sub_group: z.enum(GROUP_BY_VALUES).optional().describe("Secondary grouping dimension"), + start: z + .string() + .optional() + .describe("Filter: only entries starting after this UTC datetime"), + end: z.string().optional().describe("Filter: only entries ending before this UTC datetime"), + project_ids: coerceUuidArray.optional().describe("Filter by project UUIDs"), + client_ids: coerceUuidArray.optional().describe("Filter by client UUIDs"), + billable: z.enum(["true", "false"]).optional().describe("Filter by billable status"), + tag_ids: coerceUuidArray.optional().describe("Filter by tag UUIDs"), + member_ids: coerceUuidArray.optional().describe("Filter by member UUIDs"), + }, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const query = new URLSearchParams(); + query.set("group", params.group_by); + if (params.sub_group) query.set("sub_group", params.sub_group); + if (params.start) query.set("start", toUTC(params.start, tz)); + if (params.end) query.set("end", toUTC(params.end, tz)); + if (params.billable) query.set("billable", params.billable); + if (params.project_ids) + params.project_ids.forEach((id) => query.append("filter[project_ids][]", id)); + if (params.client_ids) + params.client_ids.forEach((id) => query.append("filter[client_ids][]", id)); + if (params.tag_ids) params.tag_ids.forEach((id) => query.append("filter[tag_ids][]", id)); + if (params.member_ids) + params.member_ids.forEach((id) => query.append("filter[member_ids][]", id)); + + const path = `${API_PATHS.timeEntryReport(orgId)}?${query.toString()}`; + const response = await api.get<{ data: TimeEntryReport }>(path); + const result = response.data; + + if (!result.grouped_data || result.grouped_data.length === 0) { + return { content: [{ type: "text", text: "No data for this report." }] }; + } + + const lines = [`Report grouped by: ${result.grouped_type}\n`]; + for (const group of result.grouped_data) { + const duration = formatDuration(group.seconds); + const cost = group.cost > 0 ? ` (${formatCurrency(group.cost)})` : ""; + lines.push(`${group.key}: ${duration}${cost}`); + + if (group.grouped_data) { + for (const sub of group.grouped_data) { + const subDuration = formatDuration(sub.seconds); + const subCost = sub.cost > 0 ? ` (${formatCurrency(sub.cost)})` : ""; + lines.push(` ${sub.key}: ${subDuration}${subCost}`); + } + } + } + + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + // --- Update Multiple Time Entries --- + server.registerTool( + "solidtime_update_multiple_time_entries", + { + title: "Update Multiple Time Entries", + description: + "Update multiple time entries at once. All provided fields will be applied to all specified entries.", + inputSchema: { + ids: z.array(z.string().uuid()).min(1).describe("Array of time entry UUIDs to update"), + project_id: z.string().uuid().optional().describe("New project UUID for all entries"), + task_id: z.string().uuid().optional().describe("New task UUID for all entries"), + description: z.string().max(5000).optional().describe("New description for all entries"), + billable: coerceBoolean.optional().describe("New billable status for all entries"), + tag_ids: coerceUuidArray.optional().describe("New tag UUIDs for all entries"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + const body: Record = { + ids: params.ids, + changes: {}, + }; + const changes: Record = {}; + if (params.project_id !== undefined) changes.project_id = params.project_id; + if (params.task_id !== undefined) changes.task_id = params.task_id; + if (params.description !== undefined) changes.description = params.description; + if (params.billable !== undefined) changes.billable = params.billable; + if (params.tag_ids !== undefined) changes.tags = params.tag_ids; + body.changes = changes; + + await api.patch(API_PATHS.timeEntries(orgId), body); + return { + content: [ + { type: "text", text: `Updated ${params.ids.length} time entries.` }, + ], + }; + } + ); + + // --- Delete Multiple Time Entries --- + server.registerTool( + "solidtime_delete_multiple_time_entries", + { + title: "Delete Multiple Time Entries", + description: + "Permanently delete multiple time entries at once. This cannot be undone.", + inputSchema: { + ids: z + .array(z.string().uuid()) + .min(1) + .describe("Array of time entry UUIDs to delete"), + }, + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (params) => { + await api.deleteWithBody(API_PATHS.timeEntries(orgId), { ids: params.ids }); + return { + content: [ + { type: "text", text: `Deleted ${params.ids.length} time entries.` }, + ], + }; + } + ); +} + +function formatTimeEntry(entry: TimeEntry, timezone?: string): string { + const parts: string[] = [`ID: ${entry.id}`]; + if (entry.description) parts.push(`Description: ${entry.description}`); + parts.push(`Start: ${formatDateTime(entry.start, timezone)}`); + if (entry.end) { + parts.push(`End: ${formatDateTime(entry.end, timezone)}`); + } else { + parts.push("Status: RUNNING"); + } + if (entry.duration !== null && entry.duration !== undefined) { + parts.push(`Duration: ${formatDuration(entry.duration)}`); + } + parts.push(`Billable: ${entry.billable ? "Yes" : "No"}`); + if (entry.project_id) parts.push(`Project ID: ${entry.project_id}`); + if (entry.task_id) parts.push(`Task ID: ${entry.task_id}`); + if (entry.tags && entry.tags.length > 0) parts.push(`Tags: ${entry.tags.join(", ")}`); + return parts.join("\n"); +} diff --git a/src/tools/users.ts b/src/tools/users.ts new file mode 100644 index 0000000..9217b80 --- /dev/null +++ b/src/tools/users.ts @@ -0,0 +1,35 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ApiClient } from "../api-client.js"; +import { API_PATHS } from "../constants.js"; +import type { User } from "../types.js"; + +export function registerUserTools(server: McpServer, api: ApiClient, getMemberId: () => string) { + server.registerTool( + "solidtime_get_current_user", + { + title: "Get Current User", + description: + "Get the current user profile including name, email, timezone, and the resolved member_id for this organization.", + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async () => { + const response = await api.get<{ data: User }>(API_PATHS.me); + const user = response.data; + const memberId = getMemberId(); + const text = [ + `Name: ${user.name}`, + `Email: ${user.email}`, + `Timezone: ${user.timezone}`, + `Week starts: ${user.week_start}`, + `Member ID: ${memberId}`, + ].join("\n"); + + return { content: [{ type: "text", text }] }; + } + ); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6b42e4d --- /dev/null +++ b/src/types.ts @@ -0,0 +1,117 @@ +export interface User { + id: string; + name: string; + email: string; + timezone: string; + week_start: string; + is_placeholder: boolean; +} + +export interface Member { + id: string; + user_id: string; + role: string; + billable_rate: number | null; +} + +export interface TimeEntry { + id: string; + start: string; + end: string | null; + duration: number | null; + description: string | null; + billable: boolean; + tags: string[]; + project_id: string | null; + task_id: string | null; + member_id: string; + user_id: string; +} + +export interface TimeEntryReport { + grouped_type: string; + grouped_data: + | null + | { + key: string; + seconds: number; + cost: number; + grouped_type: string | null; + grouped_data: null | { key: string; seconds: number; cost: number }[]; + }[]; +} + +export interface Project { + id: string; + name: string; + color: string; + is_billable: boolean; + is_archived: boolean; + billable_rate: number | null; + estimated_time: number | null; + spent_time: number; + client_id: string | null; +} + +export interface Client { + id: string; + name: string; + is_archived: boolean; +} + +export interface Tag { + id: string; + name: string; +} + +export interface Task { + id: string; + name: string; + is_done: boolean; + project_id: string; + estimated_time: number | null; + spent_time: number; +} + +export interface PaginatedResponse { + data: T[]; + current_page?: number; + last_page?: number; + per_page?: number; + total?: number; +} + +export interface UserMembership { + id: string; + role: string; + organization: { + id: string; + name: string; + currency: string; + }; +} + +export interface Organization { + id: string; + name: string; + currency: string; + billable_rate: number | null; +} + +export interface ProjectMember { + id: string; + member_id: string; + project_id: string; + billable_rate: number | null; +} + +export interface Invitation { + id: string; + email: string; + role: string; +} + +export interface ApiError { + message: string; + errors?: Record; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2edf508 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}