├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── biome.json ├── lefthook.yaml ├── package-lock.json ├── package.json ├── src ├── constants.ts ├── helpers │ ├── __tests__ │ │ ├── createErrorResponse.test.ts │ │ ├── textFormatter.test.ts │ │ └── validateApiKey.test.ts │ ├── createErrorResponse.ts │ ├── textFormatter.ts │ └── validateApiKey.ts ├── index.ts ├── tools │ ├── __tests__ │ │ ├── createExperience.test.ts │ │ ├── deleteExperience.test.ts │ │ ├── getExperiences.test.ts │ │ ├── getJobDetail.test.ts │ │ ├── getJobSummary.test.ts │ │ ├── getWantToDo.test.ts │ │ ├── searchJobs.test.ts │ │ ├── updateExperience.test.ts │ │ ├── updateJobSummary.test.ts │ │ └── updateWantToDo.test.ts │ ├── createExperience.ts │ ├── deleteExperience.ts │ ├── getExperiences.ts │ ├── getJobDetail.ts │ ├── getJobSummary.ts │ ├── getWantToDo.ts │ ├── searchJobs.ts │ ├── updateExperience.ts │ ├── updateJobSummary.ts │ └── updateWantToDo.ts └── types.ts ├── tsconfig.json └── vitest.config.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | ci: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Set up Node.js 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: '22' 16 | cache: 'npm' 17 | 18 | - name: Install dependencies 19 | run: npm ci 20 | 21 | - name: Run lint 22 | run: npm run lint 23 | 24 | - name: Run tests 25 | run: npm run test:ci -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | 11 | # Log files 12 | logs/ 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea/ 20 | .vscode/ 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | # Operating System Files 28 | .DS_Store 29 | Thumbs.db 30 | .cursor -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.4.0] 2 | 3 | ### Added 4 | - 職務要約・今後のキャリアでやりたいこと取得・更新Toolの追加 #7 5 | 6 | ## [0.3.1] 7 | 8 | ### Fixed 9 | - 改行文字をescapeする問題の対処 #6 10 | 11 | ## [0.3.0] 12 | 13 | ### Added 14 | - 職歴更新Toolの追加 15 | 16 | ## [0.2.0] 17 | 18 | ### Added 19 | - 職歴取得Toolの追加 20 | 21 | ### Fixed 22 | - コンテキスト長のエラーを回避するため画像URLを除外 23 | - コンテキスト長のエラーを回避するためtagsをjoin 24 | 25 | ## [0.1.1] 26 | 27 | ### Changed 28 | - typo修正 29 | - URLから求人応募の旨をdescriptionに追加 30 | 31 | ## [0.1.0] 32 | 33 | ### Added 34 | - 求人検索Toolの追加 35 | - 求人取得Toolの追加 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING GUIDE 2 | 3 | ## 開発ワークフロー 4 | 5 | ### インストール 6 | 7 | ```bash 8 | # リポジトリのクローン 9 | git clone https://github.com/lapras-inc/lapras-mcp-server.git 10 | cd lapras-mcp-server 11 | 12 | npm install 13 | ``` 14 | ### ビルド 15 | 16 | ```bash 17 | npm run build 18 | ``` 19 | 20 | ### Test 21 | 22 | ```bash 23 | npm test 24 | ``` 25 | 26 | ### Lint 27 | 28 | ```bash 29 | npm run lint 30 | npm run lint:fix 31 | ``` 32 | 33 | ### Pull Request 34 | 35 | PRを作成する際は、以下の情報を含めてください: 36 | 37 | 1. 変更内容の概要 38 | 2. 関連する Issue 番号(ある場合) 39 | 3. テスト方法 40 | 41 | ## Release 42 | 43 | ### npm 44 | 45 | ``` 46 | # LAPRASアカウントでログイン 47 | npm login 48 | ``` 49 | 50 | ``` 51 | # バージョン更新 52 | npm verson [major/minor/patch] 53 | ``` 54 | 55 | ``` 56 | # 公開 57 | npm publish 58 | ``` 59 | 60 | ### Docker Hub 61 | 62 | ``` 63 | # LAPRASアカウントでログイン 64 | docker login 65 | ``` 66 | 67 | ``` 68 | # build 69 | docker build -t lapras/mcp-server . 70 | ``` 71 | 72 | ``` 73 | # latestタグでpush 74 | docker tag lapras/mcp-server laprascom/lapras-mcp-server:latest 75 | docker push laprascom/lapras-mcp-server:latest 76 | ``` 77 | 78 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.12-alpine AS builder 2 | 3 | COPY . /app 4 | 5 | WORKDIR /app 6 | 7 | RUN --mount=type=cache,target=/root/.npm npm install 8 | 9 | FROM node:22.12-alpine AS release 10 | 11 | COPY --from=builder /app/dist /app/dist 12 | COPY --from=builder /app/package.json /app/package.json 13 | COPY --from=builder /app/package-lock.json /app/package-lock.json 14 | 15 | ENV NODE_ENV=production 16 | 17 | WORKDIR /app 18 | 19 | RUN npm ci --ignore-scripts --omit-dev 20 | 21 | ENTRYPOINT ["node", "dist/index.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 LAPRAS Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LAPRAS MCP Server 2 | 3 | https://lapras.com 公式のMCP Server 4 | 5 | [![npm version](https://img.shields.io/npm/v/@lapras-inc/lapras-mcp-server.svg)](https://www.npmjs.com/package/@lapras-inc/lapras-mcp-server) 6 | [![npm downloads](https://img.shields.io/npm/dt/@lapras-inc/lapras-mcp-server.svg)](https://www.npmjs.com/package/@lapras-inc/lapras-mcp-server) 7 | [![Docker Pulls](https://img.shields.io/docker/pulls/laprascom/lapras-mcp-server)](https://hub.docker.com/r/laprascom/lapras-mcp-server) 8 | [![CI Status](https://img.shields.io/github/actions/workflow/status/lapras-inc/lapras-mcp-server/ci.yml?branch=main)](https://github.com/lapras-inc/lapras-mcp-server/actions) 9 | 10 | 11 | ## Setup 12 | 13 | MCP Serverの設定([Cursor](https://docs.cursor.com/context/model-context-protocol#configuring-mcp-servers)、[Claude Desktop](https://modelcontextprotocol.io/quickstart/user))を参考に、mcp.jsonまたはclaude_desktop_config.jsonに以下を追記してください。 14 | LAPRAS_API_KEYは職歴関連のツールを使う場合のみ必要です。https://lapras.com/config/api-key から取得できます。 15 | 16 | ### npx 17 | 18 | ``` 19 | { 20 | "mcpServers": { 21 | "lapras": { 22 | "command": "npx", 23 | "args": [ 24 | "-y", 25 | "@lapras-inc/lapras-mcp-server" 26 | ], 27 | "env": { 28 | "LAPRAS_API_KEY": "" 29 | } 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | > [!IMPORTANT] 36 | > Node.jsの環境によってはサーバー接続に失敗する可能性があります。その場合は下記のDocker経由での利用をお試しください。 37 | > また、WSL経由でnpxを実行する場合は、envの環境変数は読み取れません。argsで直接環境変数を指定する必要があります。 38 | > 例: `"args": ["LAPRAS_API_KEY=", "bash", "-c", "/home/bin/npx @lapras-inc/lapras-mcp-server"]` 39 | 40 | 41 | ### Docker 42 | 43 | ``` 44 | { 45 | "mcpServers": { 46 | "lapras": { 47 | "command": "docker", 48 | "args": [ 49 | "run", 50 | "-i", 51 | "--rm", 52 | "-e", 53 | "LAPRAS_API_KEY", 54 | "laprascom/lapras-mcp-server:v0.4.0" 55 | ], 56 | "env": { 57 | "LAPRAS_API_KEY": "" 58 | } 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | ## General notes 65 | > [!WARNING] 66 | > AIがMCPサーバー経由でLAPRASから取得した情報(個人情報等を含む)は、ご利用中のAIモデルに送信され、解釈・処理が行われます。 67 | > 利用されるAIサービスのデータ取扱いポリシー等をご確認の上、個人情報や機密情報の取り扱いにはご留意ください。 68 | 69 | ## Examples 70 | 71 | #### シンプルな求人の検索例 72 | 73 | ``` 74 | フルリモートワーク可能でRustが使えるバックエンドの求人を探してください。年収は800万以上で。 75 | 結果はMarkdownの表にまとめてください。 76 | ``` 77 | 78 | #### 自分にあった求人の検索例 79 | 80 | ``` 81 | <自分のキャリアがわかる画像 or URL を貼り付ける> 82 | これが私の職歴です。私に合いそうな求人を探してください。 83 | ``` 84 | 85 | #### 自分に合った求人の検索例 86 | 87 | ``` 88 | LAPRASで職歴を取得して、私に合いそうな求人を探してください。 89 | ``` 90 | 91 | #### 職歴を更新する例 92 | 93 | ``` 94 | <自分のキャリアがわかる画像 or URL を貼り付ける> 95 | これが私の職歴です。LARPASの職歴を更新してください。 96 | ``` 97 | 98 | #### LAPRASの職歴を改善する例 99 | 100 | ``` 101 | LAPRASの職歴を取得して、ブラッシュアップするための質問をしてください。 102 | 改善後、LAPRASの職歴を更新してください。 103 | ``` 104 | 105 | #### 職務要約を更新する例 106 | 107 | ``` 108 | 私のこれまでの職歴を整理し職務要約を作成して、LAPRASに登録してください。 109 | ``` 110 | 111 | #### 今後のキャリアでやりたいことを更新する例 112 | 113 | ``` 114 | 私の職歴を取得して、今後のキャリアでやりたいことについて質問してください。 115 | 回答をもとに、LAPRASの今後のキャリアでやりたいことを更新してください。 116 | ``` 117 | 118 | https://github.com/user-attachments/assets/9c61470f-f97d-4e6f-97ca-53718c796376 119 | 120 | ## Tools 121 | ### `search_job` 求人検索 122 | - キーワード、ページ番号、最低年収などのパラメータを使用して求人を検索 123 | - 使用例: `search_job` ツールを呼び出し、特定の条件に合致する求人リストを取得 124 | 125 | ### `get_job_detail` 求人詳細取得 126 | - 求人IDを指定して特定の求人の詳細情報を取得 127 | - 使用例: `get_job_detail` ツールを呼び出し、特定の求人の詳細情報を取得 128 | 129 | ### `get_experiences` 職歴一覧取得 130 | - LAPRASに登録されている職歴情報の一覧を取得 131 | - 使用例: `get_experiences` ツールを呼び出し、登録済みの職歴一覧を取得 132 | 133 | ### `create_experience` 職歴新規追加 134 | - LAPRASに新しい職歴情報を追加 135 | - 使用例: `create_experience` ツールを呼び出し、新しい職歴を登録 136 | 137 | ### `update_experience` 職歴更新 138 | - LAPRASに登録されている職歴情報を更新 139 | - 使用例: `update_experience` ツールを呼び出し、既存の職歴を更新 140 | 141 | ### `delete_experience` 職歴削除 142 | - LAPRASに登録されている職歴情報を削除 143 | - 使用例: `delete_experience` ツールを呼び出し、指定した職歴を削除 144 | 145 | ### `get_job_summary` 職務要約取得 146 | - LAPRASに登録されている職務要約を取得 147 | - 使用例: `get_job_summary` ツールを呼び出し、登録済みの職務要約を取得 148 | 149 | ### `update_job_summary` 職務要約更新 150 | - LAPRASに職務要約を登録または更新 151 | - 使用例: `update_job_summary` ツールを呼び出し、職務要約を更新 152 | 153 | ### `get_want_to_do` キャリア志向取得 154 | - LAPRASに登録されている今後のキャリアでやりたいことを取得 155 | - 使用例: `get_want_to_do` ツールを呼び出し、やりたいことを取得 156 | 157 | ### `update_want_to_do` キャリア志向更新 158 | - LAPRASに今後のキャリアでやりたいことを登録または更新 159 | - 使用例: `update_want_to_do` ツールを呼び出し、やりたいことを更新 160 | 161 | > [!NOTE] 162 | > 職歴関連のツールを使用するには、LAPRAS_API_KEYの設定が必要です。 163 | > APIキーは https://lapras.com/config/api-key から取得できます。 164 | 165 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "space", 15 | "indentWidth": 2, 16 | "lineWidth": 100 17 | }, 18 | "organizeImports": { 19 | "enabled": true 20 | }, 21 | "linter": { 22 | "enabled": true, 23 | "rules": { 24 | "recommended": true, 25 | "correctness": { 26 | "noUnusedVariables": "error" 27 | }, 28 | "suspicious": { 29 | "noExplicitAny": "off" 30 | }, 31 | "style": { 32 | "useImportType": "error" 33 | } 34 | } 35 | }, 36 | "javascript": { 37 | "formatter": { 38 | "quoteStyle": "double", 39 | "semicolons": "always" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lefthook.yaml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | biome-lint: 5 | glob: "*.{js,ts,jsx,tsx}" 6 | run: npx biome lint {staged_files} 7 | skip_empty: true 8 | fail_text: "Biomeによるlintチェックが失敗しました。エラーを修正してください。" 9 | biome-format: 10 | glob: "*.{js,ts,jsx,tsx}" 11 | run: npx biome format {staged_files} --write 12 | skip_empty: true 13 | fail_text: "Biomeによるフォーマットが失敗しました。エラーを修正してください。" -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lapras-inc/lapras-mcp-server", 3 | "version": "0.4.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@lapras-inc/lapras-mcp-server", 9 | "version": "0.4.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.8.0", 13 | "node-fetch": "^3.3.2", 14 | "zod": "^3.24.2" 15 | }, 16 | "bin": { 17 | "lapras-mcp-server": "dist/index.js" 18 | }, 19 | "devDependencies": { 20 | "@biomejs/biome": "1.9.4", 21 | "@types/node": "^22.14.0", 22 | "@vitest/ui": "^3.1.1", 23 | "lefthook": "^1.11.6", 24 | "typescript": "^5.8.2", 25 | "vitest": "^3.1.1" 26 | } 27 | }, 28 | "node_modules/@biomejs/biome": { 29 | "version": "1.9.4", 30 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", 31 | "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", 32 | "dev": true, 33 | "hasInstallScript": true, 34 | "license": "MIT OR Apache-2.0", 35 | "bin": { 36 | "biome": "bin/biome" 37 | }, 38 | "engines": { 39 | "node": ">=14.21.3" 40 | }, 41 | "funding": { 42 | "type": "opencollective", 43 | "url": "https://opencollective.com/biome" 44 | }, 45 | "optionalDependencies": { 46 | "@biomejs/cli-darwin-arm64": "1.9.4", 47 | "@biomejs/cli-darwin-x64": "1.9.4", 48 | "@biomejs/cli-linux-arm64": "1.9.4", 49 | "@biomejs/cli-linux-arm64-musl": "1.9.4", 50 | "@biomejs/cli-linux-x64": "1.9.4", 51 | "@biomejs/cli-linux-x64-musl": "1.9.4", 52 | "@biomejs/cli-win32-arm64": "1.9.4", 53 | "@biomejs/cli-win32-x64": "1.9.4" 54 | } 55 | }, 56 | "node_modules/@biomejs/cli-darwin-arm64": { 57 | "version": "1.9.4", 58 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", 59 | "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", 60 | "cpu": [ 61 | "arm64" 62 | ], 63 | "dev": true, 64 | "license": "MIT OR Apache-2.0", 65 | "optional": true, 66 | "os": [ 67 | "darwin" 68 | ], 69 | "engines": { 70 | "node": ">=14.21.3" 71 | } 72 | }, 73 | "node_modules/@biomejs/cli-darwin-x64": { 74 | "version": "1.9.4", 75 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", 76 | "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", 77 | "cpu": [ 78 | "x64" 79 | ], 80 | "dev": true, 81 | "license": "MIT OR Apache-2.0", 82 | "optional": true, 83 | "os": [ 84 | "darwin" 85 | ], 86 | "engines": { 87 | "node": ">=14.21.3" 88 | } 89 | }, 90 | "node_modules/@biomejs/cli-linux-arm64": { 91 | "version": "1.9.4", 92 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", 93 | "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", 94 | "cpu": [ 95 | "arm64" 96 | ], 97 | "dev": true, 98 | "license": "MIT OR Apache-2.0", 99 | "optional": true, 100 | "os": [ 101 | "linux" 102 | ], 103 | "engines": { 104 | "node": ">=14.21.3" 105 | } 106 | }, 107 | "node_modules/@biomejs/cli-linux-arm64-musl": { 108 | "version": "1.9.4", 109 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", 110 | "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "dev": true, 115 | "license": "MIT OR Apache-2.0", 116 | "optional": true, 117 | "os": [ 118 | "linux" 119 | ], 120 | "engines": { 121 | "node": ">=14.21.3" 122 | } 123 | }, 124 | "node_modules/@biomejs/cli-linux-x64": { 125 | "version": "1.9.4", 126 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", 127 | "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", 128 | "cpu": [ 129 | "x64" 130 | ], 131 | "dev": true, 132 | "license": "MIT OR Apache-2.0", 133 | "optional": true, 134 | "os": [ 135 | "linux" 136 | ], 137 | "engines": { 138 | "node": ">=14.21.3" 139 | } 140 | }, 141 | "node_modules/@biomejs/cli-linux-x64-musl": { 142 | "version": "1.9.4", 143 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", 144 | "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", 145 | "cpu": [ 146 | "x64" 147 | ], 148 | "dev": true, 149 | "license": "MIT OR Apache-2.0", 150 | "optional": true, 151 | "os": [ 152 | "linux" 153 | ], 154 | "engines": { 155 | "node": ">=14.21.3" 156 | } 157 | }, 158 | "node_modules/@biomejs/cli-win32-arm64": { 159 | "version": "1.9.4", 160 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", 161 | "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", 162 | "cpu": [ 163 | "arm64" 164 | ], 165 | "dev": true, 166 | "license": "MIT OR Apache-2.0", 167 | "optional": true, 168 | "os": [ 169 | "win32" 170 | ], 171 | "engines": { 172 | "node": ">=14.21.3" 173 | } 174 | }, 175 | "node_modules/@biomejs/cli-win32-x64": { 176 | "version": "1.9.4", 177 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", 178 | "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", 179 | "cpu": [ 180 | "x64" 181 | ], 182 | "dev": true, 183 | "license": "MIT OR Apache-2.0", 184 | "optional": true, 185 | "os": [ 186 | "win32" 187 | ], 188 | "engines": { 189 | "node": ">=14.21.3" 190 | } 191 | }, 192 | "node_modules/@esbuild/aix-ppc64": { 193 | "version": "0.25.2", 194 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", 195 | "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", 196 | "cpu": [ 197 | "ppc64" 198 | ], 199 | "dev": true, 200 | "license": "MIT", 201 | "optional": true, 202 | "os": [ 203 | "aix" 204 | ], 205 | "engines": { 206 | "node": ">=18" 207 | } 208 | }, 209 | "node_modules/@esbuild/android-arm": { 210 | "version": "0.25.2", 211 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", 212 | "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", 213 | "cpu": [ 214 | "arm" 215 | ], 216 | "dev": true, 217 | "license": "MIT", 218 | "optional": true, 219 | "os": [ 220 | "android" 221 | ], 222 | "engines": { 223 | "node": ">=18" 224 | } 225 | }, 226 | "node_modules/@esbuild/android-arm64": { 227 | "version": "0.25.2", 228 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", 229 | "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", 230 | "cpu": [ 231 | "arm64" 232 | ], 233 | "dev": true, 234 | "license": "MIT", 235 | "optional": true, 236 | "os": [ 237 | "android" 238 | ], 239 | "engines": { 240 | "node": ">=18" 241 | } 242 | }, 243 | "node_modules/@esbuild/android-x64": { 244 | "version": "0.25.2", 245 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", 246 | "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", 247 | "cpu": [ 248 | "x64" 249 | ], 250 | "dev": true, 251 | "license": "MIT", 252 | "optional": true, 253 | "os": [ 254 | "android" 255 | ], 256 | "engines": { 257 | "node": ">=18" 258 | } 259 | }, 260 | "node_modules/@esbuild/darwin-arm64": { 261 | "version": "0.25.2", 262 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", 263 | "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", 264 | "cpu": [ 265 | "arm64" 266 | ], 267 | "dev": true, 268 | "license": "MIT", 269 | "optional": true, 270 | "os": [ 271 | "darwin" 272 | ], 273 | "engines": { 274 | "node": ">=18" 275 | } 276 | }, 277 | "node_modules/@esbuild/darwin-x64": { 278 | "version": "0.25.2", 279 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", 280 | "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", 281 | "cpu": [ 282 | "x64" 283 | ], 284 | "dev": true, 285 | "license": "MIT", 286 | "optional": true, 287 | "os": [ 288 | "darwin" 289 | ], 290 | "engines": { 291 | "node": ">=18" 292 | } 293 | }, 294 | "node_modules/@esbuild/freebsd-arm64": { 295 | "version": "0.25.2", 296 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", 297 | "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", 298 | "cpu": [ 299 | "arm64" 300 | ], 301 | "dev": true, 302 | "license": "MIT", 303 | "optional": true, 304 | "os": [ 305 | "freebsd" 306 | ], 307 | "engines": { 308 | "node": ">=18" 309 | } 310 | }, 311 | "node_modules/@esbuild/freebsd-x64": { 312 | "version": "0.25.2", 313 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", 314 | "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", 315 | "cpu": [ 316 | "x64" 317 | ], 318 | "dev": true, 319 | "license": "MIT", 320 | "optional": true, 321 | "os": [ 322 | "freebsd" 323 | ], 324 | "engines": { 325 | "node": ">=18" 326 | } 327 | }, 328 | "node_modules/@esbuild/linux-arm": { 329 | "version": "0.25.2", 330 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", 331 | "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", 332 | "cpu": [ 333 | "arm" 334 | ], 335 | "dev": true, 336 | "license": "MIT", 337 | "optional": true, 338 | "os": [ 339 | "linux" 340 | ], 341 | "engines": { 342 | "node": ">=18" 343 | } 344 | }, 345 | "node_modules/@esbuild/linux-arm64": { 346 | "version": "0.25.2", 347 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", 348 | "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", 349 | "cpu": [ 350 | "arm64" 351 | ], 352 | "dev": true, 353 | "license": "MIT", 354 | "optional": true, 355 | "os": [ 356 | "linux" 357 | ], 358 | "engines": { 359 | "node": ">=18" 360 | } 361 | }, 362 | "node_modules/@esbuild/linux-ia32": { 363 | "version": "0.25.2", 364 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", 365 | "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", 366 | "cpu": [ 367 | "ia32" 368 | ], 369 | "dev": true, 370 | "license": "MIT", 371 | "optional": true, 372 | "os": [ 373 | "linux" 374 | ], 375 | "engines": { 376 | "node": ">=18" 377 | } 378 | }, 379 | "node_modules/@esbuild/linux-loong64": { 380 | "version": "0.25.2", 381 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", 382 | "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", 383 | "cpu": [ 384 | "loong64" 385 | ], 386 | "dev": true, 387 | "license": "MIT", 388 | "optional": true, 389 | "os": [ 390 | "linux" 391 | ], 392 | "engines": { 393 | "node": ">=18" 394 | } 395 | }, 396 | "node_modules/@esbuild/linux-mips64el": { 397 | "version": "0.25.2", 398 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", 399 | "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", 400 | "cpu": [ 401 | "mips64el" 402 | ], 403 | "dev": true, 404 | "license": "MIT", 405 | "optional": true, 406 | "os": [ 407 | "linux" 408 | ], 409 | "engines": { 410 | "node": ">=18" 411 | } 412 | }, 413 | "node_modules/@esbuild/linux-ppc64": { 414 | "version": "0.25.2", 415 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", 416 | "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", 417 | "cpu": [ 418 | "ppc64" 419 | ], 420 | "dev": true, 421 | "license": "MIT", 422 | "optional": true, 423 | "os": [ 424 | "linux" 425 | ], 426 | "engines": { 427 | "node": ">=18" 428 | } 429 | }, 430 | "node_modules/@esbuild/linux-riscv64": { 431 | "version": "0.25.2", 432 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", 433 | "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", 434 | "cpu": [ 435 | "riscv64" 436 | ], 437 | "dev": true, 438 | "license": "MIT", 439 | "optional": true, 440 | "os": [ 441 | "linux" 442 | ], 443 | "engines": { 444 | "node": ">=18" 445 | } 446 | }, 447 | "node_modules/@esbuild/linux-s390x": { 448 | "version": "0.25.2", 449 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", 450 | "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", 451 | "cpu": [ 452 | "s390x" 453 | ], 454 | "dev": true, 455 | "license": "MIT", 456 | "optional": true, 457 | "os": [ 458 | "linux" 459 | ], 460 | "engines": { 461 | "node": ">=18" 462 | } 463 | }, 464 | "node_modules/@esbuild/linux-x64": { 465 | "version": "0.25.2", 466 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", 467 | "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", 468 | "cpu": [ 469 | "x64" 470 | ], 471 | "dev": true, 472 | "license": "MIT", 473 | "optional": true, 474 | "os": [ 475 | "linux" 476 | ], 477 | "engines": { 478 | "node": ">=18" 479 | } 480 | }, 481 | "node_modules/@esbuild/netbsd-arm64": { 482 | "version": "0.25.2", 483 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", 484 | "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", 485 | "cpu": [ 486 | "arm64" 487 | ], 488 | "dev": true, 489 | "license": "MIT", 490 | "optional": true, 491 | "os": [ 492 | "netbsd" 493 | ], 494 | "engines": { 495 | "node": ">=18" 496 | } 497 | }, 498 | "node_modules/@esbuild/netbsd-x64": { 499 | "version": "0.25.2", 500 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", 501 | "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", 502 | "cpu": [ 503 | "x64" 504 | ], 505 | "dev": true, 506 | "license": "MIT", 507 | "optional": true, 508 | "os": [ 509 | "netbsd" 510 | ], 511 | "engines": { 512 | "node": ">=18" 513 | } 514 | }, 515 | "node_modules/@esbuild/openbsd-arm64": { 516 | "version": "0.25.2", 517 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", 518 | "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", 519 | "cpu": [ 520 | "arm64" 521 | ], 522 | "dev": true, 523 | "license": "MIT", 524 | "optional": true, 525 | "os": [ 526 | "openbsd" 527 | ], 528 | "engines": { 529 | "node": ">=18" 530 | } 531 | }, 532 | "node_modules/@esbuild/openbsd-x64": { 533 | "version": "0.25.2", 534 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", 535 | "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", 536 | "cpu": [ 537 | "x64" 538 | ], 539 | "dev": true, 540 | "license": "MIT", 541 | "optional": true, 542 | "os": [ 543 | "openbsd" 544 | ], 545 | "engines": { 546 | "node": ">=18" 547 | } 548 | }, 549 | "node_modules/@esbuild/sunos-x64": { 550 | "version": "0.25.2", 551 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", 552 | "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", 553 | "cpu": [ 554 | "x64" 555 | ], 556 | "dev": true, 557 | "license": "MIT", 558 | "optional": true, 559 | "os": [ 560 | "sunos" 561 | ], 562 | "engines": { 563 | "node": ">=18" 564 | } 565 | }, 566 | "node_modules/@esbuild/win32-arm64": { 567 | "version": "0.25.2", 568 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", 569 | "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", 570 | "cpu": [ 571 | "arm64" 572 | ], 573 | "dev": true, 574 | "license": "MIT", 575 | "optional": true, 576 | "os": [ 577 | "win32" 578 | ], 579 | "engines": { 580 | "node": ">=18" 581 | } 582 | }, 583 | "node_modules/@esbuild/win32-ia32": { 584 | "version": "0.25.2", 585 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", 586 | "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", 587 | "cpu": [ 588 | "ia32" 589 | ], 590 | "dev": true, 591 | "license": "MIT", 592 | "optional": true, 593 | "os": [ 594 | "win32" 595 | ], 596 | "engines": { 597 | "node": ">=18" 598 | } 599 | }, 600 | "node_modules/@esbuild/win32-x64": { 601 | "version": "0.25.2", 602 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", 603 | "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", 604 | "cpu": [ 605 | "x64" 606 | ], 607 | "dev": true, 608 | "license": "MIT", 609 | "optional": true, 610 | "os": [ 611 | "win32" 612 | ], 613 | "engines": { 614 | "node": ">=18" 615 | } 616 | }, 617 | "node_modules/@jridgewell/sourcemap-codec": { 618 | "version": "1.5.0", 619 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 620 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 621 | "dev": true, 622 | "license": "MIT" 623 | }, 624 | "node_modules/@modelcontextprotocol/sdk": { 625 | "version": "1.8.0", 626 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", 627 | "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", 628 | "license": "MIT", 629 | "dependencies": { 630 | "content-type": "^1.0.5", 631 | "cors": "^2.8.5", 632 | "cross-spawn": "^7.0.3", 633 | "eventsource": "^3.0.2", 634 | "express": "^5.0.1", 635 | "express-rate-limit": "^7.5.0", 636 | "pkce-challenge": "^4.1.0", 637 | "raw-body": "^3.0.0", 638 | "zod": "^3.23.8", 639 | "zod-to-json-schema": "^3.24.1" 640 | }, 641 | "engines": { 642 | "node": ">=18" 643 | } 644 | }, 645 | "node_modules/@polka/url": { 646 | "version": "1.0.0-next.28", 647 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", 648 | "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", 649 | "dev": true, 650 | "license": "MIT" 651 | }, 652 | "node_modules/@rollup/rollup-android-arm-eabi": { 653 | "version": "4.39.0", 654 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", 655 | "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", 656 | "cpu": [ 657 | "arm" 658 | ], 659 | "dev": true, 660 | "license": "MIT", 661 | "optional": true, 662 | "os": [ 663 | "android" 664 | ] 665 | }, 666 | "node_modules/@rollup/rollup-android-arm64": { 667 | "version": "4.39.0", 668 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", 669 | "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", 670 | "cpu": [ 671 | "arm64" 672 | ], 673 | "dev": true, 674 | "license": "MIT", 675 | "optional": true, 676 | "os": [ 677 | "android" 678 | ] 679 | }, 680 | "node_modules/@rollup/rollup-darwin-arm64": { 681 | "version": "4.39.0", 682 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", 683 | "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", 684 | "cpu": [ 685 | "arm64" 686 | ], 687 | "dev": true, 688 | "license": "MIT", 689 | "optional": true, 690 | "os": [ 691 | "darwin" 692 | ] 693 | }, 694 | "node_modules/@rollup/rollup-darwin-x64": { 695 | "version": "4.39.0", 696 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", 697 | "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", 698 | "cpu": [ 699 | "x64" 700 | ], 701 | "dev": true, 702 | "license": "MIT", 703 | "optional": true, 704 | "os": [ 705 | "darwin" 706 | ] 707 | }, 708 | "node_modules/@rollup/rollup-freebsd-arm64": { 709 | "version": "4.39.0", 710 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", 711 | "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", 712 | "cpu": [ 713 | "arm64" 714 | ], 715 | "dev": true, 716 | "license": "MIT", 717 | "optional": true, 718 | "os": [ 719 | "freebsd" 720 | ] 721 | }, 722 | "node_modules/@rollup/rollup-freebsd-x64": { 723 | "version": "4.39.0", 724 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", 725 | "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", 726 | "cpu": [ 727 | "x64" 728 | ], 729 | "dev": true, 730 | "license": "MIT", 731 | "optional": true, 732 | "os": [ 733 | "freebsd" 734 | ] 735 | }, 736 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 737 | "version": "4.39.0", 738 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", 739 | "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", 740 | "cpu": [ 741 | "arm" 742 | ], 743 | "dev": true, 744 | "license": "MIT", 745 | "optional": true, 746 | "os": [ 747 | "linux" 748 | ] 749 | }, 750 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 751 | "version": "4.39.0", 752 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", 753 | "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", 754 | "cpu": [ 755 | "arm" 756 | ], 757 | "dev": true, 758 | "license": "MIT", 759 | "optional": true, 760 | "os": [ 761 | "linux" 762 | ] 763 | }, 764 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 765 | "version": "4.39.0", 766 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", 767 | "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", 768 | "cpu": [ 769 | "arm64" 770 | ], 771 | "dev": true, 772 | "license": "MIT", 773 | "optional": true, 774 | "os": [ 775 | "linux" 776 | ] 777 | }, 778 | "node_modules/@rollup/rollup-linux-arm64-musl": { 779 | "version": "4.39.0", 780 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", 781 | "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", 782 | "cpu": [ 783 | "arm64" 784 | ], 785 | "dev": true, 786 | "license": "MIT", 787 | "optional": true, 788 | "os": [ 789 | "linux" 790 | ] 791 | }, 792 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 793 | "version": "4.39.0", 794 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", 795 | "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", 796 | "cpu": [ 797 | "loong64" 798 | ], 799 | "dev": true, 800 | "license": "MIT", 801 | "optional": true, 802 | "os": [ 803 | "linux" 804 | ] 805 | }, 806 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 807 | "version": "4.39.0", 808 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", 809 | "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", 810 | "cpu": [ 811 | "ppc64" 812 | ], 813 | "dev": true, 814 | "license": "MIT", 815 | "optional": true, 816 | "os": [ 817 | "linux" 818 | ] 819 | }, 820 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 821 | "version": "4.39.0", 822 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", 823 | "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", 824 | "cpu": [ 825 | "riscv64" 826 | ], 827 | "dev": true, 828 | "license": "MIT", 829 | "optional": true, 830 | "os": [ 831 | "linux" 832 | ] 833 | }, 834 | "node_modules/@rollup/rollup-linux-riscv64-musl": { 835 | "version": "4.39.0", 836 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", 837 | "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", 838 | "cpu": [ 839 | "riscv64" 840 | ], 841 | "dev": true, 842 | "license": "MIT", 843 | "optional": true, 844 | "os": [ 845 | "linux" 846 | ] 847 | }, 848 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 849 | "version": "4.39.0", 850 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", 851 | "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", 852 | "cpu": [ 853 | "s390x" 854 | ], 855 | "dev": true, 856 | "license": "MIT", 857 | "optional": true, 858 | "os": [ 859 | "linux" 860 | ] 861 | }, 862 | "node_modules/@rollup/rollup-linux-x64-gnu": { 863 | "version": "4.39.0", 864 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", 865 | "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", 866 | "cpu": [ 867 | "x64" 868 | ], 869 | "dev": true, 870 | "license": "MIT", 871 | "optional": true, 872 | "os": [ 873 | "linux" 874 | ] 875 | }, 876 | "node_modules/@rollup/rollup-linux-x64-musl": { 877 | "version": "4.39.0", 878 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", 879 | "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", 880 | "cpu": [ 881 | "x64" 882 | ], 883 | "dev": true, 884 | "license": "MIT", 885 | "optional": true, 886 | "os": [ 887 | "linux" 888 | ] 889 | }, 890 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 891 | "version": "4.39.0", 892 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", 893 | "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", 894 | "cpu": [ 895 | "arm64" 896 | ], 897 | "dev": true, 898 | "license": "MIT", 899 | "optional": true, 900 | "os": [ 901 | "win32" 902 | ] 903 | }, 904 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 905 | "version": "4.39.0", 906 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", 907 | "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", 908 | "cpu": [ 909 | "ia32" 910 | ], 911 | "dev": true, 912 | "license": "MIT", 913 | "optional": true, 914 | "os": [ 915 | "win32" 916 | ] 917 | }, 918 | "node_modules/@rollup/rollup-win32-x64-msvc": { 919 | "version": "4.39.0", 920 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", 921 | "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", 922 | "cpu": [ 923 | "x64" 924 | ], 925 | "dev": true, 926 | "license": "MIT", 927 | "optional": true, 928 | "os": [ 929 | "win32" 930 | ] 931 | }, 932 | "node_modules/@types/estree": { 933 | "version": "1.0.7", 934 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", 935 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", 936 | "dev": true, 937 | "license": "MIT" 938 | }, 939 | "node_modules/@types/node": { 940 | "version": "22.14.0", 941 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", 942 | "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", 943 | "dev": true, 944 | "license": "MIT", 945 | "dependencies": { 946 | "undici-types": "~6.21.0" 947 | } 948 | }, 949 | "node_modules/@vitest/expect": { 950 | "version": "3.1.1", 951 | "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", 952 | "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", 953 | "dev": true, 954 | "license": "MIT", 955 | "dependencies": { 956 | "@vitest/spy": "3.1.1", 957 | "@vitest/utils": "3.1.1", 958 | "chai": "^5.2.0", 959 | "tinyrainbow": "^2.0.0" 960 | }, 961 | "funding": { 962 | "url": "https://opencollective.com/vitest" 963 | } 964 | }, 965 | "node_modules/@vitest/mocker": { 966 | "version": "3.1.1", 967 | "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", 968 | "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", 969 | "dev": true, 970 | "license": "MIT", 971 | "dependencies": { 972 | "@vitest/spy": "3.1.1", 973 | "estree-walker": "^3.0.3", 974 | "magic-string": "^0.30.17" 975 | }, 976 | "funding": { 977 | "url": "https://opencollective.com/vitest" 978 | }, 979 | "peerDependencies": { 980 | "msw": "^2.4.9", 981 | "vite": "^5.0.0 || ^6.0.0" 982 | }, 983 | "peerDependenciesMeta": { 984 | "msw": { 985 | "optional": true 986 | }, 987 | "vite": { 988 | "optional": true 989 | } 990 | } 991 | }, 992 | "node_modules/@vitest/pretty-format": { 993 | "version": "3.1.1", 994 | "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", 995 | "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", 996 | "dev": true, 997 | "license": "MIT", 998 | "dependencies": { 999 | "tinyrainbow": "^2.0.0" 1000 | }, 1001 | "funding": { 1002 | "url": "https://opencollective.com/vitest" 1003 | } 1004 | }, 1005 | "node_modules/@vitest/runner": { 1006 | "version": "3.1.1", 1007 | "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", 1008 | "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", 1009 | "dev": true, 1010 | "license": "MIT", 1011 | "dependencies": { 1012 | "@vitest/utils": "3.1.1", 1013 | "pathe": "^2.0.3" 1014 | }, 1015 | "funding": { 1016 | "url": "https://opencollective.com/vitest" 1017 | } 1018 | }, 1019 | "node_modules/@vitest/snapshot": { 1020 | "version": "3.1.1", 1021 | "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", 1022 | "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", 1023 | "dev": true, 1024 | "license": "MIT", 1025 | "dependencies": { 1026 | "@vitest/pretty-format": "3.1.1", 1027 | "magic-string": "^0.30.17", 1028 | "pathe": "^2.0.3" 1029 | }, 1030 | "funding": { 1031 | "url": "https://opencollective.com/vitest" 1032 | } 1033 | }, 1034 | "node_modules/@vitest/spy": { 1035 | "version": "3.1.1", 1036 | "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", 1037 | "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", 1038 | "dev": true, 1039 | "license": "MIT", 1040 | "dependencies": { 1041 | "tinyspy": "^3.0.2" 1042 | }, 1043 | "funding": { 1044 | "url": "https://opencollective.com/vitest" 1045 | } 1046 | }, 1047 | "node_modules/@vitest/ui": { 1048 | "version": "3.1.1", 1049 | "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.1.1.tgz", 1050 | "integrity": "sha512-2HpiRIYg3dlvAJBV9RtsVswFgUSJK4Sv7QhpxoP0eBGkYwzGIKP34PjaV00AULQi9Ovl6LGyZfsetxDWY5BQdQ==", 1051 | "dev": true, 1052 | "license": "MIT", 1053 | "dependencies": { 1054 | "@vitest/utils": "3.1.1", 1055 | "fflate": "^0.8.2", 1056 | "flatted": "^3.3.3", 1057 | "pathe": "^2.0.3", 1058 | "sirv": "^3.0.1", 1059 | "tinyglobby": "^0.2.12", 1060 | "tinyrainbow": "^2.0.0" 1061 | }, 1062 | "funding": { 1063 | "url": "https://opencollective.com/vitest" 1064 | }, 1065 | "peerDependencies": { 1066 | "vitest": "3.1.1" 1067 | } 1068 | }, 1069 | "node_modules/@vitest/utils": { 1070 | "version": "3.1.1", 1071 | "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", 1072 | "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", 1073 | "dev": true, 1074 | "license": "MIT", 1075 | "dependencies": { 1076 | "@vitest/pretty-format": "3.1.1", 1077 | "loupe": "^3.1.3", 1078 | "tinyrainbow": "^2.0.0" 1079 | }, 1080 | "funding": { 1081 | "url": "https://opencollective.com/vitest" 1082 | } 1083 | }, 1084 | "node_modules/accepts": { 1085 | "version": "2.0.0", 1086 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 1087 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 1088 | "license": "MIT", 1089 | "dependencies": { 1090 | "mime-types": "^3.0.0", 1091 | "negotiator": "^1.0.0" 1092 | }, 1093 | "engines": { 1094 | "node": ">= 0.6" 1095 | } 1096 | }, 1097 | "node_modules/assertion-error": { 1098 | "version": "2.0.1", 1099 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 1100 | "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 1101 | "dev": true, 1102 | "license": "MIT", 1103 | "engines": { 1104 | "node": ">=12" 1105 | } 1106 | }, 1107 | "node_modules/body-parser": { 1108 | "version": "2.2.0", 1109 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 1110 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 1111 | "license": "MIT", 1112 | "dependencies": { 1113 | "bytes": "^3.1.2", 1114 | "content-type": "^1.0.5", 1115 | "debug": "^4.4.0", 1116 | "http-errors": "^2.0.0", 1117 | "iconv-lite": "^0.6.3", 1118 | "on-finished": "^2.4.1", 1119 | "qs": "^6.14.0", 1120 | "raw-body": "^3.0.0", 1121 | "type-is": "^2.0.0" 1122 | }, 1123 | "engines": { 1124 | "node": ">=18" 1125 | } 1126 | }, 1127 | "node_modules/bytes": { 1128 | "version": "3.1.2", 1129 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 1130 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 1131 | "license": "MIT", 1132 | "engines": { 1133 | "node": ">= 0.8" 1134 | } 1135 | }, 1136 | "node_modules/cac": { 1137 | "version": "6.7.14", 1138 | "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 1139 | "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 1140 | "dev": true, 1141 | "license": "MIT", 1142 | "engines": { 1143 | "node": ">=8" 1144 | } 1145 | }, 1146 | "node_modules/call-bind-apply-helpers": { 1147 | "version": "1.0.2", 1148 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 1149 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 1150 | "license": "MIT", 1151 | "dependencies": { 1152 | "es-errors": "^1.3.0", 1153 | "function-bind": "^1.1.2" 1154 | }, 1155 | "engines": { 1156 | "node": ">= 0.4" 1157 | } 1158 | }, 1159 | "node_modules/call-bound": { 1160 | "version": "1.0.4", 1161 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 1162 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 1163 | "license": "MIT", 1164 | "dependencies": { 1165 | "call-bind-apply-helpers": "^1.0.2", 1166 | "get-intrinsic": "^1.3.0" 1167 | }, 1168 | "engines": { 1169 | "node": ">= 0.4" 1170 | }, 1171 | "funding": { 1172 | "url": "https://github.com/sponsors/ljharb" 1173 | } 1174 | }, 1175 | "node_modules/chai": { 1176 | "version": "5.2.0", 1177 | "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", 1178 | "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", 1179 | "dev": true, 1180 | "license": "MIT", 1181 | "dependencies": { 1182 | "assertion-error": "^2.0.1", 1183 | "check-error": "^2.1.1", 1184 | "deep-eql": "^5.0.1", 1185 | "loupe": "^3.1.0", 1186 | "pathval": "^2.0.0" 1187 | }, 1188 | "engines": { 1189 | "node": ">=12" 1190 | } 1191 | }, 1192 | "node_modules/check-error": { 1193 | "version": "2.1.1", 1194 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", 1195 | "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", 1196 | "dev": true, 1197 | "license": "MIT", 1198 | "engines": { 1199 | "node": ">= 16" 1200 | } 1201 | }, 1202 | "node_modules/content-disposition": { 1203 | "version": "1.0.0", 1204 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 1205 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 1206 | "license": "MIT", 1207 | "dependencies": { 1208 | "safe-buffer": "5.2.1" 1209 | }, 1210 | "engines": { 1211 | "node": ">= 0.6" 1212 | } 1213 | }, 1214 | "node_modules/content-type": { 1215 | "version": "1.0.5", 1216 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 1217 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 1218 | "license": "MIT", 1219 | "engines": { 1220 | "node": ">= 0.6" 1221 | } 1222 | }, 1223 | "node_modules/cookie": { 1224 | "version": "0.7.2", 1225 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 1226 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 1227 | "license": "MIT", 1228 | "engines": { 1229 | "node": ">= 0.6" 1230 | } 1231 | }, 1232 | "node_modules/cookie-signature": { 1233 | "version": "1.2.2", 1234 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 1235 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 1236 | "license": "MIT", 1237 | "engines": { 1238 | "node": ">=6.6.0" 1239 | } 1240 | }, 1241 | "node_modules/cors": { 1242 | "version": "2.8.5", 1243 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 1244 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 1245 | "license": "MIT", 1246 | "dependencies": { 1247 | "object-assign": "^4", 1248 | "vary": "^1" 1249 | }, 1250 | "engines": { 1251 | "node": ">= 0.10" 1252 | } 1253 | }, 1254 | "node_modules/cross-spawn": { 1255 | "version": "7.0.6", 1256 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1257 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1258 | "license": "MIT", 1259 | "dependencies": { 1260 | "path-key": "^3.1.0", 1261 | "shebang-command": "^2.0.0", 1262 | "which": "^2.0.1" 1263 | }, 1264 | "engines": { 1265 | "node": ">= 8" 1266 | } 1267 | }, 1268 | "node_modules/data-uri-to-buffer": { 1269 | "version": "4.0.1", 1270 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 1271 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 1272 | "license": "MIT", 1273 | "engines": { 1274 | "node": ">= 12" 1275 | } 1276 | }, 1277 | "node_modules/debug": { 1278 | "version": "4.4.0", 1279 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1280 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1281 | "license": "MIT", 1282 | "dependencies": { 1283 | "ms": "^2.1.3" 1284 | }, 1285 | "engines": { 1286 | "node": ">=6.0" 1287 | }, 1288 | "peerDependenciesMeta": { 1289 | "supports-color": { 1290 | "optional": true 1291 | } 1292 | } 1293 | }, 1294 | "node_modules/deep-eql": { 1295 | "version": "5.0.2", 1296 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 1297 | "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 1298 | "dev": true, 1299 | "license": "MIT", 1300 | "engines": { 1301 | "node": ">=6" 1302 | } 1303 | }, 1304 | "node_modules/depd": { 1305 | "version": "2.0.0", 1306 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 1307 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 1308 | "license": "MIT", 1309 | "engines": { 1310 | "node": ">= 0.8" 1311 | } 1312 | }, 1313 | "node_modules/dunder-proto": { 1314 | "version": "1.0.1", 1315 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 1316 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 1317 | "license": "MIT", 1318 | "dependencies": { 1319 | "call-bind-apply-helpers": "^1.0.1", 1320 | "es-errors": "^1.3.0", 1321 | "gopd": "^1.2.0" 1322 | }, 1323 | "engines": { 1324 | "node": ">= 0.4" 1325 | } 1326 | }, 1327 | "node_modules/ee-first": { 1328 | "version": "1.1.1", 1329 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1330 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 1331 | "license": "MIT" 1332 | }, 1333 | "node_modules/encodeurl": { 1334 | "version": "2.0.0", 1335 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 1336 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 1337 | "license": "MIT", 1338 | "engines": { 1339 | "node": ">= 0.8" 1340 | } 1341 | }, 1342 | "node_modules/es-define-property": { 1343 | "version": "1.0.1", 1344 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 1345 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 1346 | "license": "MIT", 1347 | "engines": { 1348 | "node": ">= 0.4" 1349 | } 1350 | }, 1351 | "node_modules/es-errors": { 1352 | "version": "1.3.0", 1353 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 1354 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 1355 | "license": "MIT", 1356 | "engines": { 1357 | "node": ">= 0.4" 1358 | } 1359 | }, 1360 | "node_modules/es-module-lexer": { 1361 | "version": "1.6.0", 1362 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", 1363 | "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", 1364 | "dev": true, 1365 | "license": "MIT" 1366 | }, 1367 | "node_modules/es-object-atoms": { 1368 | "version": "1.1.1", 1369 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 1370 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 1371 | "license": "MIT", 1372 | "dependencies": { 1373 | "es-errors": "^1.3.0" 1374 | }, 1375 | "engines": { 1376 | "node": ">= 0.4" 1377 | } 1378 | }, 1379 | "node_modules/esbuild": { 1380 | "version": "0.25.2", 1381 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", 1382 | "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", 1383 | "dev": true, 1384 | "hasInstallScript": true, 1385 | "license": "MIT", 1386 | "bin": { 1387 | "esbuild": "bin/esbuild" 1388 | }, 1389 | "engines": { 1390 | "node": ">=18" 1391 | }, 1392 | "optionalDependencies": { 1393 | "@esbuild/aix-ppc64": "0.25.2", 1394 | "@esbuild/android-arm": "0.25.2", 1395 | "@esbuild/android-arm64": "0.25.2", 1396 | "@esbuild/android-x64": "0.25.2", 1397 | "@esbuild/darwin-arm64": "0.25.2", 1398 | "@esbuild/darwin-x64": "0.25.2", 1399 | "@esbuild/freebsd-arm64": "0.25.2", 1400 | "@esbuild/freebsd-x64": "0.25.2", 1401 | "@esbuild/linux-arm": "0.25.2", 1402 | "@esbuild/linux-arm64": "0.25.2", 1403 | "@esbuild/linux-ia32": "0.25.2", 1404 | "@esbuild/linux-loong64": "0.25.2", 1405 | "@esbuild/linux-mips64el": "0.25.2", 1406 | "@esbuild/linux-ppc64": "0.25.2", 1407 | "@esbuild/linux-riscv64": "0.25.2", 1408 | "@esbuild/linux-s390x": "0.25.2", 1409 | "@esbuild/linux-x64": "0.25.2", 1410 | "@esbuild/netbsd-arm64": "0.25.2", 1411 | "@esbuild/netbsd-x64": "0.25.2", 1412 | "@esbuild/openbsd-arm64": "0.25.2", 1413 | "@esbuild/openbsd-x64": "0.25.2", 1414 | "@esbuild/sunos-x64": "0.25.2", 1415 | "@esbuild/win32-arm64": "0.25.2", 1416 | "@esbuild/win32-ia32": "0.25.2", 1417 | "@esbuild/win32-x64": "0.25.2" 1418 | } 1419 | }, 1420 | "node_modules/escape-html": { 1421 | "version": "1.0.3", 1422 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1423 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 1424 | "license": "MIT" 1425 | }, 1426 | "node_modules/estree-walker": { 1427 | "version": "3.0.3", 1428 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 1429 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 1430 | "dev": true, 1431 | "license": "MIT", 1432 | "dependencies": { 1433 | "@types/estree": "^1.0.0" 1434 | } 1435 | }, 1436 | "node_modules/etag": { 1437 | "version": "1.8.1", 1438 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1439 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 1440 | "license": "MIT", 1441 | "engines": { 1442 | "node": ">= 0.6" 1443 | } 1444 | }, 1445 | "node_modules/eventsource": { 1446 | "version": "3.0.6", 1447 | "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", 1448 | "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", 1449 | "license": "MIT", 1450 | "dependencies": { 1451 | "eventsource-parser": "^3.0.1" 1452 | }, 1453 | "engines": { 1454 | "node": ">=18.0.0" 1455 | } 1456 | }, 1457 | "node_modules/eventsource-parser": { 1458 | "version": "3.0.1", 1459 | "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", 1460 | "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", 1461 | "license": "MIT", 1462 | "engines": { 1463 | "node": ">=18.0.0" 1464 | } 1465 | }, 1466 | "node_modules/expect-type": { 1467 | "version": "1.2.1", 1468 | "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", 1469 | "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", 1470 | "dev": true, 1471 | "license": "Apache-2.0", 1472 | "engines": { 1473 | "node": ">=12.0.0" 1474 | } 1475 | }, 1476 | "node_modules/express": { 1477 | "version": "5.1.0", 1478 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 1479 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 1480 | "license": "MIT", 1481 | "dependencies": { 1482 | "accepts": "^2.0.0", 1483 | "body-parser": "^2.2.0", 1484 | "content-disposition": "^1.0.0", 1485 | "content-type": "^1.0.5", 1486 | "cookie": "^0.7.1", 1487 | "cookie-signature": "^1.2.1", 1488 | "debug": "^4.4.0", 1489 | "encodeurl": "^2.0.0", 1490 | "escape-html": "^1.0.3", 1491 | "etag": "^1.8.1", 1492 | "finalhandler": "^2.1.0", 1493 | "fresh": "^2.0.0", 1494 | "http-errors": "^2.0.0", 1495 | "merge-descriptors": "^2.0.0", 1496 | "mime-types": "^3.0.0", 1497 | "on-finished": "^2.4.1", 1498 | "once": "^1.4.0", 1499 | "parseurl": "^1.3.3", 1500 | "proxy-addr": "^2.0.7", 1501 | "qs": "^6.14.0", 1502 | "range-parser": "^1.2.1", 1503 | "router": "^2.2.0", 1504 | "send": "^1.1.0", 1505 | "serve-static": "^2.2.0", 1506 | "statuses": "^2.0.1", 1507 | "type-is": "^2.0.1", 1508 | "vary": "^1.1.2" 1509 | }, 1510 | "engines": { 1511 | "node": ">= 18" 1512 | }, 1513 | "funding": { 1514 | "type": "opencollective", 1515 | "url": "https://opencollective.com/express" 1516 | } 1517 | }, 1518 | "node_modules/express-rate-limit": { 1519 | "version": "7.5.0", 1520 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", 1521 | "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 1522 | "license": "MIT", 1523 | "engines": { 1524 | "node": ">= 16" 1525 | }, 1526 | "funding": { 1527 | "url": "https://github.com/sponsors/express-rate-limit" 1528 | }, 1529 | "peerDependencies": { 1530 | "express": "^4.11 || 5 || ^5.0.0-beta.1" 1531 | } 1532 | }, 1533 | "node_modules/fdir": { 1534 | "version": "6.4.3", 1535 | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", 1536 | "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", 1537 | "dev": true, 1538 | "license": "MIT", 1539 | "peerDependencies": { 1540 | "picomatch": "^3 || ^4" 1541 | }, 1542 | "peerDependenciesMeta": { 1543 | "picomatch": { 1544 | "optional": true 1545 | } 1546 | } 1547 | }, 1548 | "node_modules/fetch-blob": { 1549 | "version": "3.2.0", 1550 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 1551 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 1552 | "funding": [ 1553 | { 1554 | "type": "github", 1555 | "url": "https://github.com/sponsors/jimmywarting" 1556 | }, 1557 | { 1558 | "type": "paypal", 1559 | "url": "https://paypal.me/jimmywarting" 1560 | } 1561 | ], 1562 | "license": "MIT", 1563 | "dependencies": { 1564 | "node-domexception": "^1.0.0", 1565 | "web-streams-polyfill": "^3.0.3" 1566 | }, 1567 | "engines": { 1568 | "node": "^12.20 || >= 14.13" 1569 | } 1570 | }, 1571 | "node_modules/fflate": { 1572 | "version": "0.8.2", 1573 | "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", 1574 | "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", 1575 | "dev": true, 1576 | "license": "MIT" 1577 | }, 1578 | "node_modules/finalhandler": { 1579 | "version": "2.1.0", 1580 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 1581 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 1582 | "license": "MIT", 1583 | "dependencies": { 1584 | "debug": "^4.4.0", 1585 | "encodeurl": "^2.0.0", 1586 | "escape-html": "^1.0.3", 1587 | "on-finished": "^2.4.1", 1588 | "parseurl": "^1.3.3", 1589 | "statuses": "^2.0.1" 1590 | }, 1591 | "engines": { 1592 | "node": ">= 0.8" 1593 | } 1594 | }, 1595 | "node_modules/flatted": { 1596 | "version": "3.3.3", 1597 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 1598 | "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 1599 | "dev": true, 1600 | "license": "ISC" 1601 | }, 1602 | "node_modules/formdata-polyfill": { 1603 | "version": "4.0.10", 1604 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 1605 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 1606 | "license": "MIT", 1607 | "dependencies": { 1608 | "fetch-blob": "^3.1.2" 1609 | }, 1610 | "engines": { 1611 | "node": ">=12.20.0" 1612 | } 1613 | }, 1614 | "node_modules/forwarded": { 1615 | "version": "0.2.0", 1616 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1617 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 1618 | "license": "MIT", 1619 | "engines": { 1620 | "node": ">= 0.6" 1621 | } 1622 | }, 1623 | "node_modules/fresh": { 1624 | "version": "2.0.0", 1625 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 1626 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 1627 | "license": "MIT", 1628 | "engines": { 1629 | "node": ">= 0.8" 1630 | } 1631 | }, 1632 | "node_modules/fsevents": { 1633 | "version": "2.3.3", 1634 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1635 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1636 | "dev": true, 1637 | "hasInstallScript": true, 1638 | "license": "MIT", 1639 | "optional": true, 1640 | "os": [ 1641 | "darwin" 1642 | ], 1643 | "engines": { 1644 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1645 | } 1646 | }, 1647 | "node_modules/function-bind": { 1648 | "version": "1.1.2", 1649 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1650 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1651 | "license": "MIT", 1652 | "funding": { 1653 | "url": "https://github.com/sponsors/ljharb" 1654 | } 1655 | }, 1656 | "node_modules/get-intrinsic": { 1657 | "version": "1.3.0", 1658 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 1659 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1660 | "license": "MIT", 1661 | "dependencies": { 1662 | "call-bind-apply-helpers": "^1.0.2", 1663 | "es-define-property": "^1.0.1", 1664 | "es-errors": "^1.3.0", 1665 | "es-object-atoms": "^1.1.1", 1666 | "function-bind": "^1.1.2", 1667 | "get-proto": "^1.0.1", 1668 | "gopd": "^1.2.0", 1669 | "has-symbols": "^1.1.0", 1670 | "hasown": "^2.0.2", 1671 | "math-intrinsics": "^1.1.0" 1672 | }, 1673 | "engines": { 1674 | "node": ">= 0.4" 1675 | }, 1676 | "funding": { 1677 | "url": "https://github.com/sponsors/ljharb" 1678 | } 1679 | }, 1680 | "node_modules/get-proto": { 1681 | "version": "1.0.1", 1682 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 1683 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1684 | "license": "MIT", 1685 | "dependencies": { 1686 | "dunder-proto": "^1.0.1", 1687 | "es-object-atoms": "^1.0.0" 1688 | }, 1689 | "engines": { 1690 | "node": ">= 0.4" 1691 | } 1692 | }, 1693 | "node_modules/gopd": { 1694 | "version": "1.2.0", 1695 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1696 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1697 | "license": "MIT", 1698 | "engines": { 1699 | "node": ">= 0.4" 1700 | }, 1701 | "funding": { 1702 | "url": "https://github.com/sponsors/ljharb" 1703 | } 1704 | }, 1705 | "node_modules/has-symbols": { 1706 | "version": "1.1.0", 1707 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1708 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1709 | "license": "MIT", 1710 | "engines": { 1711 | "node": ">= 0.4" 1712 | }, 1713 | "funding": { 1714 | "url": "https://github.com/sponsors/ljharb" 1715 | } 1716 | }, 1717 | "node_modules/hasown": { 1718 | "version": "2.0.2", 1719 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1720 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1721 | "license": "MIT", 1722 | "dependencies": { 1723 | "function-bind": "^1.1.2" 1724 | }, 1725 | "engines": { 1726 | "node": ">= 0.4" 1727 | } 1728 | }, 1729 | "node_modules/http-errors": { 1730 | "version": "2.0.0", 1731 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1732 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1733 | "license": "MIT", 1734 | "dependencies": { 1735 | "depd": "2.0.0", 1736 | "inherits": "2.0.4", 1737 | "setprototypeof": "1.2.0", 1738 | "statuses": "2.0.1", 1739 | "toidentifier": "1.0.1" 1740 | }, 1741 | "engines": { 1742 | "node": ">= 0.8" 1743 | } 1744 | }, 1745 | "node_modules/iconv-lite": { 1746 | "version": "0.6.3", 1747 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1748 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1749 | "license": "MIT", 1750 | "dependencies": { 1751 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1752 | }, 1753 | "engines": { 1754 | "node": ">=0.10.0" 1755 | } 1756 | }, 1757 | "node_modules/inherits": { 1758 | "version": "2.0.4", 1759 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1760 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1761 | "license": "ISC" 1762 | }, 1763 | "node_modules/ipaddr.js": { 1764 | "version": "1.9.1", 1765 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1766 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1767 | "license": "MIT", 1768 | "engines": { 1769 | "node": ">= 0.10" 1770 | } 1771 | }, 1772 | "node_modules/is-promise": { 1773 | "version": "4.0.0", 1774 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 1775 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 1776 | "license": "MIT" 1777 | }, 1778 | "node_modules/isexe": { 1779 | "version": "2.0.0", 1780 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1781 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1782 | "license": "ISC" 1783 | }, 1784 | "node_modules/lefthook": { 1785 | "version": "1.11.6", 1786 | "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-1.11.6.tgz", 1787 | "integrity": "sha512-j0VmMM50WlPDassmgvapRum9po29Tv1BXzBNFpzGkk9E91CEG9jKik/OHyH/r/na+q8qNIUUyPL6QQuTN/UhQQ==", 1788 | "dev": true, 1789 | "hasInstallScript": true, 1790 | "license": "MIT", 1791 | "bin": { 1792 | "lefthook": "bin/index.js" 1793 | }, 1794 | "optionalDependencies": { 1795 | "lefthook-darwin-arm64": "1.11.6", 1796 | "lefthook-darwin-x64": "1.11.6", 1797 | "lefthook-freebsd-arm64": "1.11.6", 1798 | "lefthook-freebsd-x64": "1.11.6", 1799 | "lefthook-linux-arm64": "1.11.6", 1800 | "lefthook-linux-x64": "1.11.6", 1801 | "lefthook-openbsd-arm64": "1.11.6", 1802 | "lefthook-openbsd-x64": "1.11.6", 1803 | "lefthook-windows-arm64": "1.11.6", 1804 | "lefthook-windows-x64": "1.11.6" 1805 | } 1806 | }, 1807 | "node_modules/lefthook-darwin-arm64": { 1808 | "version": "1.11.6", 1809 | "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-1.11.6.tgz", 1810 | "integrity": "sha512-gWgdWrKgZgX+bKc6Vs/x7JkO+58lLOpRzpteLx//82D0MKVPlNZwjd4zz4AbIBXtM4Hcj+6gSsOzQ7QDXxjVvQ==", 1811 | "cpu": [ 1812 | "arm64" 1813 | ], 1814 | "dev": true, 1815 | "license": "MIT", 1816 | "optional": true, 1817 | "os": [ 1818 | "darwin" 1819 | ] 1820 | }, 1821 | "node_modules/lefthook-darwin-x64": { 1822 | "version": "1.11.6", 1823 | "resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-1.11.6.tgz", 1824 | "integrity": "sha512-Ia0TjTKuYcSaDTuoCnbWtpPZ2VEoKzgn33OB90VjNaSVs4ooE0PIdpO+w00x1elqIaf1pbrpq6HgeB26Du8KbQ==", 1825 | "cpu": [ 1826 | "x64" 1827 | ], 1828 | "dev": true, 1829 | "license": "MIT", 1830 | "optional": true, 1831 | "os": [ 1832 | "darwin" 1833 | ] 1834 | }, 1835 | "node_modules/lefthook-freebsd-arm64": { 1836 | "version": "1.11.6", 1837 | "resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-1.11.6.tgz", 1838 | "integrity": "sha512-PxIwj+hmjLahyzEmcIfalIBDhgklAQCavwM4sGCgbzDi4/+VQX+4aEs4pQqtd7v3aohmjtO/4n2emzTI8donww==", 1839 | "cpu": [ 1840 | "arm64" 1841 | ], 1842 | "dev": true, 1843 | "license": "MIT", 1844 | "optional": true, 1845 | "os": [ 1846 | "freebsd" 1847 | ] 1848 | }, 1849 | "node_modules/lefthook-freebsd-x64": { 1850 | "version": "1.11.6", 1851 | "resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-1.11.6.tgz", 1852 | "integrity": "sha512-3o1lMKxz1VtWaP/o117wgUn3ZOpefMoSf+8LuiTzI3/PDprIuzgyw2nXKlBZAMDpNPHMNnJeQNts9XLMRmkldg==", 1853 | "cpu": [ 1854 | "x64" 1855 | ], 1856 | "dev": true, 1857 | "license": "MIT", 1858 | "optional": true, 1859 | "os": [ 1860 | "freebsd" 1861 | ] 1862 | }, 1863 | "node_modules/lefthook-linux-arm64": { 1864 | "version": "1.11.6", 1865 | "resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-1.11.6.tgz", 1866 | "integrity": "sha512-nKPFZ5cA9f5tVn0ybDVqcXXlpTHZqo05N4KQRhWTj5Nem+JoD2YzJIlvZhdJhUrldERqj6deDMXChH5T3z4Rrw==", 1867 | "cpu": [ 1868 | "arm64" 1869 | ], 1870 | "dev": true, 1871 | "license": "MIT", 1872 | "optional": true, 1873 | "os": [ 1874 | "linux" 1875 | ] 1876 | }, 1877 | "node_modules/lefthook-linux-x64": { 1878 | "version": "1.11.6", 1879 | "resolved": "https://registry.npmjs.org/lefthook-linux-x64/-/lefthook-linux-x64-1.11.6.tgz", 1880 | "integrity": "sha512-naN8dllLCOEeP+wznLnq+oXrs1dvt/iMLkcl+pOPWLqFccPfDiHzr8V8GslaTa+rSFsAnvjR7SJIOi5C29xedA==", 1881 | "cpu": [ 1882 | "x64" 1883 | ], 1884 | "dev": true, 1885 | "license": "MIT", 1886 | "optional": true, 1887 | "os": [ 1888 | "linux" 1889 | ] 1890 | }, 1891 | "node_modules/lefthook-openbsd-arm64": { 1892 | "version": "1.11.6", 1893 | "resolved": "https://registry.npmjs.org/lefthook-openbsd-arm64/-/lefthook-openbsd-arm64-1.11.6.tgz", 1894 | "integrity": "sha512-dPxhJfYQ667T+U3pz1+O3mTRNHzXH/BvPlXSH+oy8uiSry4AtVNRXkVvXPUcpLlrAy6HuFYodsrpCIlWFeYwiQ==", 1895 | "cpu": [ 1896 | "arm64" 1897 | ], 1898 | "dev": true, 1899 | "license": "MIT", 1900 | "optional": true, 1901 | "os": [ 1902 | "openbsd" 1903 | ] 1904 | }, 1905 | "node_modules/lefthook-openbsd-x64": { 1906 | "version": "1.11.6", 1907 | "resolved": "https://registry.npmjs.org/lefthook-openbsd-x64/-/lefthook-openbsd-x64-1.11.6.tgz", 1908 | "integrity": "sha512-9D26kcSsjiW4D0AuVDdi+0ZqrsOzRWOpMS/kcUbLfrU99yCvma0rMTqKbbDMkVur/znS7qL53oGahXCXDNA+IQ==", 1909 | "cpu": [ 1910 | "x64" 1911 | ], 1912 | "dev": true, 1913 | "license": "MIT", 1914 | "optional": true, 1915 | "os": [ 1916 | "openbsd" 1917 | ] 1918 | }, 1919 | "node_modules/lefthook-windows-arm64": { 1920 | "version": "1.11.6", 1921 | "resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-1.11.6.tgz", 1922 | "integrity": "sha512-xdCenr4+BFnfBEhiXj6GJp02EPmcwTAGa7NYm6hVTfDwGXw24tuLv7lpnGjgK3kovN6EukgLH1FYkeyDOBEMnA==", 1923 | "cpu": [ 1924 | "arm64" 1925 | ], 1926 | "dev": true, 1927 | "license": "MIT", 1928 | "optional": true, 1929 | "os": [ 1930 | "win32" 1931 | ] 1932 | }, 1933 | "node_modules/lefthook-windows-x64": { 1934 | "version": "1.11.6", 1935 | "resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-1.11.6.tgz", 1936 | "integrity": "sha512-Fg2GzLhzeDV/GX8+ydrI0wBOytQWpPkNdngx+a8B/feCDbwjAiFklDG5oV4ytuWrtg1JPEEWLJd6nHefj4wtHA==", 1937 | "cpu": [ 1938 | "x64" 1939 | ], 1940 | "dev": true, 1941 | "license": "MIT", 1942 | "optional": true, 1943 | "os": [ 1944 | "win32" 1945 | ] 1946 | }, 1947 | "node_modules/loupe": { 1948 | "version": "3.1.3", 1949 | "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", 1950 | "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", 1951 | "dev": true, 1952 | "license": "MIT" 1953 | }, 1954 | "node_modules/magic-string": { 1955 | "version": "0.30.17", 1956 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1957 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1958 | "dev": true, 1959 | "license": "MIT", 1960 | "dependencies": { 1961 | "@jridgewell/sourcemap-codec": "^1.5.0" 1962 | } 1963 | }, 1964 | "node_modules/math-intrinsics": { 1965 | "version": "1.1.0", 1966 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1967 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1968 | "license": "MIT", 1969 | "engines": { 1970 | "node": ">= 0.4" 1971 | } 1972 | }, 1973 | "node_modules/media-typer": { 1974 | "version": "1.1.0", 1975 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 1976 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 1977 | "license": "MIT", 1978 | "engines": { 1979 | "node": ">= 0.8" 1980 | } 1981 | }, 1982 | "node_modules/merge-descriptors": { 1983 | "version": "2.0.0", 1984 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 1985 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 1986 | "license": "MIT", 1987 | "engines": { 1988 | "node": ">=18" 1989 | }, 1990 | "funding": { 1991 | "url": "https://github.com/sponsors/sindresorhus" 1992 | } 1993 | }, 1994 | "node_modules/mime-db": { 1995 | "version": "1.54.0", 1996 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 1997 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 1998 | "license": "MIT", 1999 | "engines": { 2000 | "node": ">= 0.6" 2001 | } 2002 | }, 2003 | "node_modules/mime-types": { 2004 | "version": "3.0.1", 2005 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 2006 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 2007 | "license": "MIT", 2008 | "dependencies": { 2009 | "mime-db": "^1.54.0" 2010 | }, 2011 | "engines": { 2012 | "node": ">= 0.6" 2013 | } 2014 | }, 2015 | "node_modules/mrmime": { 2016 | "version": "2.0.1", 2017 | "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", 2018 | "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", 2019 | "dev": true, 2020 | "license": "MIT", 2021 | "engines": { 2022 | "node": ">=10" 2023 | } 2024 | }, 2025 | "node_modules/ms": { 2026 | "version": "2.1.3", 2027 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2028 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2029 | "license": "MIT" 2030 | }, 2031 | "node_modules/nanoid": { 2032 | "version": "3.3.11", 2033 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 2034 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 2035 | "dev": true, 2036 | "funding": [ 2037 | { 2038 | "type": "github", 2039 | "url": "https://github.com/sponsors/ai" 2040 | } 2041 | ], 2042 | "license": "MIT", 2043 | "bin": { 2044 | "nanoid": "bin/nanoid.cjs" 2045 | }, 2046 | "engines": { 2047 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2048 | } 2049 | }, 2050 | "node_modules/negotiator": { 2051 | "version": "1.0.0", 2052 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 2053 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 2054 | "license": "MIT", 2055 | "engines": { 2056 | "node": ">= 0.6" 2057 | } 2058 | }, 2059 | "node_modules/node-domexception": { 2060 | "version": "1.0.0", 2061 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 2062 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 2063 | "funding": [ 2064 | { 2065 | "type": "github", 2066 | "url": "https://github.com/sponsors/jimmywarting" 2067 | }, 2068 | { 2069 | "type": "github", 2070 | "url": "https://paypal.me/jimmywarting" 2071 | } 2072 | ], 2073 | "license": "MIT", 2074 | "engines": { 2075 | "node": ">=10.5.0" 2076 | } 2077 | }, 2078 | "node_modules/node-fetch": { 2079 | "version": "3.3.2", 2080 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 2081 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 2082 | "license": "MIT", 2083 | "dependencies": { 2084 | "data-uri-to-buffer": "^4.0.0", 2085 | "fetch-blob": "^3.1.4", 2086 | "formdata-polyfill": "^4.0.10" 2087 | }, 2088 | "engines": { 2089 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 2090 | }, 2091 | "funding": { 2092 | "type": "opencollective", 2093 | "url": "https://opencollective.com/node-fetch" 2094 | } 2095 | }, 2096 | "node_modules/object-assign": { 2097 | "version": "4.1.1", 2098 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 2099 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 2100 | "license": "MIT", 2101 | "engines": { 2102 | "node": ">=0.10.0" 2103 | } 2104 | }, 2105 | "node_modules/object-inspect": { 2106 | "version": "1.13.4", 2107 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 2108 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 2109 | "license": "MIT", 2110 | "engines": { 2111 | "node": ">= 0.4" 2112 | }, 2113 | "funding": { 2114 | "url": "https://github.com/sponsors/ljharb" 2115 | } 2116 | }, 2117 | "node_modules/on-finished": { 2118 | "version": "2.4.1", 2119 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 2120 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 2121 | "license": "MIT", 2122 | "dependencies": { 2123 | "ee-first": "1.1.1" 2124 | }, 2125 | "engines": { 2126 | "node": ">= 0.8" 2127 | } 2128 | }, 2129 | "node_modules/once": { 2130 | "version": "1.4.0", 2131 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 2132 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 2133 | "license": "ISC", 2134 | "dependencies": { 2135 | "wrappy": "1" 2136 | } 2137 | }, 2138 | "node_modules/parseurl": { 2139 | "version": "1.3.3", 2140 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 2141 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 2142 | "license": "MIT", 2143 | "engines": { 2144 | "node": ">= 0.8" 2145 | } 2146 | }, 2147 | "node_modules/path-key": { 2148 | "version": "3.1.1", 2149 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2150 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2151 | "license": "MIT", 2152 | "engines": { 2153 | "node": ">=8" 2154 | } 2155 | }, 2156 | "node_modules/path-to-regexp": { 2157 | "version": "8.2.0", 2158 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 2159 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 2160 | "license": "MIT", 2161 | "engines": { 2162 | "node": ">=16" 2163 | } 2164 | }, 2165 | "node_modules/pathe": { 2166 | "version": "2.0.3", 2167 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 2168 | "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 2169 | "dev": true, 2170 | "license": "MIT" 2171 | }, 2172 | "node_modules/pathval": { 2173 | "version": "2.0.0", 2174 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", 2175 | "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", 2176 | "dev": true, 2177 | "license": "MIT", 2178 | "engines": { 2179 | "node": ">= 14.16" 2180 | } 2181 | }, 2182 | "node_modules/picocolors": { 2183 | "version": "1.1.1", 2184 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2185 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 2186 | "dev": true, 2187 | "license": "ISC" 2188 | }, 2189 | "node_modules/picomatch": { 2190 | "version": "4.0.2", 2191 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 2192 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 2193 | "dev": true, 2194 | "license": "MIT", 2195 | "engines": { 2196 | "node": ">=12" 2197 | }, 2198 | "funding": { 2199 | "url": "https://github.com/sponsors/jonschlinkert" 2200 | } 2201 | }, 2202 | "node_modules/pkce-challenge": { 2203 | "version": "4.1.0", 2204 | "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", 2205 | "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", 2206 | "license": "MIT", 2207 | "engines": { 2208 | "node": ">=16.20.0" 2209 | } 2210 | }, 2211 | "node_modules/postcss": { 2212 | "version": "8.5.3", 2213 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", 2214 | "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", 2215 | "dev": true, 2216 | "funding": [ 2217 | { 2218 | "type": "opencollective", 2219 | "url": "https://opencollective.com/postcss/" 2220 | }, 2221 | { 2222 | "type": "tidelift", 2223 | "url": "https://tidelift.com/funding/github/npm/postcss" 2224 | }, 2225 | { 2226 | "type": "github", 2227 | "url": "https://github.com/sponsors/ai" 2228 | } 2229 | ], 2230 | "license": "MIT", 2231 | "dependencies": { 2232 | "nanoid": "^3.3.8", 2233 | "picocolors": "^1.1.1", 2234 | "source-map-js": "^1.2.1" 2235 | }, 2236 | "engines": { 2237 | "node": "^10 || ^12 || >=14" 2238 | } 2239 | }, 2240 | "node_modules/proxy-addr": { 2241 | "version": "2.0.7", 2242 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 2243 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 2244 | "license": "MIT", 2245 | "dependencies": { 2246 | "forwarded": "0.2.0", 2247 | "ipaddr.js": "1.9.1" 2248 | }, 2249 | "engines": { 2250 | "node": ">= 0.10" 2251 | } 2252 | }, 2253 | "node_modules/qs": { 2254 | "version": "6.14.0", 2255 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 2256 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 2257 | "license": "BSD-3-Clause", 2258 | "dependencies": { 2259 | "side-channel": "^1.1.0" 2260 | }, 2261 | "engines": { 2262 | "node": ">=0.6" 2263 | }, 2264 | "funding": { 2265 | "url": "https://github.com/sponsors/ljharb" 2266 | } 2267 | }, 2268 | "node_modules/range-parser": { 2269 | "version": "1.2.1", 2270 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 2271 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 2272 | "license": "MIT", 2273 | "engines": { 2274 | "node": ">= 0.6" 2275 | } 2276 | }, 2277 | "node_modules/raw-body": { 2278 | "version": "3.0.0", 2279 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 2280 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 2281 | "license": "MIT", 2282 | "dependencies": { 2283 | "bytes": "3.1.2", 2284 | "http-errors": "2.0.0", 2285 | "iconv-lite": "0.6.3", 2286 | "unpipe": "1.0.0" 2287 | }, 2288 | "engines": { 2289 | "node": ">= 0.8" 2290 | } 2291 | }, 2292 | "node_modules/rollup": { 2293 | "version": "4.39.0", 2294 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", 2295 | "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", 2296 | "dev": true, 2297 | "license": "MIT", 2298 | "dependencies": { 2299 | "@types/estree": "1.0.7" 2300 | }, 2301 | "bin": { 2302 | "rollup": "dist/bin/rollup" 2303 | }, 2304 | "engines": { 2305 | "node": ">=18.0.0", 2306 | "npm": ">=8.0.0" 2307 | }, 2308 | "optionalDependencies": { 2309 | "@rollup/rollup-android-arm-eabi": "4.39.0", 2310 | "@rollup/rollup-android-arm64": "4.39.0", 2311 | "@rollup/rollup-darwin-arm64": "4.39.0", 2312 | "@rollup/rollup-darwin-x64": "4.39.0", 2313 | "@rollup/rollup-freebsd-arm64": "4.39.0", 2314 | "@rollup/rollup-freebsd-x64": "4.39.0", 2315 | "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", 2316 | "@rollup/rollup-linux-arm-musleabihf": "4.39.0", 2317 | "@rollup/rollup-linux-arm64-gnu": "4.39.0", 2318 | "@rollup/rollup-linux-arm64-musl": "4.39.0", 2319 | "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", 2320 | "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", 2321 | "@rollup/rollup-linux-riscv64-gnu": "4.39.0", 2322 | "@rollup/rollup-linux-riscv64-musl": "4.39.0", 2323 | "@rollup/rollup-linux-s390x-gnu": "4.39.0", 2324 | "@rollup/rollup-linux-x64-gnu": "4.39.0", 2325 | "@rollup/rollup-linux-x64-musl": "4.39.0", 2326 | "@rollup/rollup-win32-arm64-msvc": "4.39.0", 2327 | "@rollup/rollup-win32-ia32-msvc": "4.39.0", 2328 | "@rollup/rollup-win32-x64-msvc": "4.39.0", 2329 | "fsevents": "~2.3.2" 2330 | } 2331 | }, 2332 | "node_modules/router": { 2333 | "version": "2.2.0", 2334 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 2335 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 2336 | "license": "MIT", 2337 | "dependencies": { 2338 | "debug": "^4.4.0", 2339 | "depd": "^2.0.0", 2340 | "is-promise": "^4.0.0", 2341 | "parseurl": "^1.3.3", 2342 | "path-to-regexp": "^8.0.0" 2343 | }, 2344 | "engines": { 2345 | "node": ">= 18" 2346 | } 2347 | }, 2348 | "node_modules/safe-buffer": { 2349 | "version": "5.2.1", 2350 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2351 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 2352 | "funding": [ 2353 | { 2354 | "type": "github", 2355 | "url": "https://github.com/sponsors/feross" 2356 | }, 2357 | { 2358 | "type": "patreon", 2359 | "url": "https://www.patreon.com/feross" 2360 | }, 2361 | { 2362 | "type": "consulting", 2363 | "url": "https://feross.org/support" 2364 | } 2365 | ], 2366 | "license": "MIT" 2367 | }, 2368 | "node_modules/safer-buffer": { 2369 | "version": "2.1.2", 2370 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2371 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 2372 | "license": "MIT" 2373 | }, 2374 | "node_modules/send": { 2375 | "version": "1.2.0", 2376 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 2377 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 2378 | "license": "MIT", 2379 | "dependencies": { 2380 | "debug": "^4.3.5", 2381 | "encodeurl": "^2.0.0", 2382 | "escape-html": "^1.0.3", 2383 | "etag": "^1.8.1", 2384 | "fresh": "^2.0.0", 2385 | "http-errors": "^2.0.0", 2386 | "mime-types": "^3.0.1", 2387 | "ms": "^2.1.3", 2388 | "on-finished": "^2.4.1", 2389 | "range-parser": "^1.2.1", 2390 | "statuses": "^2.0.1" 2391 | }, 2392 | "engines": { 2393 | "node": ">= 18" 2394 | } 2395 | }, 2396 | "node_modules/serve-static": { 2397 | "version": "2.2.0", 2398 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 2399 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 2400 | "license": "MIT", 2401 | "dependencies": { 2402 | "encodeurl": "^2.0.0", 2403 | "escape-html": "^1.0.3", 2404 | "parseurl": "^1.3.3", 2405 | "send": "^1.2.0" 2406 | }, 2407 | "engines": { 2408 | "node": ">= 18" 2409 | } 2410 | }, 2411 | "node_modules/setprototypeof": { 2412 | "version": "1.2.0", 2413 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 2414 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 2415 | "license": "ISC" 2416 | }, 2417 | "node_modules/shebang-command": { 2418 | "version": "2.0.0", 2419 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2420 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2421 | "license": "MIT", 2422 | "dependencies": { 2423 | "shebang-regex": "^3.0.0" 2424 | }, 2425 | "engines": { 2426 | "node": ">=8" 2427 | } 2428 | }, 2429 | "node_modules/shebang-regex": { 2430 | "version": "3.0.0", 2431 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2432 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2433 | "license": "MIT", 2434 | "engines": { 2435 | "node": ">=8" 2436 | } 2437 | }, 2438 | "node_modules/side-channel": { 2439 | "version": "1.1.0", 2440 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 2441 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 2442 | "license": "MIT", 2443 | "dependencies": { 2444 | "es-errors": "^1.3.0", 2445 | "object-inspect": "^1.13.3", 2446 | "side-channel-list": "^1.0.0", 2447 | "side-channel-map": "^1.0.1", 2448 | "side-channel-weakmap": "^1.0.2" 2449 | }, 2450 | "engines": { 2451 | "node": ">= 0.4" 2452 | }, 2453 | "funding": { 2454 | "url": "https://github.com/sponsors/ljharb" 2455 | } 2456 | }, 2457 | "node_modules/side-channel-list": { 2458 | "version": "1.0.0", 2459 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 2460 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 2461 | "license": "MIT", 2462 | "dependencies": { 2463 | "es-errors": "^1.3.0", 2464 | "object-inspect": "^1.13.3" 2465 | }, 2466 | "engines": { 2467 | "node": ">= 0.4" 2468 | }, 2469 | "funding": { 2470 | "url": "https://github.com/sponsors/ljharb" 2471 | } 2472 | }, 2473 | "node_modules/side-channel-map": { 2474 | "version": "1.0.1", 2475 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 2476 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 2477 | "license": "MIT", 2478 | "dependencies": { 2479 | "call-bound": "^1.0.2", 2480 | "es-errors": "^1.3.0", 2481 | "get-intrinsic": "^1.2.5", 2482 | "object-inspect": "^1.13.3" 2483 | }, 2484 | "engines": { 2485 | "node": ">= 0.4" 2486 | }, 2487 | "funding": { 2488 | "url": "https://github.com/sponsors/ljharb" 2489 | } 2490 | }, 2491 | "node_modules/side-channel-weakmap": { 2492 | "version": "1.0.2", 2493 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 2494 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 2495 | "license": "MIT", 2496 | "dependencies": { 2497 | "call-bound": "^1.0.2", 2498 | "es-errors": "^1.3.0", 2499 | "get-intrinsic": "^1.2.5", 2500 | "object-inspect": "^1.13.3", 2501 | "side-channel-map": "^1.0.1" 2502 | }, 2503 | "engines": { 2504 | "node": ">= 0.4" 2505 | }, 2506 | "funding": { 2507 | "url": "https://github.com/sponsors/ljharb" 2508 | } 2509 | }, 2510 | "node_modules/siginfo": { 2511 | "version": "2.0.0", 2512 | "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 2513 | "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 2514 | "dev": true, 2515 | "license": "ISC" 2516 | }, 2517 | "node_modules/sirv": { 2518 | "version": "3.0.1", 2519 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", 2520 | "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", 2521 | "dev": true, 2522 | "license": "MIT", 2523 | "dependencies": { 2524 | "@polka/url": "^1.0.0-next.24", 2525 | "mrmime": "^2.0.0", 2526 | "totalist": "^3.0.0" 2527 | }, 2528 | "engines": { 2529 | "node": ">=18" 2530 | } 2531 | }, 2532 | "node_modules/source-map-js": { 2533 | "version": "1.2.1", 2534 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 2535 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 2536 | "dev": true, 2537 | "license": "BSD-3-Clause", 2538 | "engines": { 2539 | "node": ">=0.10.0" 2540 | } 2541 | }, 2542 | "node_modules/stackback": { 2543 | "version": "0.0.2", 2544 | "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 2545 | "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 2546 | "dev": true, 2547 | "license": "MIT" 2548 | }, 2549 | "node_modules/statuses": { 2550 | "version": "2.0.1", 2551 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 2552 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 2553 | "license": "MIT", 2554 | "engines": { 2555 | "node": ">= 0.8" 2556 | } 2557 | }, 2558 | "node_modules/std-env": { 2559 | "version": "3.8.1", 2560 | "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", 2561 | "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", 2562 | "dev": true, 2563 | "license": "MIT" 2564 | }, 2565 | "node_modules/tinybench": { 2566 | "version": "2.9.0", 2567 | "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 2568 | "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 2569 | "dev": true, 2570 | "license": "MIT" 2571 | }, 2572 | "node_modules/tinyexec": { 2573 | "version": "0.3.2", 2574 | "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", 2575 | "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", 2576 | "dev": true, 2577 | "license": "MIT" 2578 | }, 2579 | "node_modules/tinyglobby": { 2580 | "version": "0.2.12", 2581 | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", 2582 | "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", 2583 | "dev": true, 2584 | "license": "MIT", 2585 | "dependencies": { 2586 | "fdir": "^6.4.3", 2587 | "picomatch": "^4.0.2" 2588 | }, 2589 | "engines": { 2590 | "node": ">=12.0.0" 2591 | }, 2592 | "funding": { 2593 | "url": "https://github.com/sponsors/SuperchupuDev" 2594 | } 2595 | }, 2596 | "node_modules/tinypool": { 2597 | "version": "1.0.2", 2598 | "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", 2599 | "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", 2600 | "dev": true, 2601 | "license": "MIT", 2602 | "engines": { 2603 | "node": "^18.0.0 || >=20.0.0" 2604 | } 2605 | }, 2606 | "node_modules/tinyrainbow": { 2607 | "version": "2.0.0", 2608 | "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", 2609 | "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", 2610 | "dev": true, 2611 | "license": "MIT", 2612 | "engines": { 2613 | "node": ">=14.0.0" 2614 | } 2615 | }, 2616 | "node_modules/tinyspy": { 2617 | "version": "3.0.2", 2618 | "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", 2619 | "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", 2620 | "dev": true, 2621 | "license": "MIT", 2622 | "engines": { 2623 | "node": ">=14.0.0" 2624 | } 2625 | }, 2626 | "node_modules/toidentifier": { 2627 | "version": "1.0.1", 2628 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 2629 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 2630 | "license": "MIT", 2631 | "engines": { 2632 | "node": ">=0.6" 2633 | } 2634 | }, 2635 | "node_modules/totalist": { 2636 | "version": "3.0.1", 2637 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", 2638 | "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", 2639 | "dev": true, 2640 | "license": "MIT", 2641 | "engines": { 2642 | "node": ">=6" 2643 | } 2644 | }, 2645 | "node_modules/type-is": { 2646 | "version": "2.0.1", 2647 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 2648 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 2649 | "license": "MIT", 2650 | "dependencies": { 2651 | "content-type": "^1.0.5", 2652 | "media-typer": "^1.1.0", 2653 | "mime-types": "^3.0.0" 2654 | }, 2655 | "engines": { 2656 | "node": ">= 0.6" 2657 | } 2658 | }, 2659 | "node_modules/typescript": { 2660 | "version": "5.8.2", 2661 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 2662 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 2663 | "dev": true, 2664 | "license": "Apache-2.0", 2665 | "bin": { 2666 | "tsc": "bin/tsc", 2667 | "tsserver": "bin/tsserver" 2668 | }, 2669 | "engines": { 2670 | "node": ">=14.17" 2671 | } 2672 | }, 2673 | "node_modules/undici-types": { 2674 | "version": "6.21.0", 2675 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 2676 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 2677 | "dev": true, 2678 | "license": "MIT" 2679 | }, 2680 | "node_modules/unpipe": { 2681 | "version": "1.0.0", 2682 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2683 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 2684 | "license": "MIT", 2685 | "engines": { 2686 | "node": ">= 0.8" 2687 | } 2688 | }, 2689 | "node_modules/vary": { 2690 | "version": "1.1.2", 2691 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2692 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 2693 | "license": "MIT", 2694 | "engines": { 2695 | "node": ">= 0.8" 2696 | } 2697 | }, 2698 | "node_modules/vite": { 2699 | "version": "6.2.5", 2700 | "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", 2701 | "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", 2702 | "dev": true, 2703 | "license": "MIT", 2704 | "dependencies": { 2705 | "esbuild": "^0.25.0", 2706 | "postcss": "^8.5.3", 2707 | "rollup": "^4.30.1" 2708 | }, 2709 | "bin": { 2710 | "vite": "bin/vite.js" 2711 | }, 2712 | "engines": { 2713 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 2714 | }, 2715 | "funding": { 2716 | "url": "https://github.com/vitejs/vite?sponsor=1" 2717 | }, 2718 | "optionalDependencies": { 2719 | "fsevents": "~2.3.3" 2720 | }, 2721 | "peerDependencies": { 2722 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 2723 | "jiti": ">=1.21.0", 2724 | "less": "*", 2725 | "lightningcss": "^1.21.0", 2726 | "sass": "*", 2727 | "sass-embedded": "*", 2728 | "stylus": "*", 2729 | "sugarss": "*", 2730 | "terser": "^5.16.0", 2731 | "tsx": "^4.8.1", 2732 | "yaml": "^2.4.2" 2733 | }, 2734 | "peerDependenciesMeta": { 2735 | "@types/node": { 2736 | "optional": true 2737 | }, 2738 | "jiti": { 2739 | "optional": true 2740 | }, 2741 | "less": { 2742 | "optional": true 2743 | }, 2744 | "lightningcss": { 2745 | "optional": true 2746 | }, 2747 | "sass": { 2748 | "optional": true 2749 | }, 2750 | "sass-embedded": { 2751 | "optional": true 2752 | }, 2753 | "stylus": { 2754 | "optional": true 2755 | }, 2756 | "sugarss": { 2757 | "optional": true 2758 | }, 2759 | "terser": { 2760 | "optional": true 2761 | }, 2762 | "tsx": { 2763 | "optional": true 2764 | }, 2765 | "yaml": { 2766 | "optional": true 2767 | } 2768 | } 2769 | }, 2770 | "node_modules/vite-node": { 2771 | "version": "3.1.1", 2772 | "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", 2773 | "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", 2774 | "dev": true, 2775 | "license": "MIT", 2776 | "dependencies": { 2777 | "cac": "^6.7.14", 2778 | "debug": "^4.4.0", 2779 | "es-module-lexer": "^1.6.0", 2780 | "pathe": "^2.0.3", 2781 | "vite": "^5.0.0 || ^6.0.0" 2782 | }, 2783 | "bin": { 2784 | "vite-node": "vite-node.mjs" 2785 | }, 2786 | "engines": { 2787 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 2788 | }, 2789 | "funding": { 2790 | "url": "https://opencollective.com/vitest" 2791 | } 2792 | }, 2793 | "node_modules/vitest": { 2794 | "version": "3.1.1", 2795 | "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", 2796 | "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", 2797 | "dev": true, 2798 | "license": "MIT", 2799 | "dependencies": { 2800 | "@vitest/expect": "3.1.1", 2801 | "@vitest/mocker": "3.1.1", 2802 | "@vitest/pretty-format": "^3.1.1", 2803 | "@vitest/runner": "3.1.1", 2804 | "@vitest/snapshot": "3.1.1", 2805 | "@vitest/spy": "3.1.1", 2806 | "@vitest/utils": "3.1.1", 2807 | "chai": "^5.2.0", 2808 | "debug": "^4.4.0", 2809 | "expect-type": "^1.2.0", 2810 | "magic-string": "^0.30.17", 2811 | "pathe": "^2.0.3", 2812 | "std-env": "^3.8.1", 2813 | "tinybench": "^2.9.0", 2814 | "tinyexec": "^0.3.2", 2815 | "tinypool": "^1.0.2", 2816 | "tinyrainbow": "^2.0.0", 2817 | "vite": "^5.0.0 || ^6.0.0", 2818 | "vite-node": "3.1.1", 2819 | "why-is-node-running": "^2.3.0" 2820 | }, 2821 | "bin": { 2822 | "vitest": "vitest.mjs" 2823 | }, 2824 | "engines": { 2825 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 2826 | }, 2827 | "funding": { 2828 | "url": "https://opencollective.com/vitest" 2829 | }, 2830 | "peerDependencies": { 2831 | "@edge-runtime/vm": "*", 2832 | "@types/debug": "^4.1.12", 2833 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 2834 | "@vitest/browser": "3.1.1", 2835 | "@vitest/ui": "3.1.1", 2836 | "happy-dom": "*", 2837 | "jsdom": "*" 2838 | }, 2839 | "peerDependenciesMeta": { 2840 | "@edge-runtime/vm": { 2841 | "optional": true 2842 | }, 2843 | "@types/debug": { 2844 | "optional": true 2845 | }, 2846 | "@types/node": { 2847 | "optional": true 2848 | }, 2849 | "@vitest/browser": { 2850 | "optional": true 2851 | }, 2852 | "@vitest/ui": { 2853 | "optional": true 2854 | }, 2855 | "happy-dom": { 2856 | "optional": true 2857 | }, 2858 | "jsdom": { 2859 | "optional": true 2860 | } 2861 | } 2862 | }, 2863 | "node_modules/web-streams-polyfill": { 2864 | "version": "3.3.3", 2865 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 2866 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 2867 | "license": "MIT", 2868 | "engines": { 2869 | "node": ">= 8" 2870 | } 2871 | }, 2872 | "node_modules/which": { 2873 | "version": "2.0.2", 2874 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2875 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2876 | "license": "ISC", 2877 | "dependencies": { 2878 | "isexe": "^2.0.0" 2879 | }, 2880 | "bin": { 2881 | "node-which": "bin/node-which" 2882 | }, 2883 | "engines": { 2884 | "node": ">= 8" 2885 | } 2886 | }, 2887 | "node_modules/why-is-node-running": { 2888 | "version": "2.3.0", 2889 | "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 2890 | "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 2891 | "dev": true, 2892 | "license": "MIT", 2893 | "dependencies": { 2894 | "siginfo": "^2.0.0", 2895 | "stackback": "0.0.2" 2896 | }, 2897 | "bin": { 2898 | "why-is-node-running": "cli.js" 2899 | }, 2900 | "engines": { 2901 | "node": ">=8" 2902 | } 2903 | }, 2904 | "node_modules/wrappy": { 2905 | "version": "1.0.2", 2906 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2907 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2908 | "license": "ISC" 2909 | }, 2910 | "node_modules/zod": { 2911 | "version": "3.24.2", 2912 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 2913 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 2914 | "license": "MIT", 2915 | "funding": { 2916 | "url": "https://github.com/sponsors/colinhacks" 2917 | } 2918 | }, 2919 | "node_modules/zod-to-json-schema": { 2920 | "version": "3.24.5", 2921 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", 2922 | "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 2923 | "license": "ISC", 2924 | "peerDependencies": { 2925 | "zod": "^3.24.1" 2926 | } 2927 | } 2928 | } 2929 | } 2930 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lapras-inc/lapras-mcp-server", 3 | "version": "0.4.0", 4 | "type": "module", 5 | "homepage": "https://github.com/lapras-inc/lapras-mcp-server", 6 | "bugs": "https://github.com/lapras-inc/lapras-mcp-server/issues", 7 | "bin": { 8 | "lapras-mcp-server": "dist/index.js" 9 | }, 10 | "main": "dist/index.js", 11 | "exports": { 12 | ".": "./dist/index.js" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "test": "vitest", 19 | "test:ci": "vitest run", 20 | "test:coverage": "vitest run --coverage", 21 | "build": "rm -rf dist && tsc && chmod +x dist/index.js", 22 | "prepare": "npm run build", 23 | "dev": "tsc --watch", 24 | "lint": "biome check ./src", 25 | "lint:fix": "biome check --write ./src" 26 | }, 27 | "keywords": [ 28 | "mcp", 29 | "lapras", 30 | "job_description" 31 | ], 32 | "author": "lapras-inc", 33 | "license": "MIT", 34 | "description": "MCP server for lapras.com", 35 | "dependencies": { 36 | "@modelcontextprotocol/sdk": "^1.8.0", 37 | "node-fetch": "^3.3.2", 38 | "zod": "^3.24.2" 39 | }, 40 | "devDependencies": { 41 | "@biomejs/biome": "1.9.4", 42 | "@types/node": "^22.14.0", 43 | "@vitest/ui": "^3.1.1", 44 | "lefthook": "^1.11.6", 45 | "typescript": "^5.8.2", 46 | "vitest": "^3.1.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const BASE_URL = "https://lapras.com/api/mcp"; 2 | -------------------------------------------------------------------------------- /src/helpers/__tests__/createErrorResponse.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { createErrorResponse } from "../createErrorResponse.js"; 3 | 4 | describe("createErrorResponse", () => { 5 | it("Errorインスタンスを渡した場合、エラーメッセージを含むレスポンスを返す", () => { 6 | const error = new Error("テストエラー"); 7 | const errorMessage = "エラーが発生しました"; 8 | const response = createErrorResponse(error, errorMessage); 9 | 10 | expect(response.isError).toBe(true); 11 | expect(response.content).toHaveLength(1); 12 | expect(response.content[0].type).toBe("text"); 13 | expect(JSON.parse(response.content[0].text)).toEqual({ 14 | error: errorMessage, 15 | details: error.message, 16 | }); 17 | }); 18 | 19 | it("Error以外のオブジェクトを渡した場合、文字列に変換したdetailsを含むレスポンスを返す", () => { 20 | const error = { code: 404, message: "Not Found" }; 21 | const errorMessage = "リソースが見つかりません"; 22 | const response = createErrorResponse(error, errorMessage); 23 | 24 | expect(response.isError).toBe(true); 25 | expect(response.content).toHaveLength(1); 26 | expect(response.content[0].type).toBe("text"); 27 | expect(JSON.parse(response.content[0].text)).toEqual({ 28 | error: errorMessage, 29 | details: String(error), 30 | }); 31 | }); 32 | 33 | it("プリミティブ値を渡した場合、文字列に変換したdetailsを含むレスポンスを返す", () => { 34 | const error = 404; 35 | const errorMessage = "不正なステータスコード"; 36 | const response = createErrorResponse(error, errorMessage); 37 | 38 | expect(response.isError).toBe(true); 39 | expect(response.content).toHaveLength(1); 40 | expect(response.content[0].type).toBe("text"); 41 | expect(JSON.parse(response.content[0].text)).toEqual({ 42 | error: errorMessage, 43 | details: String(error), 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/helpers/__tests__/textFormatter.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { unescapeText } from "../textFormatter.js"; 3 | 4 | describe("unescapeText", () => { 5 | it("エスケープされた改行文字を実際の改行に変換する", () => { 6 | const input = "Line 1\\nLine 2\\nLine 3"; 7 | const expected = "Line 1\nLine 2\nLine 3"; 8 | expect(unescapeText(input)).toBe(expected); 9 | }); 10 | 11 | it("通常の文字列はそのまま返す", () => { 12 | const input = "Normal text without escapes"; 13 | expect(unescapeText(input)).toBe(input); 14 | }); 15 | 16 | it("undefinedの場合は空文字列を返す", () => { 17 | expect(unescapeText(undefined)).toBe(""); 18 | }); 19 | 20 | it("nullの場合は空文字列を返す", () => { 21 | expect(unescapeText(null)).toBe(""); 22 | }); 23 | 24 | it("複数の連続した改行文字を正しく変換する", () => { 25 | const input = "Line 1\\n\\nLine 3"; 26 | const expected = "Line 1\n\nLine 3"; 27 | expect(unescapeText(input)).toBe(expected); 28 | }); 29 | 30 | it("タブ文字を正しく変換する", () => { 31 | const input = "Column1\\tColumn2\\tColumn3"; 32 | const expected = "Column1\tColumn2\tColumn3"; 33 | expect(unescapeText(input)).toBe(expected); 34 | }); 35 | 36 | it("改行とタブを組み合わせて正しく変換する", () => { 37 | const input = "Title\\nColumn1\\tColumn2\\tColumn3\\nData1\\tData2"; 38 | const expected = "Title\nColumn1\tColumn2\tColumn3\nData1\tData2"; 39 | expect(unescapeText(input)).toBe(expected); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/helpers/__tests__/validateApiKey.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, describe, expect, it } from "vitest"; 2 | import { validateApiKey } from "../validateApiKey.js"; 3 | 4 | describe("validateApiKey", () => { 5 | const originalEnv = process.env; 6 | 7 | beforeEach(() => { 8 | process.env = { ...originalEnv }; 9 | }); 10 | 11 | afterEach(() => { 12 | process.env = originalEnv; 13 | }); 14 | 15 | it("LAPRAS_API_KEYが設定されていない場合、エラーレスポンスを返す", () => { 16 | process.env.LAPRAS_API_KEY = undefined; 17 | const result = validateApiKey(); 18 | expect(result.isInvalid).toBe(true); 19 | if (result.isInvalid) { 20 | expect(result.errorResopnse.isError).toBe(true); 21 | expect(result.errorResopnse.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 22 | } 23 | }); 24 | 25 | it("LAPRAS_API_KEYが設定されている場合、APIキーを返す", () => { 26 | const testApiKey = "test-api-key"; 27 | process.env.LAPRAS_API_KEY = testApiKey; 28 | const result = validateApiKey(); 29 | expect(result.isInvalid).toBe(false); 30 | if (!result.isInvalid) { 31 | expect(result.apiKey).toBe(testApiKey); 32 | } 33 | }); 34 | 35 | it("LAPRAS_API_KEYが空白文字を含む場合、トリムされたAPIキーを返す", () => { 36 | const testApiKey = " test-api-key "; 37 | process.env.LAPRAS_API_KEY = testApiKey; 38 | const result = validateApiKey(); 39 | expect(result.isInvalid).toBe(false); 40 | if (!result.isInvalid) { 41 | expect(result.apiKey).toBe(testApiKey.trim()); 42 | } 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/helpers/createErrorResponse.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | 3 | /** 4 | * エラーレスポンスを作成するユーティリティ関数 5 | * @param error エラー情報 6 | * @param errorMessage エラーメッセージ 7 | */ 8 | export function createErrorResponse( 9 | error: unknown, 10 | errorMessage: string, 11 | ): { 12 | content: TextContent[]; 13 | isError: boolean; 14 | } { 15 | const details = error instanceof Error ? error.message : String(error); 16 | 17 | return { 18 | content: [ 19 | { 20 | type: "text", 21 | text: JSON.stringify({ 22 | error: errorMessage, 23 | details, 24 | }), 25 | }, 26 | ], 27 | isError: true, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/helpers/textFormatter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * エスケープされた文字列を実際の文字に変換します 3 | * @see https://github.com/lapras-inc/lapras-mcp-server/issues/5 4 | */ 5 | export function unescapeText(text: string | undefined | null): string { 6 | if (!text) return ""; 7 | 8 | // エスケープされた改行文字とタブ文字を実際の文字に変換 9 | return text 10 | .replace(/\\n/g, "\n") // 改行 11 | .replace(/\\t/g, "\t"); // タブ 12 | } 13 | -------------------------------------------------------------------------------- /src/helpers/validateApiKey.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import { createErrorResponse } from "./createErrorResponse.js"; 3 | 4 | /** 5 | * APIキーの検証を行う関数 6 | * @returns 有効なAPIキーがある場合はAPIキーを、ない場合はエラーレスポンスを返す 7 | */ 8 | export function validateApiKey(): 9 | | { apiKey: string; isInvalid: false } 10 | | { 11 | errorResopnse: ReturnType; 12 | isInvalid: true; 13 | } { 14 | const lapras_api_key = process.env.LAPRAS_API_KEY?.trim(); 15 | if (!lapras_api_key) { 16 | return { 17 | errorResopnse: createErrorResponse( 18 | new Error("LAPRAS_API_KEY is required"), 19 | "LAPRAS_API_KEYの設定が必要です。https://lapras.com/config/api-key から取得してmcp.jsonに設定してください。", 20 | ), 21 | isInvalid: true, 22 | }; 23 | } 24 | return { apiKey: lapras_api_key, isInvalid: false }; 25 | } 26 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { CreateExperienceTool } from "./tools/createExperience.js"; 6 | import { DeleteExperienceTool } from "./tools/deleteExperience.js"; 7 | import { GetExpriencesTool } from "./tools/getExperiences.js"; 8 | import { GetJobDetailTool } from "./tools/getJobDetail.js"; 9 | import { GetJobSummaryTool } from "./tools/getJobSummary.js"; 10 | import { GetWantToDoTool } from "./tools/getWantToDo.js"; 11 | import { SearchJobsTool } from "./tools/searchJobs.js"; 12 | import { UpdateExperienceTool } from "./tools/updateExperience.js"; 13 | import { UpdateJobSummaryTool } from "./tools/updateJobSummary.js"; 14 | import { UpdateWantToDoTool } from "./tools/updateWantToDo.js"; 15 | import type { IMCPTool } from "./types.js"; 16 | 17 | export const ALL_TOOLS: IMCPTool[] = [ 18 | new SearchJobsTool(), // 求人検索ツール 19 | new GetJobDetailTool(), // 求人詳細取得ツール 20 | new GetExpriencesTool(), // 職歴取得ツール 21 | new CreateExperienceTool(), // 職歴新規追加ツール 22 | new UpdateExperienceTool(), // 職歴更新ツール 23 | new DeleteExperienceTool(), // 職歴削除ツール 24 | new GetJobSummaryTool(), // 職務要約取得ツール 25 | new UpdateJobSummaryTool(), // 職務要約更新ツール 26 | new GetWantToDoTool(), // 今後のキャリアでやりたいこと取得ツール 27 | new UpdateWantToDoTool(), // 今後のキャリアでやりたいこと更新ツール 28 | ]; 29 | 30 | const server = new McpServer( 31 | { 32 | name: "LAPRAS", 33 | version: "0.1.0", 34 | }, 35 | { 36 | capabilities: { 37 | tools: {}, 38 | }, 39 | }, 40 | ); 41 | 42 | for (const tool of ALL_TOOLS) { 43 | server.tool(tool.name, tool.description, tool.parameters, tool.execute.bind(tool)); 44 | } 45 | 46 | const transport = new StdioServerTransport(); 47 | await server.connect(transport); 48 | -------------------------------------------------------------------------------- /src/tools/__tests__/createExperience.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { CreateExperienceTool } from "../createExperience.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("CreateExperienceTool", () => { 12 | let tool: CreateExperienceTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new CreateExperienceTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("職歴を正常に作成できる", async () => { 29 | const mockData = { 30 | id: "123", 31 | organization_name: "Test Company", 32 | positions: [{ id: 1 }], 33 | start_year: 2020, 34 | start_month: 1, 35 | end_year: 2023, 36 | end_month: 12, 37 | }; 38 | 39 | const createParams = { 40 | organization_name: "Test Company", 41 | positions: [{ id: 1 }], 42 | is_client_work: false, 43 | start_year: 2020, 44 | start_month: 1, 45 | end_year: 2023, 46 | end_month: 12, 47 | position_name: "", 48 | client_company_name: "", 49 | description: "", 50 | }; 51 | 52 | mockFetch.mockResolvedValueOnce({ 53 | ok: true, 54 | json: () => Promise.resolve(mockData), 55 | }); 56 | 57 | const result = await tool.execute(createParams); 58 | 59 | expect(result.isError).toBeUndefined(); 60 | expect(result.content[0].type).toBe("text"); 61 | expect(JSON.parse(result.content[0].text)).toEqual(mockData); 62 | 63 | expect(mockFetch).toHaveBeenCalledWith( 64 | expect.any(URL), 65 | expect.objectContaining({ 66 | method: "POST", 67 | headers: { 68 | accept: "application/json, text/plain, */*", 69 | Authorization: "Bearer test-api-key", 70 | }, 71 | body: JSON.stringify(createParams), 72 | }), 73 | ); 74 | }); 75 | 76 | it("クライアントワークの場合、client_company_nameが必須", async () => { 77 | const createParams = { 78 | organization_name: "Test Company", 79 | positions: [{ id: 1 }], 80 | is_client_work: true, 81 | start_year: 2020, 82 | start_month: 1, 83 | end_year: 2023, 84 | end_month: 12, 85 | position_name: "", 86 | client_company_name: "Client Company", 87 | description: "", 88 | }; 89 | 90 | mockFetch.mockResolvedValueOnce({ 91 | ok: true, 92 | json: () => Promise.resolve({ ...createParams, id: "123" }), 93 | }); 94 | 95 | const result = await tool.execute(createParams); 96 | 97 | expect(result.isError).toBeUndefined(); 98 | expect(mockFetch).toHaveBeenCalledWith( 99 | expect.any(URL), 100 | expect.objectContaining({ 101 | method: "POST", 102 | body: JSON.stringify(createParams), 103 | }), 104 | ); 105 | }); 106 | 107 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 108 | process.env.LAPRAS_API_KEY = undefined; 109 | 110 | const result = await tool.execute({ 111 | organization_name: "Test Company", 112 | positions: [{ id: 1 }], 113 | is_client_work: false, 114 | start_year: 2020, 115 | start_month: 1, 116 | end_year: 2023, 117 | end_month: 12, 118 | position_name: "", 119 | client_company_name: "", 120 | description: "", 121 | }); 122 | 123 | expect(result.isError).toBe(true); 124 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 125 | }); 126 | 127 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 128 | const originalConsoleError = console.error; 129 | console.error = vi.fn(); 130 | 131 | mockFetch.mockResolvedValueOnce({ 132 | ok: false, 133 | status: 400, 134 | }); 135 | 136 | const result = await tool.execute({ 137 | organization_name: "Test Company", 138 | positions: [{ id: 1 }], 139 | is_client_work: false, 140 | start_year: 2020, 141 | start_month: 1, 142 | end_year: 2023, 143 | end_month: 12, 144 | position_name: "", 145 | client_company_name: "", 146 | description: "", 147 | }); 148 | 149 | expect(result.isError).toBe(true); 150 | expect(result.content[0].text).toContain("職歴の新規追加に失敗しました"); 151 | expect(console.error).toHaveBeenCalled(); 152 | 153 | console.error = originalConsoleError; 154 | }); 155 | 156 | it("descriptionの\\nを改行文字に変換する", async () => { 157 | const createParams = { 158 | organization_name: "Test Company", 159 | positions: [{ id: 1 }], 160 | is_client_work: false, 161 | start_year: 2020, 162 | start_month: 1, 163 | end_year: 2023, 164 | end_month: 12, 165 | position_name: "", 166 | client_company_name: "", 167 | description: "Line 1\\nLine 2\\nLine 3", 168 | }; 169 | 170 | const expectedBody = { 171 | ...createParams, 172 | description: "Line 1\nLine 2\nLine 3", 173 | }; 174 | 175 | mockFetch.mockResolvedValueOnce({ 176 | ok: true, 177 | json: () => Promise.resolve({ ...expectedBody, id: "123" }), 178 | }); 179 | 180 | const result = await tool.execute(createParams); 181 | 182 | expect(result.isError).toBeUndefined(); 183 | expect(mockFetch).toHaveBeenCalledWith( 184 | expect.any(URL), 185 | expect.objectContaining({ 186 | method: "POST", 187 | body: JSON.stringify(expectedBody), 188 | }), 189 | ); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /src/tools/__tests__/deleteExperience.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { DeleteExperienceTool } from "../deleteExperience.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("DeleteExperienceTool", () => { 12 | let tool: DeleteExperienceTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new DeleteExperienceTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("職歴を正常に削除できる", async () => { 29 | mockFetch.mockResolvedValueOnce({ 30 | ok: true, 31 | }); 32 | 33 | const result = await tool.execute({ experience_id: 123 }); 34 | 35 | expect(result.isError).toBeUndefined(); 36 | expect(result.content[0].type).toBe("text"); 37 | expect(result.content[0].text).toBe("職歴の削除が完了しました"); 38 | 39 | expect(mockFetch).toHaveBeenCalledWith( 40 | expect.any(URL), 41 | expect.objectContaining({ 42 | method: "DELETE", 43 | headers: { 44 | accept: "application/json, text/plain, */*", 45 | Authorization: "Bearer test-api-key", 46 | }, 47 | }), 48 | ); 49 | }); 50 | 51 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 52 | process.env.LAPRAS_API_KEY = undefined; 53 | 54 | const result = await tool.execute({ experience_id: 123 }); 55 | 56 | expect(result.isError).toBe(true); 57 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 58 | }); 59 | 60 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 61 | const originalConsoleError = console.error; 62 | console.error = vi.fn(); 63 | 64 | mockFetch.mockResolvedValueOnce({ 65 | ok: false, 66 | status: 404, 67 | }); 68 | 69 | const result = await tool.execute({ experience_id: 123 }); 70 | 71 | expect(result.isError).toBe(true); 72 | expect(result.content[0].text).toContain("職歴の削除に失敗しました"); 73 | expect(console.error).toHaveBeenCalled(); 74 | 75 | console.error = originalConsoleError; 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/tools/__tests__/getExperiences.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { GetExpriencesTool } from "../getExperiences.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("GetExpriencesTool", () => { 12 | let tool: GetExpriencesTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new GetExpriencesTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("職歴一覧を正常に取得できる", async () => { 29 | const mockData = { 30 | experiences: [ 31 | { 32 | id: 123, 33 | organization_name: "Test Company", 34 | positions: [{ id: 1 }], 35 | start_year: 2020, 36 | start_month: 1, 37 | end_year: 2023, 38 | end_month: 12, 39 | }, 40 | { 41 | id: 124, 42 | organization_name: "Another Company", 43 | positions: [{ id: 2 }], 44 | start_year: 2018, 45 | start_month: 4, 46 | end_year: 2020, 47 | end_month: 3, 48 | }, 49 | ], 50 | }; 51 | 52 | mockFetch.mockResolvedValueOnce({ 53 | ok: true, 54 | json: () => Promise.resolve(mockData), 55 | }); 56 | 57 | const result = await tool.execute(); 58 | 59 | expect(result.isError).toBeUndefined(); 60 | expect(result.content[0].type).toBe("text"); 61 | expect(result.content[0].text).toBe(JSON.stringify(mockData, null, 2)); 62 | 63 | expect(mockFetch).toHaveBeenCalledWith( 64 | expect.any(URL), 65 | expect.objectContaining({ 66 | method: "GET", 67 | headers: { 68 | accept: "application/json, text/plain, */*", 69 | Authorization: "Bearer test-api-key", 70 | }, 71 | }), 72 | ); 73 | }); 74 | 75 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 76 | process.env.LAPRAS_API_KEY = undefined; 77 | 78 | const result = await tool.execute(); 79 | 80 | expect(result.isError).toBe(true); 81 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 82 | }); 83 | 84 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 85 | const originalConsoleError = console.error; 86 | console.error = vi.fn(); 87 | 88 | mockFetch.mockResolvedValueOnce({ 89 | ok: false, 90 | status: 400, 91 | json: () => Promise.resolve({}), 92 | }); 93 | 94 | const result = await tool.execute(); 95 | expect(result.isError).toBe(true); 96 | expect(result.content[0].text).toContain("職歴の取得に失敗しました"); 97 | 98 | console.error = originalConsoleError; 99 | }); 100 | 101 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 102 | const originalConsoleError = console.error; 103 | console.error = vi.fn(); 104 | 105 | const networkError = new Error("Network error"); 106 | mockFetch.mockRejectedValueOnce(networkError); 107 | 108 | const result = await tool.execute(); 109 | expect(result.isError).toBe(true); 110 | expect(result.content[0].text).toContain("職歴の取得に失敗しました"); 111 | 112 | console.error = originalConsoleError; 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/tools/__tests__/getJobDetail.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { GetJobDetailTool } from "../getJobDetail.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("GetJobDetailTool", () => { 12 | let tool: GetJobDetailTool; 13 | let mockFetch: ReturnType; 14 | 15 | beforeEach(() => { 16 | tool = new GetJobDetailTool(); 17 | mockFetch = fetch as unknown as ReturnType; 18 | vi.clearAllMocks(); 19 | }); 20 | 21 | afterEach(() => { 22 | vi.resetAllMocks(); 23 | }); 24 | 25 | it("jobIdが空の場合はエラーを返す", async () => { 26 | const result = await tool.execute({ jobId: "" }); 27 | expect(result.isError).toBe(true); 28 | expect(result.content[0].text).toContain("求人IDが必要です"); 29 | }); 30 | 31 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 32 | const originalConsoleError = console.error; 33 | console.error = vi.fn(); 34 | 35 | mockFetch.mockResolvedValueOnce({ 36 | ok: false, 37 | status: 404, 38 | json: () => Promise.resolve({}), 39 | }); 40 | 41 | const result = await tool.execute({ jobId: "123" }); 42 | expect(result.isError).toBe(true); 43 | expect(result.content[0].text).toContain("求人詳細の取得に失敗しました"); 44 | expect(console.error).toHaveBeenCalled(); 45 | 46 | console.error = originalConsoleError; 47 | }); 48 | 49 | it("正常なレスポンスを適切に処理できる", async () => { 50 | const mockData = { 51 | job_description_id: "123", 52 | job_description: { 53 | title: "Software Engineer", 54 | company_name: "LAPRAS Inc.", 55 | description: "Job description here", 56 | }, 57 | company: { 58 | name: "LAPRAS Inc.", 59 | logo_image_url: "https://example.com/logo.png", 60 | }, 61 | images: ["image1.jpg", "image2.jpg"], 62 | }; 63 | 64 | mockFetch.mockResolvedValueOnce({ 65 | ok: true, 66 | json: () => Promise.resolve(mockData), 67 | }); 68 | 69 | const result = await tool.execute({ jobId: "123" }); 70 | 71 | expect(result.isError).toBeUndefined(); 72 | expect(result.content[0].type).toBe("text"); 73 | expect(result.content[0].text).toBe(JSON.stringify(mockData, null, 2)); 74 | 75 | expect(mockFetch).toHaveBeenCalledWith("https://lapras.com/api/mcp/job_descriptions/123"); 76 | }); 77 | 78 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 79 | const originalConsoleError = console.error; 80 | console.error = vi.fn(); 81 | 82 | const networkError = new Error("Network error"); 83 | mockFetch.mockRejectedValueOnce(networkError); 84 | 85 | const result = await tool.execute({ jobId: "123" }); 86 | expect(result.isError).toBe(true); 87 | expect(result.content[0].text).toContain("求人詳細の取得に失敗しました"); 88 | expect(console.error).toHaveBeenCalledWith(networkError); 89 | 90 | console.error = originalConsoleError; 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /src/tools/__tests__/getJobSummary.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { GetJobSummaryTool } from "../getJobSummary.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("GetJobSummaryTool", () => { 12 | let tool: GetJobSummaryTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new GetJobSummaryTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("職務要約を正常に取得できる", async () => { 29 | const mockData = { 30 | job_summary: "これは職務要約のサンプルテキストです。", 31 | }; 32 | 33 | mockFetch.mockResolvedValueOnce({ 34 | ok: true, 35 | json: () => Promise.resolve(mockData), 36 | }); 37 | 38 | const result = await tool.execute(); 39 | 40 | expect(result.isError).toBeUndefined(); 41 | expect(result.content[0].type).toBe("text"); 42 | expect(result.content[0].text).toBe(JSON.stringify(mockData, null, 2)); 43 | 44 | expect(mockFetch).toHaveBeenCalledWith( 45 | expect.any(URL), 46 | expect.objectContaining({ 47 | method: "GET", 48 | headers: { 49 | accept: "application/json, text/plain, */*", 50 | Authorization: "Bearer test-api-key", 51 | }, 52 | }), 53 | ); 54 | }); 55 | 56 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 57 | process.env.LAPRAS_API_KEY = undefined; 58 | 59 | const result = await tool.execute(); 60 | 61 | expect(result.isError).toBe(true); 62 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 63 | }); 64 | 65 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 66 | const originalConsoleError = console.error; 67 | console.error = vi.fn(); 68 | 69 | mockFetch.mockResolvedValueOnce({ 70 | ok: false, 71 | status: 400, 72 | json: () => Promise.resolve({}), 73 | }); 74 | 75 | const result = await tool.execute(); 76 | expect(result.isError).toBe(true); 77 | expect(result.content[0].text).toContain("職務要約の取得に失敗しました"); 78 | 79 | console.error = originalConsoleError; 80 | }); 81 | 82 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 83 | const originalConsoleError = console.error; 84 | console.error = vi.fn(); 85 | 86 | const networkError = new Error("Network error"); 87 | mockFetch.mockRejectedValueOnce(networkError); 88 | 89 | const result = await tool.execute(); 90 | expect(result.isError).toBe(true); 91 | expect(result.content[0].text).toContain("職務要約の取得に失敗しました"); 92 | 93 | console.error = originalConsoleError; 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /src/tools/__tests__/getWantToDo.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { GetWantToDoTool } from "../getWantToDo.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("GetWantToDoTool", () => { 12 | let tool: GetWantToDoTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new GetWantToDoTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("今後のキャリアでやりたいことを正常に取得できる", async () => { 29 | const mockData = { 30 | want_to_do: "これからはAIエンジニアとして活躍したいです。", 31 | }; 32 | 33 | mockFetch.mockResolvedValueOnce({ 34 | ok: true, 35 | json: () => Promise.resolve(mockData), 36 | }); 37 | 38 | const result = await tool.execute(); 39 | 40 | expect(result.isError).toBeUndefined(); 41 | expect(result.content[0].type).toBe("text"); 42 | expect(result.content[0].text).toBe(JSON.stringify(mockData, null, 2)); 43 | 44 | expect(mockFetch).toHaveBeenCalledWith( 45 | expect.any(URL), 46 | expect.objectContaining({ 47 | method: "GET", 48 | headers: { 49 | accept: "application/json, text/plain, */*", 50 | Authorization: "Bearer test-api-key", 51 | }, 52 | }), 53 | ); 54 | }); 55 | 56 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 57 | process.env.LAPRAS_API_KEY = undefined; 58 | 59 | const result = await tool.execute(); 60 | 61 | expect(result.isError).toBe(true); 62 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 63 | }); 64 | 65 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 66 | const originalConsoleError = console.error; 67 | console.error = vi.fn(); 68 | 69 | mockFetch.mockResolvedValueOnce({ 70 | ok: false, 71 | status: 400, 72 | json: () => Promise.resolve({}), 73 | }); 74 | 75 | const result = await tool.execute(); 76 | expect(result.isError).toBe(true); 77 | expect(result.content[0].text).toContain("今後のキャリアでやりたいことの取得に失敗しました"); 78 | 79 | console.error = originalConsoleError; 80 | }); 81 | 82 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 83 | const originalConsoleError = console.error; 84 | console.error = vi.fn(); 85 | 86 | const networkError = new Error("Network error"); 87 | mockFetch.mockRejectedValueOnce(networkError); 88 | 89 | const result = await tool.execute(); 90 | expect(result.isError).toBe(true); 91 | expect(result.content[0].text).toContain("今後のキャリアでやりたいことの取得に失敗しました"); 92 | 93 | console.error = originalConsoleError; 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /src/tools/__tests__/searchJobs.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { SearchJobsTool } from "../searchJobs.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("SearchJobsTool", () => { 12 | let tool: SearchJobsTool; 13 | let mockFetch: ReturnType; 14 | 15 | beforeEach(() => { 16 | tool = new SearchJobsTool(); 17 | mockFetch = fetch as unknown as ReturnType; 18 | vi.clearAllMocks(); 19 | }); 20 | 21 | afterEach(() => { 22 | vi.resetAllMocks(); 23 | }); 24 | 25 | it("基本的なパラメータで正常にAPIリクエストを実行できる", async () => { 26 | const mockData = { 27 | job_descriptions: [ 28 | { 29 | job_description_id: 123, 30 | company_id: 456, 31 | title: "Software Engineer", 32 | created_at: 1234567890, 33 | updated_at: 1234567890, 34 | company: { 35 | name: "LAPRAS Inc.", 36 | }, 37 | work_location_prefecture: ["Tokyo"], 38 | url: "https://example.com/job/123", 39 | }, 40 | ], 41 | total_count: 10, 42 | current_page: 1, 43 | per_page: 20, 44 | total_pages: 1, 45 | }; 46 | 47 | mockFetch.mockResolvedValueOnce({ 48 | ok: true, 49 | json: () => Promise.resolve(mockData), 50 | }); 51 | 52 | const result = await tool.execute({ keyword: "engineer" } as any); 53 | 54 | expect(result.isError).toBeUndefined(); 55 | expect(result.content[0].type).toBe("text"); 56 | expect(JSON.parse(result.content[0].text)).toEqual(mockData); 57 | 58 | const callUrl = mockFetch.mock.calls[0][0].toString(); 59 | expect(callUrl).toContain( 60 | "https://lapras.com/api/mcp/job_descriptions/search?keyword=engineer", 61 | ); 62 | }); 63 | 64 | it("複数のパラメータを使用して正常にAPIリクエストを実行できる", async () => { 65 | const mockData = { 66 | job_descriptions: [ 67 | { 68 | job_description_id: 456, 69 | company_id: 789, 70 | title: "Frontend Engineer", 71 | created_at: 1234567890, 72 | updated_at: 1234567890, 73 | company: { 74 | name: "LAPRAS Inc.", 75 | }, 76 | work_location_prefecture: ["Tokyo"], 77 | url: "https://example.com/job/456", 78 | }, 79 | ], 80 | total_count: 5, 81 | current_page: 1, 82 | per_page: 20, 83 | total_pages: 1, 84 | }; 85 | 86 | mockFetch.mockResolvedValueOnce({ 87 | ok: true, 88 | json: () => Promise.resolve(mockData), 89 | }); 90 | 91 | const params = { 92 | keyword: "frontend", 93 | page: 1, 94 | positions: ["FRONTEND_ENGINEER"], 95 | prog_lang_ids: [3], // TypeScript 96 | annual_salary_min: 6000000, 97 | sort_type: "popularity_desc", 98 | }; 99 | 100 | const result = await tool.execute(params as any); 101 | 102 | expect(result.isError).toBeUndefined(); 103 | expect(result.content[0].type).toBe("text"); 104 | expect(JSON.parse(result.content[0].text)).toEqual(mockData); 105 | 106 | const fetchCall = mockFetch.mock.calls[0][0].toString(); 107 | expect(fetchCall).toContain("keyword=frontend"); 108 | expect(fetchCall).toContain("page=1"); 109 | expect(fetchCall).toContain("positions%5B%5D=FRONTEND_ENGINEER"); 110 | expect(fetchCall).toContain("prog_lang_ids%5B%5D=3"); 111 | expect(fetchCall).toContain("annual_salary_min=6000000"); 112 | expect(fetchCall).toContain("sort_type=popularity_desc"); 113 | }); 114 | 115 | it("すべての配列パラメータを正しく処理できる", async () => { 116 | const mockData = { 117 | job_descriptions: [ 118 | { 119 | job_description_id: 789, 120 | company_id: 101, 121 | title: "Full Stack Engineer", 122 | created_at: 1234567890, 123 | updated_at: 1234567890, 124 | company: { 125 | name: "LAPRAS Inc.", 126 | }, 127 | work_location_prefecture: ["Tokyo"], 128 | url: "https://example.com/job/789", 129 | }, 130 | ], 131 | total_count: 3, 132 | current_page: 1, 133 | per_page: 20, 134 | total_pages: 1, 135 | }; 136 | mockFetch.mockResolvedValueOnce({ 137 | ok: true, 138 | json: () => Promise.resolve(mockData), 139 | }); 140 | 141 | const params = { 142 | positions: ["FRONTEND_ENGINEER", "BACKEND_ENGINEER"], 143 | prog_lang_ids: [3, 39], // TypeScript, JavaScript 144 | framework_ids: [4, 1428], // Vue.js, React 145 | db_ids: [28, 10], // MySQL, PostgreSQL 146 | infra_ids: [15, 52], // AWS, GCP 147 | business_types: [1, 2], // 自社開発, 受託開発 148 | employment_types: [1], // 正社員 149 | work_styles: [1, 2], // フルリモート, 一部リモート 150 | preferred_condition_ids: [1, 3], // 副業OK, SOあり 151 | }; 152 | 153 | const result = await tool.execute(params as any); 154 | 155 | expect(result.isError).toBeUndefined(); 156 | expect(result.content[0].type).toBe("text"); 157 | expect(JSON.parse(result.content[0].text)).toEqual(mockData); 158 | 159 | const fetchCall = mockFetch.mock.calls[0][0].toString(); 160 | expect(fetchCall).toContain("positions%5B%5D=FRONTEND_ENGINEER"); 161 | expect(fetchCall).toContain("positions%5B%5D=BACKEND_ENGINEER"); 162 | expect(fetchCall).toContain("prog_lang_ids%5B%5D=3"); 163 | expect(fetchCall).toContain("prog_lang_ids%5B%5D=39"); 164 | expect(fetchCall).toContain("framework_ids%5B%5D=4"); 165 | expect(fetchCall).toContain("framework_ids%5B%5D=1428"); 166 | expect(fetchCall).toContain("db_ids%5B%5D=28"); 167 | expect(fetchCall).toContain("db_ids%5B%5D=10"); 168 | expect(fetchCall).toContain("infra_ids%5B%5D=15"); 169 | expect(fetchCall).toContain("infra_ids%5B%5D=52"); 170 | expect(fetchCall).toContain("business_types%5B%5D=1"); 171 | expect(fetchCall).toContain("business_types%5B%5D=2"); 172 | expect(fetchCall).toContain("employment_types%5B%5D=1"); 173 | expect(fetchCall).toContain("work_styles%5B%5D=1"); 174 | expect(fetchCall).toContain("work_styles%5B%5D=2"); 175 | expect(fetchCall).toContain("preferred_condition_ids%5B%5D=1"); 176 | expect(fetchCall).toContain("preferred_condition_ids%5B%5D=3"); 177 | }); 178 | 179 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 180 | const originalConsoleError = console.error; 181 | console.error = vi.fn(); 182 | 183 | mockFetch.mockResolvedValueOnce({ 184 | ok: false, 185 | status: 404, 186 | json: () => Promise.resolve({}), 187 | }); 188 | 189 | const result = await tool.execute({ keyword: "engineer" } as any); 190 | expect(result.isError).toBe(true); 191 | expect(result.content[0].text).toContain("求人情報の取得に失敗しました"); 192 | 193 | console.error = originalConsoleError; 194 | }); 195 | 196 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 197 | const originalConsoleError = console.error; 198 | console.error = vi.fn(); 199 | 200 | const networkError = new Error("Network error"); 201 | mockFetch.mockRejectedValueOnce(networkError); 202 | 203 | const result = await tool.execute({ keyword: "engineer" } as any); 204 | expect(result.isError).toBe(true); 205 | expect(result.content[0].text).toContain("求人情報の取得に失敗しました"); 206 | 207 | console.error = originalConsoleError; 208 | }); 209 | 210 | it("APIレスポンスが正しくバリデーションされ、画像URLが除外される", async () => { 211 | const mockApiResponse = { 212 | job_descriptions: [ 213 | { 214 | job_description_id: 123, 215 | company_id: 456, 216 | title: "エンジニア募集", 217 | created_at: 1234567890, 218 | updated_at: 1234567890, 219 | service_image_url: "https://example.com/image.jpg", 220 | service_image_thumbnail_url: "https://example.com/thumbnail.jpg", 221 | company: { 222 | name: "LAPRAS Inc.", 223 | logo_image_url: "https://example.com/logo.jpg", 224 | }, 225 | work_location_prefecture: ["東京都"], 226 | position_name: "バックエンドエンジニア", 227 | tags: [{ name: "Python" }, { name: "Django" }], 228 | employment_type: "正社員", 229 | salary_min: 5000000, 230 | salary_max: 8000000, 231 | salary_type: 1, 232 | preferred_condition_names: ["フレックス"], 233 | business_type_names: ["自社開発"], 234 | work_style_names: ["フルリモート"], 235 | url: "https://example.com/job/123", 236 | }, 237 | ], 238 | total_count: 1, 239 | current_page: 1, 240 | per_page: 20, 241 | total_pages: 1, 242 | }; 243 | 244 | mockFetch.mockResolvedValueOnce({ 245 | ok: true, 246 | json: () => Promise.resolve(mockApiResponse), 247 | }); 248 | 249 | const result = await tool.execute({} as any); 250 | 251 | expect(result.isError).toBeUndefined(); 252 | const parsedContent = JSON.parse(result.content[0].text); 253 | 254 | // 画像URLが除外されていることを確認 255 | expect(parsedContent.job_descriptions[0].service_image_url).toBeUndefined(); 256 | expect(parsedContent.job_descriptions[0].service_image_thumbnail_url).toBeUndefined(); 257 | expect(parsedContent.job_descriptions[0].company.logo_image_url).toBeUndefined(); 258 | 259 | // タグが文字列に変換されていることを確認 260 | expect(parsedContent.job_descriptions[0].tags).toBe("Python, Django"); 261 | 262 | // 必須フィールドが存在することを確認 263 | expect(parsedContent.job_descriptions[0].job_description_id).toBe(123); 264 | expect(parsedContent.job_descriptions[0].title).toBe("エンジニア募集"); 265 | expect(parsedContent.job_descriptions[0].company.name).toBe("LAPRAS Inc."); 266 | }); 267 | 268 | it("不正なAPIレスポンスの場合はエラーを返す", async () => { 269 | const invalidApiResponse = { 270 | job_descriptions: [ 271 | { 272 | // job_description_idが欠けている 273 | title: "エンジニア募集", 274 | company: { 275 | name: "LAPRAS Inc.", 276 | }, 277 | }, 278 | ], 279 | // total_countが欠けている 280 | current_page: 1, 281 | per_page: 20, 282 | total_pages: 1, 283 | }; 284 | 285 | mockFetch.mockResolvedValueOnce({ 286 | ok: true, 287 | json: () => Promise.resolve(invalidApiResponse), 288 | }); 289 | 290 | const result = await tool.execute({} as any); 291 | expect(result.isError).toBe(true); 292 | expect(result.content[0].text).toContain("求人情報の取得に失敗しました"); 293 | }); 294 | }); 295 | -------------------------------------------------------------------------------- /src/tools/__tests__/updateExperience.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { UpdateExperienceTool } from "../updateExperience.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("UpdateExperienceTool", () => { 12 | let tool: UpdateExperienceTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new UpdateExperienceTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("職歴を正常に更新できる", async () => { 29 | const mockData = { 30 | id: 123, 31 | organization_name: "Updated Company", 32 | positions: [{ id: 1 }], 33 | start_year: 2020, 34 | start_month: 1, 35 | end_year: 2023, 36 | end_month: 12, 37 | }; 38 | 39 | const updateParams = { 40 | experience_id: 123, 41 | organization_name: "Updated Company", 42 | positions: [{ id: 1 }], 43 | is_client_work: false, 44 | start_year: 2020, 45 | start_month: 1, 46 | end_year: 2023, 47 | end_month: 12, 48 | position_name: "", 49 | client_company_name: "", 50 | description: "", 51 | }; 52 | 53 | mockFetch.mockResolvedValueOnce({ 54 | ok: true, 55 | json: () => Promise.resolve(mockData), 56 | }); 57 | 58 | const result = await tool.execute(updateParams); 59 | 60 | expect(result.isError).toBeUndefined(); 61 | expect(result.content[0].type).toBe("text"); 62 | expect(JSON.parse(result.content[0].text)).toEqual(mockData); 63 | 64 | expect(mockFetch).toHaveBeenCalledWith( 65 | expect.any(URL), 66 | expect.objectContaining({ 67 | method: "PUT", 68 | headers: { 69 | accept: "application/json, text/plain, */*", 70 | "Content-Type": "application/json", 71 | Authorization: "Bearer test-api-key", 72 | }, 73 | body: JSON.stringify({ 74 | organization_name: updateParams.organization_name, 75 | positions: updateParams.positions, 76 | is_client_work: updateParams.is_client_work, 77 | start_year: updateParams.start_year, 78 | start_month: updateParams.start_month, 79 | end_year: updateParams.end_year, 80 | end_month: updateParams.end_month, 81 | position_name: updateParams.position_name, 82 | client_company_name: updateParams.client_company_name, 83 | description: updateParams.description, 84 | }), 85 | }), 86 | ); 87 | }); 88 | 89 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 90 | process.env.LAPRAS_API_KEY = undefined; 91 | 92 | const result = await tool.execute({ 93 | experience_id: 123, 94 | organization_name: "Test Company", 95 | positions: [{ id: 1 }], 96 | is_client_work: false, 97 | start_year: 2020, 98 | start_month: 1, 99 | end_year: 2023, 100 | end_month: 12, 101 | position_name: "", 102 | client_company_name: "", 103 | description: "", 104 | }); 105 | 106 | expect(result.isError).toBe(true); 107 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 108 | }); 109 | 110 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 111 | const originalConsoleError = console.error; 112 | console.error = vi.fn(); 113 | 114 | mockFetch.mockResolvedValueOnce({ 115 | ok: false, 116 | status: 400, 117 | }); 118 | 119 | const result = await tool.execute({ 120 | experience_id: 123, 121 | organization_name: "Test Company", 122 | positions: [{ id: 1 }], 123 | is_client_work: false, 124 | start_year: 2020, 125 | start_month: 1, 126 | end_year: 2023, 127 | end_month: 12, 128 | position_name: "", 129 | client_company_name: "", 130 | description: "", 131 | }); 132 | 133 | expect(result.isError).toBe(true); 134 | expect(result.content[0].text).toContain("職歴の更新に失敗しました"); 135 | expect(console.error).toHaveBeenCalled(); 136 | 137 | console.error = originalConsoleError; 138 | }); 139 | 140 | it("descriptionの\\nを改行文字に変換する", async () => { 141 | const updateParams = { 142 | experience_id: 123, 143 | organization_name: "Test Company", 144 | positions: [{ id: 1 }], 145 | is_client_work: false, 146 | start_year: 2020, 147 | start_month: 1, 148 | end_year: 2023, 149 | end_month: 12, 150 | position_name: "", 151 | client_company_name: "", 152 | description: "Line 1\\nLine 2\\nLine 3", 153 | }; 154 | 155 | const expectedBody = { 156 | organization_name: updateParams.organization_name, 157 | positions: updateParams.positions, 158 | is_client_work: updateParams.is_client_work, 159 | start_year: updateParams.start_year, 160 | start_month: updateParams.start_month, 161 | end_year: updateParams.end_year, 162 | end_month: updateParams.end_month, 163 | position_name: updateParams.position_name, 164 | client_company_name: updateParams.client_company_name, 165 | description: "Line 1\nLine 2\nLine 3", 166 | }; 167 | 168 | mockFetch.mockResolvedValueOnce({ 169 | ok: true, 170 | json: () => Promise.resolve({ ...expectedBody, id: 123 }), 171 | }); 172 | 173 | const result = await tool.execute(updateParams); 174 | 175 | expect(result.isError).toBeUndefined(); 176 | expect(mockFetch).toHaveBeenCalledWith( 177 | expect.any(URL), 178 | expect.objectContaining({ 179 | method: "PUT", 180 | body: JSON.stringify(expectedBody), 181 | }), 182 | ); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /src/tools/__tests__/updateJobSummary.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { UpdateJobSummaryTool } from "../updateJobSummary.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("UpdateJobSummaryTool", () => { 12 | let tool: UpdateJobSummaryTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new UpdateJobSummaryTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("職務要約を正常に更新できる", async () => { 29 | const mockData = { 30 | error: false, 31 | }; 32 | 33 | mockFetch.mockResolvedValueOnce({ 34 | ok: true, 35 | json: () => Promise.resolve(mockData), 36 | }); 37 | 38 | const jobSummary = "新しい職務要約です。この職務要約は更新されます。"; 39 | const result = await tool.execute({ job_summary: jobSummary }); 40 | 41 | expect(result.isError).toBeUndefined(); 42 | expect(result.content[0].type).toBe("text"); 43 | expect(result.content[0].text).toBe(JSON.stringify(mockData, null, 2)); 44 | 45 | expect(mockFetch).toHaveBeenCalledWith( 46 | expect.any(URL), 47 | expect.objectContaining({ 48 | method: "PUT", 49 | headers: { 50 | accept: "application/json, text/plain, */*", 51 | "Content-Type": "application/json", 52 | Authorization: "Bearer test-api-key", 53 | }, 54 | body: JSON.stringify({ job_summary: jobSummary }), 55 | }), 56 | ); 57 | }); 58 | 59 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 60 | process.env.LAPRAS_API_KEY = undefined; 61 | 62 | const result = await tool.execute({ job_summary: "テスト" }); 63 | 64 | expect(result.isError).toBe(true); 65 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 66 | }); 67 | 68 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 69 | const originalConsoleError = console.error; 70 | console.error = vi.fn(); 71 | 72 | mockFetch.mockResolvedValueOnce({ 73 | ok: false, 74 | status: 400, 75 | json: () => Promise.resolve({}), 76 | }); 77 | 78 | const result = await tool.execute({ job_summary: "テスト" }); 79 | expect(result.isError).toBe(true); 80 | expect(result.content[0].text).toContain("職務要約の更新に失敗しました"); 81 | 82 | console.error = originalConsoleError; 83 | }); 84 | 85 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 86 | const originalConsoleError = console.error; 87 | console.error = vi.fn(); 88 | 89 | const networkError = new Error("Network error"); 90 | mockFetch.mockRejectedValueOnce(networkError); 91 | 92 | const result = await tool.execute({ job_summary: "テスト" }); 93 | expect(result.isError).toBe(true); 94 | expect(result.content[0].text).toContain("職務要約の更新に失敗しました"); 95 | 96 | console.error = originalConsoleError; 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /src/tools/__tests__/updateWantToDo.test.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 | import { UpdateWantToDoTool } from "../updateWantToDo.js"; 4 | 5 | vi.mock("node-fetch", () => { 6 | return { 7 | default: vi.fn(), 8 | }; 9 | }); 10 | 11 | describe("UpdateWantToDoTool", () => { 12 | let tool: UpdateWantToDoTool; 13 | let mockFetch: ReturnType; 14 | const originalEnv = process.env; 15 | 16 | beforeEach(() => { 17 | tool = new UpdateWantToDoTool(); 18 | mockFetch = fetch as unknown as ReturnType; 19 | process.env = { ...originalEnv, LAPRAS_API_KEY: "test-api-key" }; 20 | vi.clearAllMocks(); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks(); 25 | process.env = originalEnv; 26 | }); 27 | 28 | it("今後のキャリアでやりたいことを正常に更新できる", async () => { 29 | const mockData = { 30 | error: false, 31 | }; 32 | 33 | mockFetch.mockResolvedValueOnce({ 34 | ok: true, 35 | json: () => Promise.resolve(mockData), 36 | }); 37 | 38 | const wantToDo = "今後はAIエンジニアとして成長し、大規模プロジェクトをリードしていきたいです。"; 39 | const result = await tool.execute({ want_to_do: wantToDo }); 40 | 41 | expect(result.isError).toBeUndefined(); 42 | expect(result.content[0].type).toBe("text"); 43 | expect(result.content[0].text).toBe(JSON.stringify(mockData, null, 2)); 44 | 45 | expect(mockFetch).toHaveBeenCalledWith( 46 | expect.any(URL), 47 | expect.objectContaining({ 48 | method: "PUT", 49 | headers: { 50 | accept: "application/json, text/plain, */*", 51 | "Content-Type": "application/json", 52 | Authorization: "Bearer test-api-key", 53 | }, 54 | body: JSON.stringify({ want_to_do: wantToDo }), 55 | }), 56 | ); 57 | }); 58 | 59 | it("LAPRAS_API_KEYが設定されていない場合はエラーを返す", async () => { 60 | process.env.LAPRAS_API_KEY = undefined; 61 | 62 | const result = await tool.execute({ want_to_do: "テスト" }); 63 | 64 | expect(result.isError).toBe(true); 65 | expect(result.content[0].text).toContain("LAPRAS_API_KEYの設定が必要です"); 66 | }); 67 | 68 | it("APIリクエストが失敗した場合はエラーを返す", async () => { 69 | const originalConsoleError = console.error; 70 | console.error = vi.fn(); 71 | 72 | mockFetch.mockResolvedValueOnce({ 73 | ok: false, 74 | status: 400, 75 | json: () => Promise.resolve({}), 76 | }); 77 | 78 | const result = await tool.execute({ want_to_do: "テスト" }); 79 | expect(result.isError).toBe(true); 80 | expect(result.content[0].text).toContain("今後のキャリアでやりたいことの更新に失敗しました"); 81 | 82 | console.error = originalConsoleError; 83 | }); 84 | 85 | it("ネットワークエラーが発生した場合は適切にエラーハンドリングする", async () => { 86 | const originalConsoleError = console.error; 87 | console.error = vi.fn(); 88 | 89 | const networkError = new Error("Network error"); 90 | mockFetch.mockRejectedValueOnce(networkError); 91 | 92 | const result = await tool.execute({ want_to_do: "テスト" }); 93 | expect(result.isError).toBe(true); 94 | expect(result.content[0].text).toContain("今後のキャリアでやりたいことの更新に失敗しました"); 95 | 96 | console.error = originalConsoleError; 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /src/tools/createExperience.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import { unescapeText } from "../helpers/textFormatter.js"; 7 | import { validateApiKey } from "../helpers/validateApiKey.js"; 8 | import type { IMCPTool, InferZodParams } from "../types.js"; 9 | 10 | /** 11 | * 職歴新規追加ツール 12 | */ 13 | export class CreateExperienceTool implements IMCPTool { 14 | /** 15 | * Tool name 16 | */ 17 | readonly name = "create_experience"; 18 | 19 | /** 20 | * Tool description 21 | */ 22 | readonly description = 23 | "Create a new work experience on LAPRAS(https://lapras.com). You can check the result at https://lapras.com/cv"; 24 | 25 | /** 26 | * Parameter definition 27 | */ 28 | readonly parameters = { 29 | organization_name: z.string().describe("Name of the organization"), 30 | positions: z 31 | .array( 32 | z.object({ 33 | id: z 34 | .number() 35 | .describe( 36 | "Position type ID (1: フロントエンドエンジニア, 2: バックエンドエンジニア, 3: Webアプリケーションエンジニア, 4: インフラエンジニア, 5: SRE, 6: Android アプリエンジニア, 7: iOS アプリエンジニア, 8: モバイルエンジニア, 9: 機械学習エンジニア, 10: データサイエンティスト, 11: プロジェクトマネージャー, 12: プロダクトマネージャー, 13: テックリード, 14: エンジニアリングマネージャー, 15: リサーチエンジニア, 16: QA・テストエンジニア, 17: アーキテクト, 18: システムエンジニア, 19: 組み込みエンジニア, 20: データベースエンジニア, 21: ネットワークエンジニア, 22: セキュリティエンジニア, 23: スクラムマスター, 24: ゲームエンジニア, 25: CTO, 26: コーポレートエンジニア, 27: デザイナーその他, 28: データエンジニア, 29: CRE・テクニカルサポート, 30: セールスエンジニア・プリセールス, 32: ITエンジニアその他, 33: UI/UXデザイナー, 34: Webデザイナー, 35: ゲームデザイナー, 36: 動画制作・編集, 37: Webプロデューサー・ディレクター, 38: Webコンテンツ企画・編集・ライティング, 39: ゲームプロデューサー・ディレクター, 40: プロダクトマーケティングマネージャー, 41: 動画プロデューサー・ディレクター, 42: アートディレクター, 43: PM/ディレクターその他, 44: 営業, 45: 法人営業, 46: 個人営業, 47: 営業企画, 48: 営業事務, 49: 代理店営業, 50: インサイドセールス, 51: セールスその他, 52: 事業企画, 53: 経営企画, 54: 新規事業開発, 55: 事業開発その他, 56: カスタマーサクセス, 57: カスタマーサポート, 58: ヘルプデスク, 59: コールセンター管理・運営, 60: カスタマーサクセス・サポートその他, 61: 広報・PR・広告宣伝, 62: リサーチ・データ分析, 63: 商品企画・開発, 64: 販促, 65: MD・VMD・バイヤー, 66: Web広告運用・SEO・SNS運用, 67: CRM, 68: 広報・マーケティングその他, 69: 経営者・CEO・COO等, 70: CFO, 71: CIO, 72: 監査役, 73: 経営・CXOその他, 74: 経理, 75: 財務, 76: 法務, 77: 総務, 78: 労務, 79: 秘書, 80: 事務, 81: コーポレートその他, 82: 採用, 83: 人材開発・人材育成・研修, 84: 制度企画・組織開発, 85: 労務・給与, 86: 人事その他, 87: システムコンサルタント, 88: パッケージ導入コンサルタント, 89: セキュリティコンサルタント, 90: ネットワークコンサルタント, 91: ITコンサルタントその他, 92: 戦略コンサルタント, 93: DXコンサルタント, 94: 財務・会計コンサルタント, 95: 組織・人事コンサルタント, 96: 業務プロセスコンサルタント, 97: 物流コンサルタント, 98: リサーチャー・調査員, 99: コンサルタントその他, 100: その他)", 37 | ), 38 | }), 39 | ) 40 | .describe( 41 | "List of position type IDs - multiple selections are allowed. Please set relevant position types.", 42 | ), 43 | position_name: z.string().default("").describe("Position title"), 44 | is_client_work: z 45 | .boolean() 46 | .describe( 47 | "Whether this is client work (Set to true when the affiliated company and the project client are different, such as in contract development companies)", 48 | ), 49 | client_company_name: z 50 | .string() 51 | .optional() 52 | .describe("Client company name (required only when is_client_work is true)"), 53 | start_year: z.number().describe("Start year"), 54 | start_month: z.number().describe("Start month"), 55 | end_year: z.number().describe("End year (0 if ongoing)"), 56 | end_month: z.number().describe("End month (0 if ongoing)"), 57 | description: z 58 | .string() 59 | .default("") 60 | .describe("Detailed description of the experience (Markdown format)"), 61 | } as const; 62 | 63 | /** 64 | * Execute function 65 | */ 66 | async execute(args: InferZodParams): Promise<{ 67 | content: TextContent[]; 68 | isError?: boolean; 69 | }> { 70 | const apiKeyResult = validateApiKey(); 71 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 72 | 73 | try { 74 | const response = await fetch(new URL(`${BASE_URL}/experiences`), { 75 | method: "POST", 76 | headers: { 77 | accept: "application/json, text/plain, */*", 78 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 79 | }, 80 | body: JSON.stringify({ 81 | ...args, 82 | description: unescapeText(args.description), 83 | }), 84 | }); 85 | 86 | if (!response.ok) { 87 | throw new Error(`API request failed with status: ${response.status}`); 88 | } 89 | 90 | const data = await response.json(); 91 | return { 92 | content: [{ type: "text", text: JSON.stringify(data, null, 2) }], 93 | }; 94 | } catch (error) { 95 | console.error(error); 96 | return createErrorResponse(error, "職歴の新規追加に失敗しました"); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/tools/deleteExperience.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import { validateApiKey } from "../helpers/validateApiKey.js"; 7 | import type { IMCPTool, InferZodParams } from "../types.js"; 8 | 9 | /** 10 | * 職歴削除ツール 11 | */ 12 | export class DeleteExperienceTool implements IMCPTool { 13 | /** 14 | * Tool name 15 | */ 16 | readonly name = "delete_experience"; 17 | 18 | /** 19 | * Tool description 20 | */ 21 | readonly description = 22 | "Delete a work experience from LAPRAS(https://lapras.com). You can check the result at https://lapras.com/cv"; 23 | 24 | /** 25 | * Parameter definition 26 | */ 27 | readonly parameters = { 28 | experience_id: z.number().describe("ID of the experience to delete"), 29 | } as const; 30 | 31 | /** 32 | * Execute function 33 | */ 34 | async execute(args: InferZodParams): Promise<{ 35 | content: TextContent[]; 36 | isError?: boolean; 37 | }> { 38 | const apiKeyResult = validateApiKey(); 39 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 40 | 41 | try { 42 | const response = await fetch(new URL(`${BASE_URL}/experiences/${args.experience_id}`), { 43 | method: "DELETE", 44 | headers: { 45 | accept: "application/json, text/plain, */*", 46 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 47 | }, 48 | }); 49 | 50 | if (!response.ok) { 51 | throw new Error(`API request failed with status: ${response.status}`); 52 | } 53 | 54 | return { 55 | content: [{ type: "text", text: "職歴の削除が完了しました" }], 56 | }; 57 | } catch (error) { 58 | console.error(error); 59 | return createErrorResponse(error, "職歴の削除に失敗しました"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/tools/getExperiences.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { BASE_URL } from "../constants.js"; 4 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 5 | import { validateApiKey } from "../helpers/validateApiKey.js"; 6 | import type { IMCPTool } from "../types.js"; 7 | 8 | /** 9 | * 職歴取得ツール 10 | */ 11 | export class GetExpriencesTool implements IMCPTool { 12 | /** 13 | * Tool name 14 | */ 15 | readonly name = "get_experiences"; 16 | 17 | /** 18 | * Tool description 19 | */ 20 | readonly description = "Get work experiences on LAPRAS(https://lapras.com)"; 21 | 22 | /** 23 | * Parameter definition 24 | */ 25 | readonly parameters = {} as const; 26 | 27 | /** 28 | * Execute function 29 | */ 30 | async execute(): Promise<{ 31 | content: TextContent[]; 32 | isError?: boolean; 33 | }> { 34 | const apiKeyResult = validateApiKey(); 35 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 36 | 37 | try { 38 | const url = new URL(`${BASE_URL}/experiences`); 39 | const response = await fetch(url, { 40 | headers: { 41 | accept: "application/json, text/plain, */*", 42 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 43 | }, 44 | method: "GET", 45 | }); 46 | 47 | if (!response.ok) { 48 | throw new Error(`API request failed with status: ${response.status}`); 49 | } 50 | 51 | const data = await response.json(); 52 | 53 | const content: TextContent[] = [ 54 | { 55 | type: "text", 56 | text: JSON.stringify(data, null, 2), 57 | }, 58 | ]; 59 | 60 | return { content }; 61 | } catch (error) { 62 | console.error(error); 63 | return createErrorResponse(error, "職歴の取得に失敗しました"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/tools/getJobDetail.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import type { IMCPTool, InferZodParams } from "../types.js"; 7 | 8 | /** 9 | * 求人詳細取得ツール 10 | */ 11 | export class GetJobDetailTool implements IMCPTool { 12 | /** 13 | * Tool name 14 | */ 15 | readonly name = "get_job_detail"; 16 | 17 | /** 18 | * Tool description 19 | */ 20 | readonly description = 21 | "Get detailed information about a specific job posting. You can apply for jobs through the URL provided in the response."; 22 | 23 | /** 24 | * Parameter definition 25 | */ 26 | readonly parameters = { 27 | jobId: z.string().describe("The unique identifier of the job posting"), 28 | } as const; 29 | 30 | /** 31 | * Execute function 32 | */ 33 | async execute(args: InferZodParams): Promise<{ 34 | content: TextContent[]; 35 | isError?: boolean; 36 | }> { 37 | const { jobId } = args; 38 | if (!jobId) { 39 | return createErrorResponse(new Error("jobId is required"), "求人IDが必要です"); 40 | } 41 | 42 | const url = `${BASE_URL}/job_descriptions/${jobId}`; 43 | 44 | try { 45 | const response = await fetch(url); 46 | 47 | if (!response.ok) { 48 | throw new Error(`API request failed with status: ${response.status}`); 49 | } 50 | 51 | const data = await response.json(); 52 | 53 | const content: TextContent[] = [ 54 | { 55 | type: "text", 56 | text: JSON.stringify(data, null, 2), 57 | }, 58 | ]; 59 | 60 | return { content }; 61 | } catch (error) { 62 | console.error(error); 63 | return createErrorResponse(error, "求人詳細の取得に失敗しました"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/tools/getJobSummary.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { BASE_URL } from "../constants.js"; 4 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 5 | import { validateApiKey } from "../helpers/validateApiKey.js"; 6 | import type { IMCPTool } from "../types.js"; 7 | 8 | /** 9 | * 職務要約取得ツール 10 | */ 11 | export class GetJobSummaryTool implements IMCPTool { 12 | /** 13 | * Tool name 14 | */ 15 | readonly name = "get_job_summary"; 16 | 17 | /** 18 | * Tool description 19 | */ 20 | readonly description = "Get job summary(職務要約) on LAPRAS(https://lapras.com)"; 21 | 22 | /** 23 | * Parameter definition 24 | */ 25 | readonly parameters = {} as const; 26 | 27 | /** 28 | * Execute function 29 | */ 30 | async execute(): Promise<{ 31 | content: TextContent[]; 32 | isError?: boolean; 33 | }> { 34 | const apiKeyResult = validateApiKey(); 35 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 36 | 37 | try { 38 | const url = new URL(`${BASE_URL}/job_summary`); 39 | const response = await fetch(url, { 40 | headers: { 41 | accept: "application/json, text/plain, */*", 42 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 43 | }, 44 | method: "GET", 45 | }); 46 | 47 | if (!response.ok) { 48 | throw new Error(`API request failed with status: ${response.status}`); 49 | } 50 | 51 | const data = await response.json(); 52 | 53 | const content: TextContent[] = [ 54 | { 55 | type: "text", 56 | text: JSON.stringify(data, null, 2), 57 | }, 58 | ]; 59 | 60 | return { content }; 61 | } catch (error) { 62 | console.error(error); 63 | return createErrorResponse(error, "職務要約の取得に失敗しました"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/tools/getWantToDo.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { BASE_URL } from "../constants.js"; 4 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 5 | import { validateApiKey } from "../helpers/validateApiKey.js"; 6 | import type { IMCPTool } from "../types.js"; 7 | 8 | /** 9 | * 今後のキャリアでやりたいこと取得ツール 10 | */ 11 | export class GetWantToDoTool implements IMCPTool { 12 | /** 13 | * Tool name 14 | */ 15 | readonly name = "get_want_to_do"; 16 | 17 | /** 18 | * Tool description 19 | */ 20 | readonly description = 21 | "Get career aspirations(今後のキャリアでやりたいこと) on LAPRAS(https://lapras.com)"; 22 | 23 | /** 24 | * Parameter definition 25 | */ 26 | readonly parameters = {} as const; 27 | 28 | /** 29 | * Execute function 30 | */ 31 | async execute(): Promise<{ 32 | content: TextContent[]; 33 | isError?: boolean; 34 | }> { 35 | const apiKeyResult = validateApiKey(); 36 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 37 | 38 | try { 39 | const url = new URL(`${BASE_URL}/want_to_do`); 40 | const response = await fetch(url, { 41 | headers: { 42 | accept: "application/json, text/plain, */*", 43 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 44 | }, 45 | method: "GET", 46 | }); 47 | 48 | if (!response.ok) { 49 | throw new Error(`API request failed with status: ${response.status}`); 50 | } 51 | 52 | const data = await response.json(); 53 | 54 | const content: TextContent[] = [ 55 | { 56 | type: "text", 57 | text: JSON.stringify(data, null, 2), 58 | }, 59 | ]; 60 | 61 | return { content }; 62 | } catch (error) { 63 | console.error(error); 64 | return createErrorResponse(error, "今後のキャリアでやりたいことの取得に失敗しました"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/tools/searchJobs.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import type { IMCPTool, InferZodParams } from "../types.js"; 7 | 8 | export const JobSearchResultSchema = z.object({ 9 | job_description_id: z.number(), 10 | company_id: z.number(), 11 | title: z.string(), 12 | created_at: z.number(), 13 | updated_at: z.number(), 14 | service_image_url: z.string().optional(), 15 | company: z.object({ 16 | name: z.string(), 17 | logo_image_url: z.string().optional(), 18 | }), 19 | work_location_prefecture: z.array(z.string()), 20 | position_name: z.string().optional(), 21 | tags: z 22 | .array( 23 | z.object({ 24 | name: z.string(), 25 | }), 26 | ) 27 | .optional(), 28 | employment_type: z.string().optional(), 29 | salary_min: z.number().optional(), 30 | salary_max: z.number().optional(), 31 | service_image_thumbnail_url: z.string().optional(), 32 | salary_type: z.number().optional(), 33 | preferred_condition_names: z.array(z.string()).optional(), 34 | business_type_names: z.array(z.string()).optional(), 35 | work_style_names: z.array(z.string()).optional(), 36 | url: z.string(), 37 | }); 38 | 39 | export type JobSearchResult = z.infer; 40 | 41 | /** 42 | * 求人検索ツール 43 | */ 44 | export class SearchJobsTool implements IMCPTool { 45 | /** 46 | * Tool name 47 | */ 48 | readonly name = "search_jobs"; 49 | 50 | /** 51 | * Tool description 52 | */ 53 | readonly description = "Search job by keyword, position, and minimum annual salary"; 54 | 55 | /** 56 | * Parameter definition 57 | */ 58 | readonly parameters = { 59 | keyword: z.string().optional().describe("The keyword to search for in job listings"), 60 | page: z.number().optional().describe("Page number for pagination"), 61 | positions: z 62 | .array(z.string()) 63 | .optional() 64 | .describe( 65 | "List of job position keys (e.g., FRONTEND_ENGINEER, BACKEND_ENGINEER, WEB_APPLICATION_ENGINEER, INFRA_ENGINEER, SITE_RELIABILITY_ENGINEER, ANDROID_ENGINEER, IOS_ENGINEER, MOBILE_ENGINEER, MACHINE_LEARNING_ENGINEER, DATA_SCIENTIST, PROJECT_MANAGER, PRODUCT_MANAGER, TECH_LEAD, ENGINEERING_MANAGER, RESEARCH_ENGINEER, TEST_ENGINEER, SOFTWARE_ARCHITECT, SYSTEM_ENGINEER, EMBEDDED_ENGINEER, DATABASE_ENGINEER, NETWORK_ENGINEER, SECURITY_ENGINEER, SCRUM_MASTER, GAME_ENGINEER, CTO, CORPORATE_ENGINEER, DESIGNER, DATA_ENGINEER, OTHER)", 66 | ), 67 | prog_lang_ids: z 68 | .array(z.number()) 69 | .optional() 70 | .describe( 71 | "List of programming language IDs (3: TypeScript, 39: JavaScript, 5: Python, 32: Go, 2: Ruby, 25: PHP, 45: Java, 40: Kotlin, 27: Node.js, 43: Swift, 82: Scala, 421: C#, 46: Rust, 56: C++, 42: Dart, 55: Objective-C)", 72 | ), 73 | framework_ids: z 74 | .array(z.number()) 75 | .optional() 76 | .describe( 77 | "List of framework IDs (4: Vue.js, 1428: React, 20: Next.js, 31: Nuxt.js, 6: Angular, 172: Redux, 21: Ruby on Rails, 76: Laravel, 140: Spring Boot, 8: Django, 237: Express, 41: Flutter, 171: ReactNative)", 78 | ), 79 | db_ids: z 80 | .array(z.number()) 81 | .optional() 82 | .describe( 83 | "List of database IDs (28: MySQL, 10: PostgreSQL, 419: SQL Server, 318: Oracle, 33: Aurora, 60: Redis, 221: DynamoDB, 170: MongoDB, 169: Elasticsearch, 200: BigQuery)", 84 | ), 85 | infra_ids: z 86 | .array(z.number()) 87 | .optional() 88 | .describe( 89 | "List of infrastructure and CI/CD IDs (15: AWS, 52: GCP, 165: Azure, 18: Docker, 17: Terraform, 224: Kubernetes, 51: Firebase, 16: CircleCI, 122: Jenkins, 180: GitHubActions)", 90 | ), 91 | business_types: z 92 | .array(z.number()) 93 | .optional() 94 | .describe("List of business type IDs (1: 自社開発, 2: 受託開発, 3: SES)"), 95 | employment_types: z 96 | .array(z.number()) 97 | .optional() 98 | .describe( 99 | "List of employment type IDs (1: 正社員, 2: 業務委託, 3: インターンシップ, 4: その他)", 100 | ), 101 | work_styles: z 102 | .array(z.number()) 103 | .optional() 104 | .describe("List of work style IDs (1: フルリモート, 2: 一部リモート)"), 105 | preferred_condition_ids: z 106 | .array(z.number()) 107 | .optional() 108 | .describe( 109 | "List of preferred condition IDs (1: 副業OK, 2: 副業からのジョイン可, 3: SOあり, 4: BtoB, 5: BtoC, 6: 株式上場済み, 7: グローバル, 8: 残業平均20時間未満, 9: アジャイル開発, 10: 英語で書く・話す業務がある, 11: フレックス, 12: 役員以上にエンジニアがいる, 13: 育休取得実績あり, 14: 地方在住社員がいる, 15: スタートアップ, 16: 副業)", 110 | ), 111 | annual_salary_min: z.number().optional().describe("Minimum annual salary requirement in JPY"), 112 | sort_type: z 113 | .string() 114 | .optional() 115 | .describe( 116 | "Sort order (人気順: popularity_desc, 新着順: updated_at_desc, 年収が低い順: annual_salary_at_asc, 年収が高い順: annual_salary_at_desc)", 117 | ), 118 | } as const; 119 | 120 | /** 121 | * Execute function 122 | */ 123 | async execute(args: InferZodParams): Promise<{ 124 | content: TextContent[]; 125 | isError?: boolean; 126 | }> { 127 | const { 128 | keyword, 129 | page, 130 | positions, 131 | prog_lang_ids, 132 | framework_ids, 133 | db_ids, 134 | infra_ids, 135 | business_types, 136 | employment_types, 137 | work_styles, 138 | preferred_condition_ids, 139 | annual_salary_min, 140 | sort_type, 141 | } = args; 142 | 143 | const url = new URL(`${BASE_URL}/job_descriptions/search`); 144 | 145 | if (page) { 146 | url.searchParams.append("page", page.toString()); 147 | } 148 | 149 | if (keyword) { 150 | url.searchParams.append("keyword", keyword); 151 | } 152 | 153 | if (positions && positions.length > 0) { 154 | for (const position of positions) { 155 | url.searchParams.append("positions[]", position); 156 | } 157 | } 158 | 159 | if (prog_lang_ids && prog_lang_ids.length > 0) { 160 | for (const id of prog_lang_ids) { 161 | url.searchParams.append("prog_lang_ids[]", id.toString()); 162 | } 163 | } 164 | 165 | if (framework_ids && framework_ids.length > 0) { 166 | for (const id of framework_ids) { 167 | url.searchParams.append("framework_ids[]", id.toString()); 168 | } 169 | } 170 | 171 | if (db_ids && db_ids.length > 0) { 172 | for (const id of db_ids) { 173 | url.searchParams.append("db_ids[]", id.toString()); 174 | } 175 | } 176 | 177 | if (infra_ids && infra_ids.length > 0) { 178 | for (const id of infra_ids) { 179 | url.searchParams.append("infra_ids[]", id.toString()); 180 | } 181 | } 182 | 183 | if (business_types && business_types.length > 0) { 184 | for (const type of business_types) { 185 | url.searchParams.append("business_types[]", type.toString()); 186 | } 187 | } 188 | 189 | if (employment_types && employment_types.length > 0) { 190 | for (const type of employment_types) { 191 | url.searchParams.append("employment_types[]", type.toString()); 192 | } 193 | } 194 | 195 | if (work_styles && work_styles.length > 0) { 196 | for (const style of work_styles) { 197 | url.searchParams.append("work_styles[]", style.toString()); 198 | } 199 | } 200 | 201 | if (preferred_condition_ids && preferred_condition_ids.length > 0) { 202 | for (const id of preferred_condition_ids) { 203 | url.searchParams.append("preferred_condition_ids[]", id.toString()); 204 | } 205 | } 206 | 207 | if (annual_salary_min !== undefined) { 208 | url.searchParams.append("annual_salary_min", annual_salary_min.toString()); 209 | } 210 | 211 | if (sort_type) { 212 | url.searchParams.append("sort_type", sort_type); 213 | } 214 | 215 | try { 216 | const response = await fetch(url); 217 | 218 | if (!response.ok) { 219 | throw new Error(`API request failed with status: ${response.status}`); 220 | } 221 | 222 | const rawData = await response.json(); 223 | 224 | const ApiResponse = z.object({ 225 | job_descriptions: z.array(JobSearchResultSchema).catch([]), 226 | total_count: z.number(), 227 | current_page: z.number(), 228 | per_page: z.number(), 229 | total_pages: z.number(), 230 | }); 231 | 232 | const data = ApiResponse.parse(rawData); 233 | 234 | // 画像URLはコンテキスト長を圧迫するため除外 235 | const cleanedJobs = data.job_descriptions.map((job) => { 236 | const { service_image_url, service_image_thumbnail_url, company, tags, ...rest } = job; 237 | return { 238 | ...rest, 239 | tags: tags?.map((tag) => tag.name).join(", "), 240 | company: { 241 | name: company.name, 242 | }, 243 | }; 244 | }); 245 | 246 | const content: TextContent[] = [ 247 | { 248 | type: "text", 249 | text: JSON.stringify({ ...data, job_descriptions: cleanedJobs }, null, 2), 250 | }, 251 | ]; 252 | 253 | return { content }; 254 | } catch (error) { 255 | return createErrorResponse(error, "求人情報の取得に失敗しました"); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/tools/updateExperience.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import { unescapeText } from "../helpers/textFormatter.js"; 7 | import { validateApiKey } from "../helpers/validateApiKey.js"; 8 | import type { IMCPTool, InferZodParams } from "../types.js"; 9 | 10 | /** 11 | * 職歴更新ツール 12 | */ 13 | export class UpdateExperienceTool implements IMCPTool { 14 | /** 15 | * Tool name 16 | */ 17 | readonly name = "update_experience"; 18 | 19 | /** 20 | * Tool description 21 | */ 22 | readonly description = 23 | "Update a work experience on LAPRAS(https://lapras.com). You can check the result at https://lapras.com/cv"; 24 | 25 | /** 26 | * Parameter definition 27 | */ 28 | readonly parameters = { 29 | experience_id: z.number().describe("ID of the experience to update"), 30 | organization_name: z.string().describe("Name of the organization"), 31 | positions: z 32 | .array( 33 | z.object({ 34 | id: z 35 | .number() 36 | .describe( 37 | "Position type ID (1: フロントエンドエンジニア, 2: バックエンドエンジニア, 3: Webアプリケーションエンジニア, 4: インフラエンジニア, 5: SRE, 6: Android アプリエンジニア, 7: iOS アプリエンジニア, 8: モバイルエンジニア, 9: 機械学習エンジニア, 10: データサイエンティスト, 11: プロジェクトマネージャー, 12: プロダクトマネージャー, 13: テックリード, 14: エンジニアリングマネージャー, 15: リサーチエンジニア, 16: QA・テストエンジニア, 17: アーキテクト, 18: システムエンジニア, 19: 組み込みエンジニア, 20: データベースエンジニア, 21: ネットワークエンジニア, 22: セキュリティエンジニア, 23: スクラムマスター, 24: ゲームエンジニア, 25: CTO, 26: コーポレートエンジニア, 27: デザイナーその他, 28: データエンジニア, 29: CRE・テクニカルサポート, 30: セールスエンジニア・プリセールス, 32: ITエンジニアその他, 33: UI/UXデザイナー, 34: Webデザイナー, 35: ゲームデザイナー, 36: 動画制作・編集, 37: Webプロデューサー・ディレクター, 38: Webコンテンツ企画・編集・ライティング, 39: ゲームプロデューサー・ディレクター, 40: プロダクトマーケティングマネージャー, 41: 動画プロデューサー・ディレクター, 42: アートディレクター, 43: PM/ディレクターその他, 44: 営業, 45: 法人営業, 46: 個人営業, 47: 営業企画, 48: 営業事務, 49: 代理店営業, 50: インサイドセールス, 51: セールスその他, 52: 事業企画, 53: 経営企画, 54: 新規事業開発, 55: 事業開発その他, 56: カスタマーサクセス, 57: カスタマーサポート, 58: ヘルプデスク, 59: コールセンター管理・運営, 60: カスタマーサクセス・サポートその他, 61: 広報・PR・広告宣伝, 62: リサーチ・データ分析, 63: 商品企画・開発, 64: 販促, 65: MD・VMD・バイヤー, 66: Web広告運用・SEO・SNS運用, 67: CRM, 68: 広報・マーケティングその他, 69: 経営者・CEO・COO等, 70: CFO, 71: CIO, 72: 監査役, 73: 経営・CXOその他, 74: 経理, 75: 財務, 76: 法務, 77: 総務, 78: 労務, 79: 秘書, 80: 事務, 81: コーポレートその他, 82: 採用, 83: 人材開発・人材育成・研修, 84: 制度企画・組織開発, 85: 労務・給与, 86: 人事その他, 87: システムコンサルタント, 88: パッケージ導入コンサルタント, 89: セキュリティコンサルタント, 90: ネットワークコンサルタント, 91: ITコンサルタントその他, 92: 戦略コンサルタント, 93: DXコンサルタント, 94: 財務・会計コンサルタント, 95: 組織・人事コンサルタント, 96: 業務プロセスコンサルタント, 97: 物流コンサルタント, 98: リサーチャー・調査員, 99: コンサルタントその他, 100: その他)", 38 | ), 39 | }), 40 | ) 41 | .describe( 42 | "List of position type IDs - multiple selections are allowed. Please set relevant position types.", 43 | ), 44 | position_name: z.string().default("").describe("Position title"), 45 | is_client_work: z 46 | .boolean() 47 | .describe( 48 | "Whether this is client work (Set to true when the affiliated company and the project client are different, such as in contract development companies)", 49 | ), 50 | client_company_name: z 51 | .string() 52 | .optional() 53 | .describe("Client company name (required only when is_client_work is true)"), 54 | start_year: z.number().describe("Start year"), 55 | start_month: z.number().describe("Start month"), 56 | end_year: z.number().describe("End year (0 if ongoing)"), 57 | end_month: z.number().describe("End month (0 if ongoing)"), 58 | description: z 59 | .string() 60 | .default("") 61 | .describe("Detailed description of the experience (Markdown format)"), 62 | } as const; 63 | 64 | /** 65 | * Execute function 66 | */ 67 | async execute(args: InferZodParams): Promise<{ 68 | content: TextContent[]; 69 | isError?: boolean; 70 | }> { 71 | const apiKeyResult = validateApiKey(); 72 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 73 | 74 | const { experience_id, ...updateData } = args; 75 | 76 | try { 77 | const response = await fetch(new URL(`${BASE_URL}/experiences/${experience_id}`), { 78 | method: "PUT", 79 | headers: { 80 | accept: "application/json, text/plain, */*", 81 | "Content-Type": "application/json", 82 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 83 | }, 84 | body: JSON.stringify({ 85 | ...updateData, 86 | description: unescapeText(updateData.description), 87 | }), 88 | }); 89 | 90 | if (!response.ok) { 91 | throw new Error(`API request failed with status: ${response.status}`); 92 | } 93 | 94 | const data = await response.json(); 95 | return { 96 | content: [{ type: "text", text: JSON.stringify(data, null, 2) }], 97 | }; 98 | } catch (error) { 99 | console.error(error); 100 | return createErrorResponse(error, "職歴の更新に失敗しました"); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/tools/updateJobSummary.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import { unescapeText } from "../helpers/textFormatter.js"; 7 | import { validateApiKey } from "../helpers/validateApiKey.js"; 8 | import type { IMCPTool, InferZodParams } from "../types.js"; 9 | 10 | /** 11 | * 職務要約更新ツール 12 | */ 13 | export class UpdateJobSummaryTool implements IMCPTool { 14 | /** 15 | * Tool name 16 | */ 17 | readonly name = "update_job_summary"; 18 | 19 | /** 20 | * Tool description 21 | */ 22 | readonly description = 23 | "Update job summary(職務要約) on LAPRAS(https://lapras.com). You can check the result at https://lapras.com/cv"; 24 | 25 | /** 26 | * Parameter definition 27 | */ 28 | readonly parameters = { 29 | job_summary: z.string().max(10000).describe("Job summary(職務要約)"), 30 | } as const; 31 | 32 | /** 33 | * Execute function 34 | */ 35 | async execute(args: InferZodParams): Promise<{ 36 | content: TextContent[]; 37 | isError?: boolean; 38 | }> { 39 | const apiKeyResult = validateApiKey(); 40 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 41 | 42 | try { 43 | const response = await fetch(new URL(`${BASE_URL}/job_summary`), { 44 | method: "PUT", 45 | headers: { 46 | accept: "application/json, text/plain, */*", 47 | "Content-Type": "application/json", 48 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 49 | }, 50 | body: JSON.stringify({ 51 | job_summary: unescapeText(args.job_summary), 52 | }), 53 | }); 54 | 55 | if (!response.ok) { 56 | throw new Error(`API request failed with status: ${response.status}`); 57 | } 58 | 59 | const data = await response.json(); 60 | return { 61 | content: [{ type: "text", text: JSON.stringify(data, null, 2) }], 62 | }; 63 | } catch (error) { 64 | console.error(error); 65 | return createErrorResponse(error, "職務要約の更新に失敗しました"); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/tools/updateWantToDo.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import fetch from "node-fetch"; 3 | import { z } from "zod"; 4 | import { BASE_URL } from "../constants.js"; 5 | import { createErrorResponse } from "../helpers/createErrorResponse.js"; 6 | import { unescapeText } from "../helpers/textFormatter.js"; 7 | import { validateApiKey } from "../helpers/validateApiKey.js"; 8 | import type { IMCPTool, InferZodParams } from "../types.js"; 9 | 10 | /** 11 | * 今後のキャリアでやりたいこと更新ツール 12 | */ 13 | export class UpdateWantToDoTool implements IMCPTool { 14 | /** 15 | * Tool name 16 | */ 17 | readonly name = "update_want_to_do"; 18 | 19 | /** 20 | * Tool description 21 | */ 22 | readonly description = 23 | "Update career aspirations(今後のキャリアでやりたいこと) on LAPRAS(https://lapras.com). You can check the result at https://lapras.com/cv"; 24 | 25 | /** 26 | * Parameter definition 27 | */ 28 | readonly parameters = { 29 | want_to_do: z.string().max(1000).describe("Career aspirations(今後のキャリアでやりたいこと)"), 30 | } as const; 31 | 32 | /** 33 | * Execute function 34 | */ 35 | async execute(args: InferZodParams): Promise<{ 36 | content: TextContent[]; 37 | isError?: boolean; 38 | }> { 39 | const apiKeyResult = validateApiKey(); 40 | if (apiKeyResult.isInvalid) return apiKeyResult.errorResopnse; 41 | 42 | try { 43 | const response = await fetch(new URL(`${BASE_URL}/want_to_do`), { 44 | method: "PUT", 45 | headers: { 46 | accept: "application/json, text/plain, */*", 47 | "Content-Type": "application/json", 48 | Authorization: `Bearer ${apiKeyResult.apiKey}`, 49 | }, 50 | body: JSON.stringify({ 51 | want_to_do: unescapeText(args.want_to_do), 52 | }), 53 | }); 54 | 55 | if (!response.ok) { 56 | throw new Error(`API request failed with status: ${response.status}`); 57 | } 58 | 59 | const data = await response.json(); 60 | return { 61 | content: [{ type: "text", text: JSON.stringify(data, null, 2) }], 62 | }; 63 | } catch (error) { 64 | console.error(error); 65 | return createErrorResponse(error, "今後のキャリアでやりたいことの更新に失敗しました"); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 2 | import type { z } from "zod"; 3 | 4 | /** 5 | * Zodスキーマから型を抽出するユーティリティ型 6 | */ 7 | export type InferZodParams> = { 8 | [K in keyof T]: z.infer; 9 | }; 10 | 11 | export interface IMCPTool = Record> { 12 | /** 13 | * ツール名 14 | */ 15 | readonly name: string; 16 | 17 | /** 18 | * ツールの説明 19 | */ 20 | readonly description: string; 21 | 22 | /** 23 | * パラメータの定義 24 | */ 25 | readonly parameters: TParams; 26 | 27 | /** 28 | * ツールを実行する 29 | * @param args パラメータ 30 | * @returns 実行結果 31 | */ 32 | execute(args: InferZodParams): Promise<{ 33 | content: TextContent[]; 34 | isError?: boolean; 35 | }>; 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "outDir": "./dist", 12 | "rootDir": "./src" 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules"] 16 | } -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: "node", 7 | include: ["src/**/*.{test,spec}.{js,ts}"], 8 | }, 9 | resolve: { 10 | extensions: [".ts", ".js"], 11 | }, 12 | }); 13 | --------------------------------------------------------------------------------