diff --git a/.changeset/2025-11-03T12-16-06-872Z-6b5d17af.md b/.changeset/2025-11-03T12-16-06-872Z-6b5d17af.md new file mode 100644 index 0000000..1759e83 --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-872Z-6b5d17af.md @@ -0,0 +1,5 @@ +--- +"@chkp/cpinfo-analysis-mcp": minor +--- + +Updated implementation and features, Updated configuration, Added 15 new file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-873Z-286c8ed1.md b/.changeset/2025-11-03T12-16-06-873Z-286c8ed1.md new file mode 100644 index 0000000..dd25b3d --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-873Z-286c8ed1.md @@ -0,0 +1,5 @@ +--- +"@chkp/quantum-gw-connection-analysis-mcp": minor +--- + +Updated implementation and features, Modified 1 file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-873Z-374c8b9d.md b/.changeset/2025-11-03T12-16-06-873Z-374c8b9d.md new file mode 100644 index 0000000..568bfff --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-873Z-374c8b9d.md @@ -0,0 +1,5 @@ +--- +"@chkp/https-inspection-mcp": minor +--- + +Updated implementation and features, Modified 1 file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-873Z-c74d8c40.md b/.changeset/2025-11-03T12-16-06-873Z-c74d8c40.md new file mode 100644 index 0000000..ecf9e64 --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-873Z-c74d8c40.md @@ -0,0 +1,5 @@ +--- +"@chkp/documentation-mcp": patch +--- + +Updated configuration, Modified 1 file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-874Z-c7b40c96.md b/.changeset/2025-11-03T12-16-06-874Z-c7b40c96.md new file mode 100644 index 0000000..5b69df5 --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-874Z-c7b40c96.md @@ -0,0 +1,5 @@ +--- +"@chkp/quantum-management-mcp": minor +--- + +Updated implementation and features, Modified 1 file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-874Z-eaf960ed.md b/.changeset/2025-11-03T12-16-06-874Z-eaf960ed.md new file mode 100644 index 0000000..58605c5 --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-874Z-eaf960ed.md @@ -0,0 +1,5 @@ +--- +"@chkp/reputation-service-mcp": minor +--- + +Updated implementation and features, Modified 1 file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-874Z-f29c7023.md b/.changeset/2025-11-03T12-16-06-874Z-f29c7023.md new file mode 100644 index 0000000..40f01fe --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-874Z-f29c7023.md @@ -0,0 +1,5 @@ +--- +"@chkp/management-logs-mcp": minor +--- + +Updated implementation and features, Modified 1 file(s) (synced from internal repository) diff --git a/.changeset/2025-11-03T12-16-06-874Z-f6206a56.md b/.changeset/2025-11-03T12-16-06-874Z-f6206a56.md new file mode 100644 index 0000000..d33352e --- /dev/null +++ b/.changeset/2025-11-03T12-16-06-874Z-f6206a56.md @@ -0,0 +1,5 @@ +--- +"@chkp/threat-prevention-mcp": minor +--- + +Updated implementation and features, Modified 1 file(s) (synced from internal repository) diff --git a/README.md b/README.md index 873106a..4b7f4a0 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ This monorepo is organized with each Check Point security domain as a separate M - **`/gaia`** - GAIA OS MCP server for network management and interface configuration - **`/documentation-tool`** - Check Point documentation assistant MCP server for product information and technical guidance - **`/spark-management`** - Spark Management MCP server for Quantum Spark appliance management + - **`/cpinfo-analysis`** - CPInfo Analysis MCP server for Check Point appliance diagnostic file analysis and troubleshooting ## Available MCP Servers @@ -52,6 +53,7 @@ The following MCP servers are available in this repository: | [Gaia](./packages/gaia/) | `@chkp/quantum-gaia-mcp` | Network management and interface configuration for GAIA OS | | [Documentation Tool](./packages/documentation-tool/) | `@chkp/documentation-mcp` | Comprehensive Check Point documentation assistant providing instant access to product information, technical specifications, configuration guidance, and feature documentation across the entire Check Point security portfolio | | [Spark Management](./packages/spark-management/) | `@chkp/spark-management-mcp` | Manage and monitor Quantum Spark appliances for MSPs and distributed networks | +| [Cpinfo analysis](./packages/cpinfo-analysis/) | `@chkp/cpinfo-analysis-mcp` | Analyze CPInfo diagnostic files for system health, performance metrics, network configuration, and troubleshooting insights | ## Example: Setting Up an MCP Server diff --git a/docs/assets/demo-videos/cve-investigation.png b/docs/assets/demo-videos/cve-investigation.png new file mode 100644 index 0000000..133428b Binary files /dev/null and b/docs/assets/demo-videos/cve-investigation.png differ diff --git a/docs/assets/demo-videos/gw-dashboard.png b/docs/assets/demo-videos/gw-dashboard.png new file mode 100644 index 0000000..85b937e Binary files /dev/null and b/docs/assets/demo-videos/gw-dashboard.png differ diff --git a/docs/assets/demo-videos/mcp-drop-analysis.png b/docs/assets/demo-videos/mcp-drop-analysis.png new file mode 100644 index 0000000..37ffd39 Binary files /dev/null and b/docs/assets/demo-videos/mcp-drop-analysis.png differ diff --git a/docs/assets/demo-videos/preemtive-actions.png b/docs/assets/demo-videos/preemtive-actions.png new file mode 100644 index 0000000..ed25e82 Binary files /dev/null and b/docs/assets/demo-videos/preemtive-actions.png differ diff --git a/docs/scripts/main.js b/docs/scripts/main.js index 09856a4..a92548b 100644 --- a/docs/scripts/main.js +++ b/docs/scripts/main.js @@ -1033,10 +1033,12 @@ function createVideoCard(video) { const videoThumbnail = document.createElement('div'); videoThumbnail.className = 'video-thumbnail'; + + // Get thumbnail filename (replace .mp4 with .png) + const thumbnailFilename = video.filename.replace('.mp4', '.png'); + videoThumbnail.innerHTML = ` - + ${video.title}
diff --git a/docs/styles/main.css b/docs/styles/main.css index 3b4e05c..55f267e 100644 --- a/docs/styles/main.css +++ b/docs/styles/main.css @@ -913,6 +913,9 @@ html { backdrop-filter: blur(5px); z-index: 10000; overflow-y: auto; + align-items: center; + justify-content: center; + padding: var(--spacing-lg); } .modal.active { @@ -1417,7 +1420,8 @@ html { justify-content: center; } -.video-thumbnail video { +.video-thumbnail video, +.video-thumbnail-img { width: 100%; height: 100%; object-fit: cover; diff --git a/package-lock.json b/package-lock.json index 0f61dce..b935242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,20 @@ "node": ">=18.0.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@anthropic-ai/mcpb": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@anthropic-ai/mcpb/-/mcpb-1.1.1.tgz", @@ -2280,6 +2294,10 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/@chkp/cpinfo-analysis-mcp": { + "resolved": "packages/cpinfo-analysis", + "link": true + }, "node_modules/@chkp/documentation-mcp": { "resolved": "packages/documentation-tool", "link": true @@ -5416,6 +5434,314 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sinclair/typebox": { "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", @@ -5557,6 +5883,13 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", @@ -6460,22 +6793,153 @@ "win32" ] }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/@yarnpkg/parsers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", - "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", + "node_modules/@vitest/coverage-v8": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", + "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "js-yaml": "^3.10.0", - "tslib": "^2.4.0" + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.6.1" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" }, "engines": { "node": ">=18.12.0" @@ -6702,6 +7166,16 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -7168,6 +7642,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -7238,6 +7722,35 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7272,6 +7785,19 @@ "dev": true, "license": "MIT" }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -7476,6 +8002,13 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -8393,6 +8926,19 @@ } } }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -9146,6 +9692,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -9973,6 +10529,16 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -14437,6 +15003,23 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -14495,6 +15078,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -14505,6 +15098,28 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -14697,6 +15312,26 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -14723,6 +15358,25 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -15330,6 +15984,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15392,6 +16063,54 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15902,6 +16621,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -16234,6 +16995,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -16274,6 +17042,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -16326,6 +17104,13 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -16335,6 +17120,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -16489,6 +17281,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16629,6 +17441,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -16659,6 +17478,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", @@ -16952,6 +17791,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -16980,192 +17826,902 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=4" + "node": ">=16.17.0" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "path-key": "^4.0.0" }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "mimic-fn": "^4.0.0" }, - "bin": { - "update-browserslist-db": "cli.js" + "engines": { + "node": ">=12" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "node": ">=12" }, - "engines": { - "node": ">=10.12.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/walker": { @@ -17219,6 +18775,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -17466,9 +19039,42 @@ "zod": "^3.24.1" } }, + "packages/cpinfo-analysis": { + "name": "@chkp/cpinfo-analysis-mcp", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.11.4", + "axios": "^1.12.1", + "commander": "^11.0.0", + "zod": "^3.24.4" + }, + "bin": { + "cpinfo-analysis-mcp": "dist/index.js" + }, + "devDependencies": { + "@chkp/mcp-utils": "*", + "@types/node": "^18.15.11", + "@vitest/coverage-v8": "^1.0.0", + "typescript": "^5.0.4", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/cpinfo-analysis/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "packages/documentation-tool": { "name": "@chkp/documentation-mcp", - "version": "0.1.4", + "version": "0.1.5", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", @@ -17638,7 +19244,7 @@ }, "packages/gw-cli-connection-analysis": { "name": "@chkp/quantum-gw-connection-analysis-mcp", - "version": "0.4.4", + "version": "0.4.5", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", @@ -18107,7 +19713,7 @@ }, "packages/https-inspection": { "name": "@chkp/https-inspection-mcp", - "version": "0.7.4", + "version": "0.7.5", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", @@ -18170,7 +19776,7 @@ }, "packages/management": { "name": "@chkp/quantum-management-mcp", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", @@ -18200,7 +19806,7 @@ }, "packages/management-logs": { "name": "@chkp/management-logs-mcp", - "version": "0.6.4", + "version": "0.6.6", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", @@ -18251,7 +19857,7 @@ }, "packages/reputation-service": { "name": "@chkp/reputation-service-mcp", - "version": "0.2.4", + "version": "0.2.5", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", @@ -18339,7 +19945,7 @@ }, "packages/threat-prevention": { "name": "@chkp/threat-prevention-mcp", - "version": "0.7.4", + "version": "0.7.5", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.1", diff --git a/packages/cpinfo-analysis/README.md b/packages/cpinfo-analysis/README.md new file mode 100644 index 0000000..ab61ab0 --- /dev/null +++ b/packages/cpinfo-analysis/README.md @@ -0,0 +1,379 @@ +# Check Point CPInfo Analysis MCP Server + +## What is MCP? + +Model Context Protocol (MCP) servers expose a structured, machine-readable API for your enterprise data—designed for AI-powered automation, copilots, and decision engines. By delivering a clear, contextual slice of your security environment, MCP lets you query, analyze, and optimize complex systems without building custom SDKs or parsing raw exports. + +## Why MCP for CPInfo Analysis? + +CPInfo files contain thousands of lines of diagnostic data from Check Point appliances, including system configurations, performance metrics, security events, network topology, and troubleshooting information. Manually parsing and analyzing these files is time-consuming and error-prone. + +This MCP server changes that: it exposes CPInfo data in a structured, semantic format ready for AI analysis. Query system health, identify performance bottlenecks, trace configuration issues, and get actionable insights—instantly. + +## Use with other MCPs for Best Results + +While the CPInfo Analysis server works independently, it is designed to complement other Check Point MCP servers (such as Management, Gateway, and HTTPS Inspection) for comprehensive security environment analysis and troubleshooting. + +## Features + +- **Semantic Analysis**: Automatically categorizes CPInfo sections by type (system, performance, security, network, etc.) +- **Intelligent Search**: Search across sections with keyword matching and content filtering +- **System Information Extraction**: Extract key system details, hardware info, and configurations +- **Performance Metrics**: Analyze CPU, memory, disk usage, and performance trends +- **Network Analysis**: Review network interfaces, routing, VPN tunnels, and connectivity +- **Security Insights**: Examine security policies, threat prevention status, and IPS configurations +- **Licensing Information**: Check license status, expiration dates, and contract details +- **Core Dump Analysis**: Identify and analyze crash dumps and system failures +- **Log File Parsing**: Search and extract relevant log entries +- **Configuration Review**: Inspect appliance configurations and settings + + +## Example Use Cases + +### System Health Check +"Analyze this CPInfo file and provide a comprehensive overview of the system's health, including CPU usage, memory status, and any critical warnings." +*→ Returns a structured summary of system metrics, identifies anomalies, and highlights areas requiring attention.* + +### Performance Troubleshooting +"The appliance is running slow. What performance issues can you find in this CPInfo?" +*→ Examines performance sections, identifies bottlenecks (high CPU/memory usage, disk I/O issues), and suggests optimization steps.* + +### Network Connectivity Issues +"Check why VPN tunnels are dropping. Show me the network configuration and tunnel status." +*→ Extracts network interface details, VPN configurations, tunnel states, and identifies connectivity problems.* + +### Security Policy Verification +"What security policies are active? Are there any security features disabled or misconfigured?" +*→ Reviews security policies, threat prevention blade status, IPS configurations, and highlights policy gaps.* + +### License Expiration Check +"When does my license expire? Show all license information." +*→ Extracts licensing details including expiration dates, contract information, and active features.* + +### Core Dump Analysis +"Are there any recent crashes? What caused them?" +*→ Identifies core dumps, analyzes crash patterns, and provides diagnostic information about system failures.* + +### Configuration Audit +"Compare my current configuration against best practices for R81.20." +*→ Reviews configuration files, identifies deviations from recommended settings, and suggests improvements.* + +### Log Analysis +"Show me all authentication failures in the last 24 hours." +*→ Searches log files for authentication events, extracts relevant entries, and identifies patterns.* + +--- + +### ⚠️ Performance Notice +Processing large CPInfo files (>100MB) may take several seconds for initial indexing. Subsequent queries use cached data for faster response times. +--- + +## Configuration Options + +This server operates locally and analyzes CPInfo files from your filesystem. No external API credentials are required. + +### File Path Configuration + +All tools accept `file_path` as an absolute path to the **uncompressed** CPInfo text export file. + +> **⚠️ IMPORTANT - File Format Requirement** +> +> The `file_path` parameter must point to an **uncompressed text file**, NOT a compressed archive. +> +> CPInfo files are commonly distributed as compressed archives (`.zip`, `.tgz`, `.tar.gz`). +> **This server does NOT automatically extract or decompress files.** +> +> ✅ **Correct**: `/path/to/cpinfo_output.txt` or `/path/to/cpinfo.info` +> ❌ **Wrong**: `/path/to/cpinfo.zip` or `/path/to/cpinfo.tgz` + +**How to extract compressed CPInfo archives:** + +```bash +# For .tgz or .tar.gz files +tar -xzf cpinfo_file.tgz + +# For .zip files +unzip cpinfo_file.zip + +# For .gz files (single file compression) +gunzip cpinfo_file.gz +``` + +After extraction, use the path to the **extracted text file**, not the archive file. + +Each request operates on a single file; submit additional file paths in separate calls. + +### Optional Environment Variables + +- `CPINFO_CACHE_TTL_MS`: Override the idle eviction window (default: 10,800,000 ms ≈ 3 hours). + +Cached readers are automatically evicted after the TTL elapses with no tool activity. A background timer enforces this even when clients are idle; evictions are logged at INFO level. + +--- + +## Client Configuration + +### Prerequisites + +Install Node.js 18+ from [nodejs.org](https://nodejs.org/en/download/) if required, then verify with `node -v`. + +### Supported Clients + +This server has been tested with Claude Desktop, Cursor, GitHub Copilot, and Windsurf clients. +It is expected to work with any MCP client that supports the Model Context Protocol. + +### Basic Configuration Example + +```json +{ + "mcpServers": { + "cpinfo-analysis": { + "command": "npx", + "args": ["@chkp/cpinfo-analysis-mcp"], + "env": { + "LOG_LEVEL": "info" + } + } + } +} +``` + +### Configuring the Claude Desktop App + +#### For macOS: + +```bash +# Create the config file if it doesn't exist +touch "$HOME/Library/Application Support/Claude/claude_desktop_config.json" + +# Open the config file in TextEdit +open -e "$HOME/Library/Application Support/Claude/claude_desktop_config.json" +``` + +#### For Windows: + +```cmd +code %APPDATA%\Claude\claude_desktop_config.json +``` + +Add the server configuration: + +```json +{ + "mcpServers": { + "cpinfo-analysis": { + "command": "npx", + "args": ["@chkp/cpinfo-analysis-mcp"], + "env": { + "LOG_LEVEL": "info" + } + } + } +} +``` + +### VSCode + +Enter VSCode settings and type "mcp" in the search bar. +You should see the option to edit the configuration file. +Add this configuration: + +```json +{ + ... + "mcp": { + "inputs": [], + "servers": { + "cpinfo-analysis": { + "command": "npx", + "args": [ + "@chkp/cpinfo-analysis-mcp" + ], + "env": { + "LOG_LEVEL": "info" + } + } + } + }, + ... +} +``` + +### Other Clients + +- **Windsurf / Cursor / GitHub Copilot** – All expose an “MCP Servers” settings page. Paste the same JSON block used for Claude Desktop, adjusting paths if you installed globally. + +--- + +## Available Tools + +This MCP server provides the following analysis tools: + +### Initialization & Overview +- `check_initialization_status` – Check initialization status of a cpinfo file. By default, ensures the file is initialized (builds caches if needed). Use `initialize=false` to poll status without triggering processing. +- `analyze_cpinfo_overview` – Produce a high-level semantic overview once the file is ready. + +### Section Browsing +- `browse_sections_by_category` – List sections for a specific semantic category with pagination. + +### System Information +- `extract_system_details` – Surface key system and hardware metadata captured in the CPInfo file. + +### Performance Analysis +- `analyze_performance_metrics` – Summarize CPU, memory, disk, and related performance signals. + +### Network Analysis +- `extract_network_config` – Inspect network interfaces, routing tables, VPN context, and related sections. + +### Security & Compliance +- `audit_security_settings` – Summarize security configuration, user data, and blade status. + +### Licensing +- `extract_license_information` – Report license inventory, expirations, and blade coverage. + +### Diagnostics & Crash Analysis +- `detect_system_crashes` – Highlight core dumps and crash-related evidence. + +### Content Retrieval & Search +- `read_section_content` – Page through raw section content with line-based pagination instructions. +- `smart_content_search` – Keyword search across section names and (optionally) content. Returns line numbers for matching content. +- `manage_unknown_sections` – List, suggest, or reclassify sections that remain uncategorized. Available only when the + `CPINFO_ENABLE_DEBUG_TOOLS` environment variable is set to `true`. Supports actions `list`, `suggest`, + `reclassify`, and `bulk_reclassify` (the last one applies pattern-based mappings to re-type many sections at once). +- `comprehensive_health_analysis` – Combine multiple caches into a focused health summary (system/performance/licensing/security). + +#### Search Result Format & Navigation + +**Important**: The `smart_content_search` tool returns **line numbers only** (e.g., `[line 1951]`) without page numbers. This ensures accuracy regardless of the `page_size` you specify when reading content. + +**To navigate to search results:** + +1. Note the line number from search results (e.g., `[line 1951]`) +2. Choose your preferred `page_size` (default is 30 lines per page) +3. Calculate the page number: `page = Math.floor((line - 1) / page_size) + 1` +4. Use `read_section_content` with the calculated page and your chosen `page_size` + +**Example:** +``` +Search finds: [line 1951] FULLSYNC Running +Your page_size: 50 + +Calculate page: Math.floor((1951 - 1) / 50) + 1 = 40 + +Read content: + file_path: "cpinfo.txt" + section_name: "log_section" + page: 40 + page_size: 50 +``` + +This approach gives you full control over page size while ensuring accurate navigation to search results. + +--- + +## Development + +### Prerequisites + +- Node.js 18+ +- npm 10+ + +### Setup + +```bash +# Clone the repository +git clone [repository-url] +cd mcp-servers-internal + +# Install all dependencies +npm install + +# Build all packages +npm run build +``` + +### Build + +```bash +# Build just the cpinfo-analysis package +npx nx build @chkp/cpinfo-analysis-mcp + +# Or build all packages +npm run build +``` + +### Running Locally + +```bash +# From the repository root +node packages/cpinfo-analysis/dist/index.js + +# Or use the npm script +cd packages/cpinfo-analysis +npm start +``` + +### Testing + +```bash +# Run all tests +npm test + +# Run tests in watch mode (for development) +npm run test:watch + +# Run tests with coverage +npm run test:coverage +``` + +The test suite includes: +- **50 comprehensive tests** validating file parsing, indexing, caching, and search functionality +- **Small test fixtures** included in the repository for portable testing +- **Pagination bug tests** ensuring accurate line number reporting in search results +- Tests run successfully on any machine without requiring large external CPInfo files + +For more details, see [TESTING.md](./TESTING.md) and [tests/README.md](./tests/README.md). + +--- + +## Troubleshooting + +### CPInfo File Not Found +Ensure the CPInfo file path you pass into a tool is absolute and the file exists: +```bash +ls -la /absolute/path/to/cpinfo.info +``` + +### Initialization Takes Too Long +Large CPInfo files (>100 MB) may take several seconds to index the first time. Subsequent tool calls reuse the cached data. If you need to force a rebuild, delete the process-level cache by restarting the MCP server. + +### Memory Issues +For extremely large CPInfo archives you can raise Node.js memory limits by wrapping the command: +```json +{ + "mcpServers": { + "cpinfo-analysis": { + "command": "node", + "args": [ + "--max-old-space-size=4096", + "node_modules/@chkp/cpinfo-analysis-mcp/dist/index.js" + ] + } + } +} +``` + +### Cache Timeout +Idle caches are evicted automatically after the configured TTL (`CPINFO_CACHE_TTL_MS`). Eviction events are logged, so check the log file if a file unexpectedly re-initializes on the next request. + +--- + +## Support + +For issues, questions, or contributions: +- GitHub Issues: [https://github.com/CheckPointSW/mcp-servers/issues](https://github.com/CheckPointSW/mcp-servers/issues) +- Documentation: [https://checkpointsw.github.io/mcp-servers/](https://checkpointsw.github.io/mcp-servers/) + +## License + +MIT License - see [LICENSE](../../LICENSE) file for details. diff --git a/packages/cpinfo-analysis/package.json b/packages/cpinfo-analysis/package.json new file mode 100644 index 0000000..8a132f5 --- /dev/null +++ b/packages/cpinfo-analysis/package.json @@ -0,0 +1,44 @@ +{ + "name": "@chkp/cpinfo-analysis-mcp", + "version": "0.0.1", + "description": "CPInfo analysis MCP server", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "cpinfo-analysis-mcp": "./dist/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/CheckPointSW/mcp-servers.git", + "directory": "packages/cpinfo-analysis" + }, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "clean": "rimraf dist", + "build": "npm run build:bundle && npx shx chmod +x ./dist/index.js && npx shx cp src/server-config.json dist/", + "build:bundle": "node ../../bundle-mcp.js", + "build:tsc": "npx tsc && npx shx chmod +x ./dist/index.js && npx shx cp src/server-config.json dist/", + "dev": "tsc --watch", + "start": "node dist/index.js", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "axios": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.11.4", + "commander": "^11.0.0", + "zod": "^3.24.4" + }, + "devDependencies": { + "@chkp/mcp-utils": "*", + "@types/node": "^18.15.11", + "@vitest/coverage-v8": "^1.0.0", + "typescript": "^5.0.4", + "vitest": "^1.0.0" + } +} diff --git a/packages/cpinfo-analysis/src/api-manager.ts b/packages/cpinfo-analysis/src/api-manager.ts new file mode 100644 index 0000000..b2821fa --- /dev/null +++ b/packages/cpinfo-analysis/src/api-manager.ts @@ -0,0 +1,18 @@ +export class CpInfoAPIManager { + constructor(private readonly settings: { defaultEncoding: string }) {} + + static create(settings: any): CpInfoAPIManager { + return new CpInfoAPIManager({ defaultEncoding: settings.defaultEncoding || "utf-8" }); + } + + async callApi(method: string, path: string, data: Record): Promise> { + return { + success: true, + method, + path, + data, + timestamp: new Date().toISOString(), + encoding: this.settings.defaultEncoding + }; + } +} diff --git a/packages/cpinfo-analysis/src/cpinfo-exceptions.ts b/packages/cpinfo-analysis/src/cpinfo-exceptions.ts new file mode 100644 index 0000000..0d0b99c --- /dev/null +++ b/packages/cpinfo-analysis/src/cpinfo-exceptions.ts @@ -0,0 +1,9 @@ +export class CpInfoError extends Error {} + +export class CpInfoIOError extends CpInfoError {} + +export class CpInfoIndexError extends CpInfoError {} + +export class CpInfoParseError extends CpInfoError {} + +export class CpInfoEncodingError extends CpInfoError {} diff --git a/packages/cpinfo-analysis/src/cpinfo-index-constants.ts b/packages/cpinfo-analysis/src/cpinfo-index-constants.ts new file mode 100644 index 0000000..b321555 --- /dev/null +++ b/packages/cpinfo-analysis/src/cpinfo-index-constants.ts @@ -0,0 +1,354 @@ +import { SectionType } from "./types.js"; + +export interface PatternConfig { + name: string; + regex: RegExp; +} + +export const CPINFO_PATTERNS: PatternConfig[] = [ + { name: "main_section", regex: new RegExp("^={46}\\s*\\n([^\\n=]+?)\\s*\\n={46}", "gm") }, + { name: "file_section", regex: new RegExp("^-{66}\\s*\\n([^\\n]+?)\\s*\\n-{66}", "gm") }, + { name: "sub_section_short", regex: new RegExp("^-{24}\\s*\\n([^\\n]+?)\\s*\\n-{24}", "gm") }, + { name: "sub_section_medium", regex: new RegExp("^-{23}\\s*\\n([^\\n]+?)\\s*\\n-{23}", "gm") } +]; + +export const PATTERN_PRIORITY: Record = { + main_section: 1, + file_section: 2, + sub_section_short: 3, + sub_section_medium: 3 +}; + +export const SECTION_TYPE_MAPPING: { pattern: string; type: SectionType }[] = [ + { pattern: "enterprise system overview", type: SectionType.SYSTEM_INFO }, + { pattern: "global system configuration", type: SectionType.SYSTEM_INFO }, + { pattern: "system info", type: SectionType.SYSTEM_INFO }, + { pattern: "general info", type: SectionType.SYSTEM_INFO }, + { pattern: "cp components", type: SectionType.MONITORING }, + { pattern: "cp status", type: SectionType.MONITORING }, + { pattern: "cpstat", type: SectionType.MONITORING }, + { pattern: "identityserver", type: SectionType.MONITORING }, + { pattern: "antimalware", type: SectionType.MONITORING }, + { pattern: "threat-emulation", type: SectionType.MONITORING }, + { pattern: "https_inspection", type: SectionType.MONITORING }, + { pattern: "ips status", type: SectionType.MONITORING }, + { pattern: "product keys", type: SectionType.MONITORING }, + { pattern: "hostid", type: SectionType.MONITORING }, + { pattern: "uname -a", type: SectionType.SYSTEM_INFO }, + { pattern: "os data", type: SectionType.SYSTEM_INFO }, + { pattern: "version", type: SectionType.SYSTEM_INFO }, + { pattern: "hostname", type: SectionType.SYSTEM_INFO }, + { pattern: "platform", type: SectionType.SYSTEM_INFO }, + { pattern: "hardware", type: SectionType.SYSTEM_INFO }, + { pattern: "operating system", type: SectionType.SYSTEM_INFO }, + { pattern: "df -", type: SectionType.SYSTEM_INFO }, + { pattern: "du -sh", type: SectionType.SYSTEM_INFO }, + { pattern: "mount listing", type: SectionType.SYSTEM_INFO }, + { pattern: "package manager report", type: SectionType.SYSTEM_INFO }, + { pattern: "smartctl", type: SectionType.SYSTEM_INFO }, + { pattern: "network infrastructure", type: SectionType.NETWORK }, + { pattern: "network", type: SectionType.NETWORK }, + { pattern: "interface", type: SectionType.NETWORK }, + { pattern: "routing", type: SectionType.NETWORK }, + { pattern: "bond", type: SectionType.NETWORK }, + { pattern: "bridge", type: SectionType.NETWORK }, + { pattern: "vlan", type: SectionType.NETWORK }, + { pattern: "ethernet", type: SectionType.NETWORK }, + { pattern: "vpn", type: SectionType.VPN }, + { pattern: "ipsec", type: SectionType.VPN }, + { pattern: "tunnel", type: SectionType.VPN }, + { pattern: "site-to-site", type: SectionType.VPN }, + { pattern: "remote access", type: SectionType.VPN }, + { pattern: "mobile access", type: SectionType.VPN }, + { pattern: "ssl vpn", type: SectionType.VPN }, + { pattern: "encryption domain", type: SectionType.VPN }, + { pattern: "firewall", type: SectionType.FIREWALL }, + { pattern: "rule base", type: SectionType.FIREWALL }, + { pattern: "policy", type: SectionType.FIREWALL }, + { pattern: "access control", type: SectionType.FIREWALL }, + { pattern: "nat", type: SectionType.FIREWALL }, + { pattern: "rules", type: SectionType.FIREWALL }, + { pattern: "connection", type: SectionType.FIREWALL }, + { pattern: "fw1", type: SectionType.FIREWALL }, + { pattern: "virtual systems", type: SectionType.CONFIGURATION }, + { pattern: "blade configuration", type: SectionType.CONFIGURATION }, + { pattern: "security configuration", type: SectionType.SECURITY }, + { pattern: "config", type: SectionType.CONFIGURATION }, + { pattern: "settings", type: SectionType.CONFIGURATION }, + { pattern: "system performance monitoring", type: SectionType.PERFORMANCE }, + { pattern: "performance", type: SectionType.PERFORMANCE }, + { pattern: "cpu", type: SectionType.PERFORMANCE }, + { pattern: "memory", type: SectionType.PERFORMANCE }, + { pattern: "storage", type: SectionType.PERFORMANCE }, + { pattern: "disk", type: SectionType.PERFORMANCE }, + { pattern: "load", type: SectionType.PERFORMANCE }, + { pattern: "utilization", type: SectionType.PERFORMANCE }, + { pattern: "monitoring", type: SectionType.MONITORING }, + { pattern: "alert", type: SectionType.MONITORING }, + { pattern: "threshold", type: SectionType.MONITORING }, + { pattern: "snmp", type: SectionType.MONITORING }, + { pattern: "syslog", type: SectionType.MONITORING }, + { pattern: "notification", type: SectionType.MONITORING }, + { pattern: "process", type: SectionType.PROCESSES }, + { pattern: "daemon", type: SectionType.PROCESSES }, + { pattern: "service", type: SectionType.PROCESSES }, + { pattern: "pid", type: SectionType.PROCESSES }, + { pattern: "task", type: SectionType.PROCESSES }, + { pattern: "ps aux", type: SectionType.PROCESSES }, + { pattern: "ps axf", type: SectionType.PROCESSES }, + { pattern: "ps auxww", type: SectionType.PROCESSES }, + { pattern: "lsof", type: SectionType.PROCESSES }, + { pattern: "logged-in users", type: SectionType.PROCESSES }, + { pattern: "env -", type: SectionType.PROCESSES }, + { pattern: "usr/bin/ps", type: SectionType.PROCESSES }, + { pattern: "database", type: SectionType.DATABASE }, + { pattern: "sql", type: SectionType.DATABASE }, + { pattern: "db", type: SectionType.DATABASE }, + { pattern: "table", type: SectionType.DATABASE }, + { pattern: "schema", type: SectionType.DATABASE }, + { pattern: "log analysis", type: SectionType.LOG_FILES }, + { pattern: "comprehensive log analysis", type: SectionType.LOG_FILES }, + { pattern: "/var/log", type: SectionType.LOG_FILES }, + { pattern: "/opt/cp", type: SectionType.LOG_FILES }, + { pattern: "messages", type: SectionType.LOG_FILES }, + { pattern: "syslog", type: SectionType.LOG_FILES }, + { pattern: "compliance", type: SectionType.SECURITY }, + { pattern: "audit", type: SectionType.SECURITY }, + { pattern: "threat intelligence", type: SectionType.SECURITY }, + { pattern: "user", type: SectionType.SECURITY }, + { pattern: "authentication", type: SectionType.SECURITY }, + { pattern: "authorization", type: SectionType.SECURITY }, + { pattern: "certificate", type: SectionType.SECURITY }, + { pattern: "cphaprob", type: SectionType.FIREWALL }, + { pattern: "syncstat", type: SectionType.FIREWALL }, + { pattern: "ldstat", type: SectionType.FIREWALL }, + { pattern: "fcustat", type: SectionType.FIREWALL }, + { pattern: "show_failover", type: SectionType.FIREWALL }, + { pattern: "ccp", type: SectionType.FIREWALL }, + { pattern: "high availability", type: SectionType.FIREWALL }, + { pattern: "dlpcmd", type: SectionType.FIREWALL }, + { pattern: "floodgate", type: SectionType.FIREWALL }, + { pattern: "cpmonitor", type: SectionType.CONFIGURATION }, + { pattern: "vsx", type: SectionType.CONFIGURATION }, + { pattern: "virtual system", type: SectionType.CONFIGURATION }, + { pattern: "vrfs", type: SectionType.CONFIGURATION }, + { pattern: "ctx", type: SectionType.CONFIGURATION }, + { pattern: "directory listing", type: SectionType.CONFIGURATION }, + { pattern: "file attributes", type: SectionType.CONFIGURATION }, + { pattern: "checksum report", type: SectionType.FIREWALL }, + { pattern: "modules", type: SectionType.FIREWALL }, + { pattern: "java parameters", type: SectionType.CONFIGURATION }, + { pattern: "compatibility pkgs", type: SectionType.CONFIGURATION }, + { pattern: "azure connectivity", type: SectionType.NETWORK }, + { pattern: "risk docker image", type: SectionType.MONITORING }, + { pattern: "risk docker container", type: SectionType.MONITORING }, + { pattern: "cpview - skyline", type: SectionType.MONITORING }, + { pattern: "cpshared", type: SectionType.SYSTEM_INFO }, + { pattern: "provider-1", type: SectionType.CONFIGURATION }, + { pattern: "date", type: SectionType.MONITORING }, + { pattern: "softnet status", type: SectionType.PERFORMANCE }, + { pattern: "fw ctl multik stat", type: SectionType.FIREWALL }, + { pattern: "fw affinity", type: SectionType.FIREWALL }, + { pattern: "fw-1 accelerator", type: SectionType.FIREWALL }, + { pattern: "sim affinity", type: SectionType.FIREWALL }, + { pattern: "kiss kernel", type: SectionType.FIREWALL }, + { pattern: "aesni support", type: SectionType.FIREWALL }, + { pattern: "failopen status", type: SectionType.FIREWALL }, + { pattern: "ipsctl -a", type: SectionType.FIREWALL }, + { pattern: "vs 0", type: SectionType.FIREWALL }, + { pattern: "vs 1", type: SectionType.FIREWALL }, + { pattern: "vs 2", type: SectionType.FIREWALL }, + { pattern: "vs 3", type: SectionType.FIREWALL }, + { pattern: "affinity of multi-queue", type: SectionType.FIREWALL }, + { pattern: "number of active rx queues", type: SectionType.FIREWALL }, + { pattern: "sxlmode -s", type: SectionType.FIREWALL }, + { pattern: "pdp m groups", type: SectionType.SECURITY }, + { pattern: "list pci devices", type: SectionType.PERFORMANCE }, + { pattern: "lspci -v", type: SectionType.PERFORMANCE }, + { pattern: "lspci -vv", type: SectionType.PERFORMANCE }, + { pattern: "lspci -nv", type: SectionType.PERFORMANCE }, + { pattern: "lspci", type: SectionType.PERFORMANCE }, + { pattern: "brctl show", type: SectionType.NETWORK }, + { pattern: "vrf all ifaces", type: SectionType.CONFIGURATION }, + { pattern: "cpwd", type: SectionType.MONITORING }, + { pattern: "uptime", type: SectionType.MONITORING }, + { pattern: "fw ", type: SectionType.FIREWALL }, + { pattern: "env", type: SectionType.PROCESSES }, + { pattern: "final system summary", type: SectionType.SYSTEM_INFO } +]; + +export const SPECIAL_SECTION_OVERRIDES: Array<{ regex: RegExp; type: SectionType }> = [ + { regex: /^\/etc\/resolv\.conf$/i, type: SectionType.NETWORK }, + { regex: /^\/etc\/hosts$/i, type: SectionType.NETWORK }, + { regex: /^\/etc\/routed\d?\.conf$/i, type: SectionType.NETWORK }, + { regex: /^\/etc\/ppp\//i, type: SectionType.NETWORK }, + { regex: /^\/web\/conf\/httpd2\.conf$/i, type: SectionType.VPN }, + { regex: /^\/web\/conf\/extra\/httpd-ssl\.conf$/i, type: SectionType.VPN }, + { regex: /^\/opt\/postfix\/etc\/postfix\/master\.cf$/i, type: SectionType.DIAGNOSTICS }, + { regex: /^\/opt\/postfix\/etc\/postfix\/main\.cf$/i, type: SectionType.DIAGNOSTICS }, + { regex: /^\/etc\/asset\.xml$/i, type: SectionType.SECURITY }, + { regex: /^\/etc\/features\.xml$/i, type: SectionType.SECURITY }, + { regex: /^\/etc\/hidden_group_names$/i, type: SectionType.SECURITY }, + { regex: /^\/etc\/fw\.boot\/ha_boot\.conf$/i, type: SectionType.FIREWALL }, + { regex: /^\/etc\/ppk\.boot\/bin\/sxlmode\s+-s$/i, type: SectionType.FIREWALL }, + { regex: /^\/usr\/bin\/sshd_template_xlate$/i, type: SectionType.SECURITY }, + { regex: /^\/bin\/dmesg$/i, type: SectionType.DIAGNOSTICS }, + { regex: /^\/etc\/modprobe\.conf$/i, type: SectionType.CONFIGURATION } +]; + +export const EXTENSION_TYPE_MAPPING: Array<{ regex: RegExp; type: SectionType }> = [ + { regex: /\.elg(\b|$)/, type: SectionType.LOG_FILES }, + { regex: /\.log(\b|$)/, type: SectionType.LOG_FILES }, + { regex: /\.conf(\b|$)/, type: SectionType.CONFIGURATION }, + { regex: /\.cf(\b|$)/, type: SectionType.CONFIGURATION }, + { regex: /\.xml(\b|$)/, type: SectionType.CONFIGURATION }, + { regex: /\.rec(\b|$)/, type: SectionType.CONFIGURATION } +]; + +export const KEYWORD_PATTERNS: Record = { + [SectionType.PERFORMANCE]: [ + "cpu", + "memory", + "performance", + "monitoring", + "storage", + "load", + "utilization", + "disk", + "smartctl", + "accelerator", + "multi-queue", + "lspci" + ], + [SectionType.NETWORK]: [ + "network", + "interface", + "routing", + "bond", + "bridge", + "vlan", + "ethernet", + "ip route", + "ip -6 route", + "ip link", + "ip addr", + "ip rule", + "ip maddr", + "ip mroute", + "ip neighbor", + "netstat", + "arp -an", + "softnet", + "fw ctl iflist", + "fw getifs", + "fw ctl multik", + "fw affinity", + "/proc/net/", + "snmp6", + "tcp6", + "udp6", + "fib_trie", + "igmp", + "if_inet6", + "azure connectivity", + "anycast6", + "curl_cli", + "nslookup" + ], + [SectionType.VPN]: ["vpn", "ipsec", "tunnel", "site-to-site", "remote access", "ssl vpn"], + [SectionType.FIREWALL]: ["firewall", "rule base", "policy", "access control", "nat", "rules", "fw1"], + [SectionType.SECURITY]: [ + "security", + "compliance", + "audit", + "threat", + "user", + "auth", + "certificate", + "pdp m groups", + "sshd_template_xlate" + ], + [SectionType.PROCESSES]: [ + "process", + "daemon", + "service", + "pid", + "task", + "ps ", + "lsof", + "w -u", + "environment", + "grep sdwan", + "grep cp-nano", + "usr/bin/ps" + ], + [SectionType.DATABASE]: ["database", "sql", "db", "table", "schema"], + [SectionType.MONITORING]: [ + "monitoring", + "alert", + "threshold", + "snmp", + "notification", + "vmstat", + "top -b", + "sar", + "cpstat", + "cpview", + "/proc/stat", + "risk docker", + "cp components", + "cp status", + "uptime", + "date" + ], + [SectionType.LOG_FILES]: ["log", "audit", "messages", "var/log", "syslog", ".elg"], + [SectionType.SYSTEM_INFO]: [ + "system", + "info", + "general", + "version", + "overview", + "summary", + "hostname", + "platform", + "hardware", + "lsdev", + "sysenv", + "smt status", + "os assets", + "rpm", + "smartctl", + "df -", + "du -sh", + "mount listing", + "lsiutil" + ], + [SectionType.CORE_DUMPS]: ["core", "dump", "crash", "segfault", "abort"], + [SectionType.LICENSING]: ["licensing", "license", "entitlement", "blade", "contract"], + [SectionType.COMMAND_OUTPUT]: ["command", "output", "result", "execute", "run"], + [SectionType.DIAGNOSTICS]: [ + "diagnostic", + "debug", + "trace", + "error", + "warning", + "dmesg", + "postfix", + "pdmesg" + ], + [SectionType.CONFIGURATION]: [ + "config", + "policy", + "settings", + "blade", + "virtual", + "/etc/", + "/conf/", + "directory listing", + "java parameters", + "compatibility pkgs", + "snmpd.conf" + ], + [SectionType.UNKNOWN]: [] +}; + diff --git a/packages/cpinfo-analysis/src/cpinfo-index.ts b/packages/cpinfo-analysis/src/cpinfo-index.ts new file mode 100644 index 0000000..262d261 --- /dev/null +++ b/packages/cpinfo-analysis/src/cpinfo-index.ts @@ -0,0 +1,552 @@ +import { promises as fs } from "fs"; +import { performance } from "perf_hooks"; +import { SectionInfo, SectionType } from "./types.js"; +import { CpInfoIndexError } from "./cpinfo-exceptions.js"; +import { createLogger } from "./logger.js"; +import { + CPINFO_PATTERNS, + EXTENSION_TYPE_MAPPING, + KEYWORD_PATTERNS, + PATTERN_PRIORITY, + SECTION_TYPE_MAPPING, + SPECIAL_SECTION_OVERRIDES +} from "./cpinfo-index-constants.js"; + +const logger = createLogger("cpinfo-index"); + +export interface BuildIndexOptions { + encoding?: BufferEncoding; + chunkSize?: number; +} + +export class CpInfoAdvancedIndex { + public filePath?: string; + public fileSize = 0; + public encoding: BufferEncoding = "utf-8"; + private built = false; + private sections = new Map(); + private sectionsByType = new Map(); + private allSections: SectionInfo[] = []; + private stats = { + totalSections: 0, + sectionsByType: new Map(), + processingTime: 0 + }; + + constructor() { + this.initializeTypeMaps(); + } + + get isBuilt(): boolean { + return this.built; + } + + getStats(): Record { + return { + totalSections: this.stats.totalSections, + processingTime: this.stats.processingTime, + sectionsByType: Object.fromEntries(Array.from(this.stats.sectionsByType.entries()).map(([key, value]) => [key, value])) + }; + } + + getAllSections(): SectionInfo[] { + return [...this.allSections]; + } + + getSectionsByType(type: SectionType): SectionInfo[] { + return [...(this.sectionsByType.get(type) ?? [])]; + } + + getUnknownSections(): SectionInfo[] { + return this.getSectionsByType(SectionType.UNKNOWN); + } + + findSectionsContaining(keyword: string, caseSensitive = false): SectionInfo[] { + const term = caseSensitive ? keyword : keyword.toLowerCase(); + return this.allSections.filter((section) => { + const name = caseSensitive ? section.name : section.name.toLowerCase(); + return name.includes(term); + }); + } + + searchSections(query: string, sectionType?: SectionType, page = 1, pageSize = 20) { + const term = query.toLowerCase(); + const filtered = this.allSections.filter((section) => { + if (sectionType && section.sectionType !== sectionType) { + return false; + } + return section.name.toLowerCase().includes(term); + }); + + const totalCount = filtered.length; + const startIdx = (page - 1) * pageSize; + const endIdx = startIdx + pageSize; + const pageSections = filtered.slice(startIdx, endIdx); + + return { + query, + sections: pageSections.map((section) => ({ + id: section.startOffset, + name: section.name, + type: section.sectionType, + patternType: String(section.metadata?.pattern_type ?? "unknown"), + size: (section.endOffset ?? section.startOffset) - section.startOffset, + startOffset: section.startOffset, + endOffset: section.endOffset + })), + pagination: { + page, + pageSize, + totalCount, + totalPages: Math.max(1, Math.ceil(totalCount / pageSize)), + hasNext: endIdx < totalCount, + hasPrev: page > 1 + } + }; + } + + getSemanticCategories(): Record { + const result: Record = {} as Record; + Object.values(SectionType).forEach((type) => { + result[type] = this.getSectionsByType(type).map((section) => section.name); + }); + return result; + } + + getCategorizationSuggestions(section: SectionInfo) { + const suggestions: SectionType[] = []; + const confidence: Record = {}; + const patternMatches: string[] = []; + const lowerName = section.name.toLowerCase(); + + SECTION_TYPE_MAPPING.forEach(({ pattern, type }) => { + if (lowerName.includes(pattern)) { + suggestions.push(type); + confidence[type] = (confidence[type] ?? 0) + 2; + patternMatches.push(pattern); + } + }); + + Object.entries(KEYWORD_PATTERNS).forEach(([typeKey, keywords]) => { + keywords.forEach((keyword) => { + if (lowerName.includes(keyword)) { + const type = typeKey as SectionType; + suggestions.push(type); + confidence[type] = (confidence[type] ?? 0) + 1; + patternMatches.push(keyword); + } + }); + }); + + const uniqueSuggestions = Array.from(new Set(suggestions)); + + return { + section: section.name, + current_type: section.sectionType, + suggestions: uniqueSuggestions.map((type) => ({ + type, + display_name: type, + confidence: confidence[type] ?? 1 + })), + pattern_matches: Array.from(new Set(patternMatches)) + }; + } + + async buildIndex(filePath: string, options: BuildIndexOptions = {}): Promise { + const start = performance.now(); + let handle: fs.FileHandle | undefined; + + try { + this.filePath = filePath; + this.encoding = options.encoding ?? "utf-8"; + const chunkSize = options.chunkSize ?? 2 * 1024 * 1024; + + handle = await fs.open(filePath, "r"); + const stats = await handle.stat(); + this.fileSize = stats.size; + + logger.info(`Building clean cpinfo index for ${this.fileSize.toLocaleString()} byte file`); + + let bytesProcessed = 0; + let bufferRemainder = Buffer.alloc(0); + this.sections.clear(); + this.allSections = []; + this.initializeTypeMaps(); + + const buffer = Buffer.alloc(chunkSize); + while (true) { + const { bytesRead } = await handle.read(buffer, 0, chunkSize, bytesProcessed); + if (bytesRead === 0) { + break; + } + + const chunk = Buffer.concat([bufferRemainder, buffer.subarray(0, bytesRead)]); + const baseOffset = bytesProcessed - bufferRemainder.length; + const newSections = this.findSectionsInChunk(chunk, baseOffset); + this.updateSectionBoundaries(this.allSections, newSections); + this.allSections.push(...newSections); + + const bufferSize = Math.max(512, Math.min(Math.floor(chunk.length / 2), Math.floor(chunkSize / 2))); + bufferRemainder = chunk.length > bufferSize ? chunk.subarray(chunk.length - bufferSize) : Buffer.alloc(0); + bytesProcessed += bytesRead; + + // Log progress every 20MB + if (bytesProcessed % (20 * 1024 * 1024) < chunkSize) { + const progressPercent = ((bytesProcessed / this.fileSize) * 100).toFixed(1); + logger.info(`Processed ${bytesProcessed.toLocaleString()} bytes (${progressPercent}% complete, ${this.allSections.length} sections found)`); + } + } + + this.finalizeSections(); + + this.stats.totalSections = this.allSections.length; + this.stats.processingTime = (performance.now() - start) / 1000; + this.built = true; + + logger.info(`Clean index built: ${this.allSections.length} sections in ${this.stats.processingTime.toFixed(2)}s`); + } catch (error) { + throw new CpInfoIndexError(`Failed to build index: ${(error as Error).message}`); + } finally { + if (handle) { + try { + await handle.close(); + } catch (closeError) { + logger.error(`Failed to close file handle: ${(closeError as Error).message}`); + } + } + } + } + + reclassifySection(sectionName: string, newSectionType: SectionType): boolean { + const target = this.allSections.find((section) => section.name === sectionName); + if (!target) { + logger.warning(`Section '${sectionName}' not found for reclassification`); + return false; + } + + const oldType = target.sectionType; + if (oldType === newSectionType) { + return true; + } + + logger.info(`Reclassified '${sectionName}' from ${oldType} to ${newSectionType}`); + + const oldList = this.sectionsByType.get(oldType) ?? []; + this.sectionsByType.set(oldType, oldList.filter((section) => section !== target)); + + target.sectionType = newSectionType; + target.metadata = { + ...target.metadata, + reclassified: true, + original_type: oldType, + reclassification_reason: "manual" + }; + + const newList = this.sectionsByType.get(newSectionType) ?? []; + newList.push(target); + this.sectionsByType.set(newSectionType, newList); + + this.stats.sectionsByType.set(oldType, Math.max(0, (this.stats.sectionsByType.get(oldType) ?? 1) - 1)); + this.stats.sectionsByType.set(newSectionType, (this.stats.sectionsByType.get(newSectionType) ?? 0) + 1); + + const key = `${target.startOffset}:${target.name}`; + this.sections.set(key, target); + return true; + } + + bulkReclassifyUnknown(patternMappings: Record) { + const unknownSections = this.getUnknownSections(); + const results = { + total_unknown: unknownSections.length, + reclassified: 0, + failed: 0, + details: [] as Array> + }; + + const lowerMappings = Object.entries(patternMappings).map(([pattern, type]) => [pattern.toLowerCase(), type] as const); + + for (const section of unknownSections) { + const nameLower = section.name.toLowerCase(); + let reclassified = false; + + for (const [pattern, typeName] of lowerMappings) { + if (!nameLower.includes(pattern)) { + continue; + } + + const typeKey = typeName.toUpperCase() as keyof typeof SectionType; + const newType = SectionType[typeKey]; + + if (!newType) { + results.failed += 1; + results.details.push({ + section: section.name, + pattern, + new_type: typeName, + success: false, + error: `Invalid section type: ${typeName}` + }); + continue; + } + + try { + if (this.reclassifySection(section.name, newType)) { + results.reclassified += 1; + results.details.push({ + section: section.name, + pattern, + new_type: typeName, + success: true + }); + reclassified = true; + break; + } + } catch (error) { + results.failed += 1; + results.details.push({ + section: section.name, + pattern, + new_type: typeName, + success: false, + error: (error as Error).message + }); + } + } + + if (!reclassified) { + const suggestions = this.getCategorizationSuggestions(section); + const top = suggestions.suggestions?.[0]; + if (top && top.confidence >= 2) { + try { + const newType = SectionType[top.type.toUpperCase() as keyof typeof SectionType]; + if (newType && this.reclassifySection(section.name, newType)) { + results.reclassified += 1; + results.details.push({ + section: section.name, + pattern: "auto-suggestion", + new_type: top.type, + success: true, + confidence: top.confidence + }); + } + } catch { + // ignore + } + } + } + } + + return results; + } + + private initializeTypeMaps(): void { + this.sectionsByType = new Map(); + this.stats.sectionsByType = new Map(); + Object.values(SectionType).forEach((type) => { + this.sectionsByType.set(type, []); + this.stats.sectionsByType.set(type, 0); + }); + } + + private findSectionsInChunk(chunk: Buffer, baseOffset: number): SectionInfo[] { + const chunkString = chunk.toString("latin1"); + const matchedOffsets = new Set(); + const found: SectionInfo[] = []; + + CPINFO_PATTERNS.forEach(({ name, regex }) => { + regex.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = regex.exec(chunkString)) !== null) { + const matchOffset = match.index; + if (matchedOffsets.has(matchOffset)) { + continue; + } + + const rawName = match[1] ?? ""; + const sectionName = Buffer.from(rawName, "latin1").toString(this.encoding).trim(); + if (!sectionName) { + continue; + } + + matchedOffsets.add(matchOffset); + const absoluteOffset = baseOffset + matchOffset; + const sectionType = this.determineSectionType(sectionName); + + const section: SectionInfo = { + name: sectionName, + sectionType, + startOffset: absoluteOffset, + metadata: { pattern_type: name } + }; + + found.push(section); + } + }); + + return found.sort((a, b) => a.startOffset - b.startOffset); + } + + private determineSectionType(name: string): SectionType { + const lower = name.toLowerCase(); + + for (const { regex, type } of SPECIAL_SECTION_OVERRIDES) { + if (regex.test(name)) { + return type; + } + } + + for (const { regex, type } of EXTENSION_TYPE_MAPPING) { + if (regex.test(lower)) { + return type; + } + } + + if (lower.includes("/proc/net")) { + return SectionType.NETWORK; + } + + if (lower.includes("/proc/stat")) { + return SectionType.MONITORING; + } + + const direct = SECTION_TYPE_MAPPING.find(({ pattern }) => lower.includes(pattern)); + if (direct) { + return direct.type; + } + + for (const [typeKey, keywords] of Object.entries(KEYWORD_PATTERNS)) { + if (keywords.some((keyword) => lower.includes(keyword))) { + return typeKey as SectionType; + } + } + + return SectionType.UNKNOWN; + } + + private updateSectionBoundaries(existing: SectionInfo[], newSections: SectionInfo[]): void { + if (!newSections.length) { + return; + } + + const sortedNew = [...newSections].sort((a, b) => a.startOffset - b.startOffset); + + sortedNew.forEach((newSection) => { + const newPriority = PATTERN_PRIORITY[newSection.metadata?.pattern_type as string] ?? 0; + + existing.forEach((section) => { + if (section.endOffset !== undefined) { + return; + } + if (section.startOffset >= newSection.startOffset) { + return; + } + + const sectionPriority = PATTERN_PRIORITY[section.metadata?.pattern_type as string] ?? 0; + let shouldClose = false; + if (sectionPriority === 1) { + // FIX: Allow subsections (priority 3) to close main sections + // This prevents main sections from absorbing subsection content + shouldClose = newPriority <= 3; + } else if (sectionPriority === 2) { + shouldClose = newPriority <= 2; + } else { + shouldClose = true; + } + + if (shouldClose) { + section.endOffset = newSection.startOffset; + section.metadata.boundary_method = "hierarchical_close"; + } + }); + }); + } + + private finalizeSections(): void { + const unique = new Map(); + this.allSections.forEach((section) => { + const key = `${section.startOffset}:${section.name}`; + if (!unique.has(key)) { + unique.set(key, section); + } + }); + + this.allSections = Array.from(unique.values()).sort((a, b) => a.startOffset - b.startOffset); + + logger.info(`Finalized ${this.allSections.length} sections (before filtering empty sections)`); + + for (let i = 0; i < this.allSections.length; i += 1) { + const section = this.allSections[i]; + if (section.endOffset === undefined) { + let nextSection: SectionInfo | undefined; + for (let j = i + 1; j < this.allSections.length; j += 1) { + const candidate = this.allSections[j]; + const sectionPriority = PATTERN_PRIORITY[section.metadata?.pattern_type as string] ?? 2; + const candidatePriority = PATTERN_PRIORITY[candidate.metadata?.pattern_type as string] ?? 2; + + let shouldClose = false; + if (sectionPriority === 1) { + // FIX: Allow subsections (priority 3) to close main sections + // This prevents main sections from absorbing subsection content + shouldClose = candidatePriority <= 3; + } else if (sectionPriority === 2) { + shouldClose = candidatePriority <= 2; + } else { + shouldClose = true; + } + + if (shouldClose) { + nextSection = candidate; + break; + } + } + + if (nextSection) { + section.endOffset = nextSection.startOffset; + section.metadata.boundary_method = "hierarchical_chunk_close"; + } else { + section.endOffset = this.fileSize; + section.metadata.boundary_method = "file_end"; + } + } + + const key = `${section.startOffset}:${section.name}`; + this.sections.set(key, section); + } + + // Filter out empty sections (sections with very little content, likely just headers) + const MIN_SECTION_SIZE = 100; // Minimum bytes for a section to be considered non-empty + const beforeFilter = this.allSections.length; + this.allSections = this.allSections.filter((section) => { + const size = (section.endOffset ?? section.startOffset) - section.startOffset; + if (size < MIN_SECTION_SIZE) { + logger.debug(`Filtering out empty section: ${section.name} (${size} bytes)`); + return false; + } + return true; + }); + + if (beforeFilter !== this.allSections.length) { + logger.info(`Filtered out ${beforeFilter - this.allSections.length} empty sections`); + } + + this.rebuildTypeIndexes(); + + // Log section summary + logger.info("Clean section summary by type:"); + this.sectionsByType.forEach((sections, type) => { + if (sections.length > 0) { + logger.info(` ${type}: ${sections.length} sections`); + } + }); + } + + private rebuildTypeIndexes(): void { + this.initializeTypeMaps(); + this.allSections.forEach((section) => { + const list = this.sectionsByType.get(section.sectionType) ?? []; + list.push(section); + this.sectionsByType.set(section.sectionType, list); + this.stats.sectionsByType.set(section.sectionType, (this.stats.sectionsByType.get(section.sectionType) ?? 0) + 1); + }); + } +} diff --git a/packages/cpinfo-analysis/src/cpinfo-reader.ts b/packages/cpinfo-analysis/src/cpinfo-reader.ts new file mode 100644 index 0000000..681d3a5 --- /dev/null +++ b/packages/cpinfo-analysis/src/cpinfo-reader.ts @@ -0,0 +1,137 @@ +import { promises as fs } from "fs"; +import { CpInfoAdvancedIndex, BuildIndexOptions } from "./cpinfo-index.js"; +import { + CpInfoError, + CpInfoIOError, + CpInfoEncodingError, + CpInfoIndexError +} from "./cpinfo-exceptions.js"; +import { SectionType, SectionInfo } from "./types.js"; +import { createLogger } from "./logger.js"; + +const logger = createLogger("cpinfo-reader"); + +export interface ReaderOptions { + buildIndex?: boolean; + encoding?: BufferEncoding; +} + +export class CpInfoReader { + public filePath?: string; + public encoding: BufferEncoding; + public buildIndexOnLoad: boolean; + private fileHandle?: fs.FileHandle; + private fileSize = 0; + private index?: CpInfoAdvancedIndex; + private indexBuilt = false; + + constructor(options: ReaderOptions = {}) { + this.encoding = options.encoding ?? "utf-8"; + this.buildIndexOnLoad = options.buildIndex ?? true; + } + + async loadFile(filePath: string, options: BuildIndexOptions = {}): Promise { + logger.info(`Loading cpinfo file: ${filePath}`); + this.filePath = filePath; + await this.openFile(); + + if (this.buildIndexOnLoad) { + logger.info("Building index automatically"); + await this.buildIndex(options); + } + } + + async close(): Promise { + if (this.fileHandle) { + await this.fileHandle.close(); + this.fileHandle = undefined; + } + this.indexBuilt = false; + this.index = undefined; + } + + getIndex(): CpInfoAdvancedIndex { + if (!this.index) { + throw new CpInfoError("Index not available - reader was created with buildIndex=false"); + } + return this.index; + } + + get fileSizeBytes(): number { + return this.fileSize; + } + + get isIndexBuilt(): boolean { + return this.indexBuilt; + } + + async readSectionByOffset(offset: number, size: number): Promise { + if (!this.fileHandle) { + await this.openFile(); + } + + if (!this.fileHandle) { + throw new CpInfoIOError("File handle is not available"); + } + + try { + const buffer = Buffer.alloc(size); + const { bytesRead } = await this.fileHandle.read(buffer, 0, size, offset); + logger.debug(`Read ${bytesRead} bytes from offset ${offset}`); + return buffer.subarray(0, bytesRead).toString(this.encoding); + } catch (error) { + logger.error(`Error reading section at offset ${offset}`, error as Error); + throw new CpInfoIOError(`Error reading section at offset ${offset}: ${(error as Error).message}`); + } + } + + async getSectionsByType(type: SectionType): Promise { + const index = this.getIndex(); + return index.getSectionsByType(type); + } + + async findSectionsContaining(keyword: string, caseSensitive = false): Promise { + const index = this.getIndex(); + return index.findSectionsContaining(keyword, caseSensitive); + } + + async getSemanticCategories(): Promise> { + return this.getIndex().getSemanticCategories(); + } + + async buildIndex(options: BuildIndexOptions = {}): Promise { + if (!this.filePath) { + throw new CpInfoIOError("File path not set"); + } + + logger.info(`Building index for ${this.filePath}`); + + if (!this.index) { + this.index = new CpInfoAdvancedIndex(); + } + + await this.index.buildIndex(this.filePath, { + encoding: options.encoding ?? this.encoding, + chunkSize: options.chunkSize + }); + + this.indexBuilt = true; + logger.info(`Index built with ${this.index.getAllSections().length} sections`); + } + + private async openFile(): Promise { + if (!this.filePath) { + throw new CpInfoIOError("File path not provided"); + } + + try { + this.fileHandle = await fs.open(this.filePath, "r"); + const stats = await this.fileHandle.stat(); + this.fileSize = stats.size; + logger.info(`Opened cpinfo file: ${this.fileSize.toLocaleString()} bytes`); + } catch (error) { + logger.error(`Failed to open file: ${this.filePath}`, error as Error); + throw new CpInfoIOError(`Failed to open file: ${(error as Error).message}`); + } + } +} diff --git a/packages/cpinfo-analysis/src/cpinfo-service.ts b/packages/cpinfo-analysis/src/cpinfo-service.ts new file mode 100644 index 0000000..3d42a48 --- /dev/null +++ b/packages/cpinfo-analysis/src/cpinfo-service.ts @@ -0,0 +1,557 @@ +import { performance } from "perf_hooks"; +import { setTimeout as delay } from "timers/promises"; +import { CpInfoReader } from "./cpinfo-reader.js"; +import { + BasicCache, + CoreDumpCache, + FileProcessingCache, + InitializationStatus, + LicensingCache, + NetworkCache, + PerformanceCache, + ProcessingStatus, + SectionType, + SecurityCache, + SemanticSummary +} from "./types.js"; +import { createLogger } from "./logger.js"; + +const logger = createLogger("cpinfo-service"); + +const DEFAULT_CACHE_TTL_MS = 3 * 60 * 60 * 1000; // 3 hours +const EVICTION_CHECK_INTERVAL_MS = 60 * 1000; // 1 minute +const PROCESSING_WAIT_ATTEMPTS = 30; + +interface CacheMetadata { + lastAccess: number; +} + +export class CpInfoService { + private readerCache = new Map>(); + private processingCache = new Map(); + private statusCache = new Map(); + private metadata = new Map(); + private readonly cacheTtlMs: number; + private evictionPromise: Promise | null = null; + private lastEvictionCheck = 0; + private evictionTimer: NodeJS.Timeout | null = null; + + constructor() { + const envValue = process.env.CPINFO_CACHE_TTL_MS; + const hasEnv = typeof envValue !== "undefined"; + const parsed = hasEnv ? Number(envValue) : NaN; + const isValid = !Number.isNaN(parsed) && parsed > 0; + + this.cacheTtlMs = isValid ? parsed : DEFAULT_CACHE_TTL_MS; + + if (hasEnv && isValid) { + logger.info(`Configured cpinfo cache TTL: ${this.cacheTtlMs}ms`); + } else if (hasEnv && !isValid) { + logger.warning(`Invalid CPINFO_CACHE_TTL_MS value "${envValue}" — falling back to default ${this.cacheTtlMs}ms`); + } else { + logger.debug(`Using default cpinfo cache TTL of ${this.cacheTtlMs}ms`); + } + + this.startEvictionTimer(); + } + + async getReader(filePath: string): Promise { + await this.maybeEvictStaleCaches(); + if (!this.readerCache.has(filePath)) { + const reader = new CpInfoReader(); + const promise = reader + .loadFile(filePath) + .then(() => reader) + .catch((error) => { + this.readerCache.delete(filePath); + throw error; + }); + this.readerCache.set(filePath, promise); + } + this.touch(filePath); + return this.readerCache.get(filePath)!; + } + + getInitializationStatus(filePath: string): InitializationStatus { + void this.maybeEvictStaleCaches(); + if (!this.statusCache.has(filePath)) { + this.statusCache.set(filePath, { + status: ProcessingStatus.NOT_STARTED, + progress: 0, + stage: "waiting", + current_activity: "Not started", + sections_processed: 0, + total_sections: 0 + }); + } + this.touch(filePath); + return this.statusCache.get(filePath)!; + } + + getProcessingSnapshot(filePath: string): FileProcessingCache { + return this.getProcessingCache(filePath); + } + + async ensureFileInitialized(filePath: string): Promise<{ reader: CpInfoReader; cache: FileProcessingCache }> { + await this.maybeEvictStaleCaches(); + const reader = await this.getReader(filePath); + const cache = this.getProcessingCache(filePath); + const status = this.getInitializationStatus(filePath); + + if (cache.initialized && status.status === ProcessingStatus.COMPLETE) { + logger.info(`Using cached data for ${filePath}`); + return { reader, cache }; + } + + if ( + status.status === ProcessingStatus.INDEXING || + status.status === ProcessingStatus.CATEGORIZING || + status.status === ProcessingStatus.ANALYZING + ) { + logger.info(`File ${filePath} is being processed by another request, waiting...`); + for (let i = 0; i < PROCESSING_WAIT_ATTEMPTS; i += 1) { + await delay(1000); + const current = this.getInitializationStatus(filePath); + if (current.status === ProcessingStatus.COMPLETE) { + logger.info(`Processing completed while waiting for ${filePath}`); + return { reader, cache: this.getProcessingCache(filePath) }; + } + } + logger.warning(`Timeout waiting for processing completion, starting own processing for ${filePath}`); + } + + logger.info(`Starting comprehensive initialization for ${filePath}`); + const processingStart = performance.now(); + this.updateStatus(filePath, ProcessingStatus.INDEXING, 10, "Building index", "Scanning file structure..."); + + if (!reader.isIndexBuilt) { + logger.info("Building file index..."); + await reader.buildIndex(); + } + + logger.info("Pre-categorizing sections..."); + this.updateStatus(filePath, ProcessingStatus.CATEGORIZING, 30, "Categorizing sections", "Analyzing section types..."); + + const sectionCounts: Record = {}; + let totalSections = 0; + for (const typeValue of Object.values(SectionType)) { + const sections = await reader.getSectionsByType(typeValue as SectionType); + sectionCounts[typeValue] = sections.length; + totalSections += sections.length; + } + + this.updateStatus( + filePath, + ProcessingStatus.ANALYZING, + 50, + "Pre-analyzing content", + `Processing ${totalSections} sections...`, + Math.floor(totalSections * 0.3), + totalSections + ); + + logger.info("Pre-caching system information..."); + cache.system_info_cache = await this.precacheSystemInfo(reader); + this.updateStatus( + filePath, + ProcessingStatus.ANALYZING, + 65, + "Pre-caching data", + "Caching performance data...", + Math.floor(totalSections * 0.4), + totalSections + ); + + logger.info("Pre-caching performance data..."); + cache.performance_cache = await this.precachePerformanceData(reader); + this.updateStatus( + filePath, + ProcessingStatus.ANALYZING, + 80, + "Pre-caching data", + "Caching licensing info...", + Math.floor(totalSections * 0.5), + totalSections + ); + + logger.info("Pre-caching licensing information..."); + cache.licensing_cache = await this.precacheLicensingInfo(reader); + this.updateStatus( + filePath, + ProcessingStatus.ANALYZING, + 90, + "Pre-caching data", + "Caching security settings...", + Math.floor(totalSections * 0.7), + totalSections + ); + + logger.info("Pre-caching security information..."); + cache.security_cache = await this.precacheSecurityInfo(reader); + logger.info("Pre-caching core dump analysis..."); + cache.core_dumps_cache = await this.precacheCoreDumps(reader); + logger.info("Pre-caching network information..."); + cache.network_cache = await this.precacheNetworkInfo(reader); + + cache.semantic_analysis = { + total_sections: totalSections, + section_types: sectionCounts, + file_size: reader.fileSizeBytes, + processing_time: (performance.now() - processingStart) / 1000, + categories: Object.fromEntries( + Object.entries(sectionCounts).filter(([, count]) => count > 0) + ) + }; + + cache.initialized = true; + cache.cache_timestamp = Date.now() / 1000; + + const totalTime = ((performance.now() - processingStart) / 1000).toFixed(2); + logger.info(`File initialization complete for ${filePath} in ${totalTime}s`); + + this.updateStatus( + filePath, + ProcessingStatus.COMPLETE, + 100, + "Complete", + `Initialized ${totalSections} sections in ${totalTime}s`, + totalSections, + totalSections + ); + + return { reader, cache }; + } + + async recomputeSemanticSummary(filePath: string): Promise { + const reader = await this.getReader(filePath); + const cache = this.getProcessingCache(filePath); + const sectionCounts: Record = {}; + let totalSections = 0; + for (const typeValue of Object.values(SectionType)) { + const sections = await reader.getSectionsByType(typeValue as SectionType); + sectionCounts[typeValue] = sections.length; + totalSections += sections.length; + } + + cache.semantic_analysis = { + total_sections: totalSections, + section_types: sectionCounts, + file_size: reader.fileSizeBytes, + processing_time: cache.semantic_analysis?.processing_time ?? 0, + categories: Object.fromEntries( + Object.entries(sectionCounts).filter(([, count]) => count > 0) + ) + }; + + return cache; + } + + private getProcessingCache(filePath: string): FileProcessingCache { + void this.maybeEvictStaleCaches(); + if (!this.processingCache.has(filePath)) { + this.processingCache.set(filePath, { + semantic_analysis: null, + system_info_cache: null, + performance_cache: null, + licensing_cache: null, + security_cache: null, + core_dumps_cache: null, + network_cache: null, + search_cache: new Map(), + section_content_cache: new Map(), + cross_analysis_cache: new Map(), + initialized: false, + cache_timestamp: null + }); + } + this.touch(filePath); + return this.processingCache.get(filePath)!; + } + + private updateStatus( + filePath: string, + status: ProcessingStatus, + progress: number, + stage: string, + activity: string, + sectionsProcessed = 0, + totalSections = 0 + ): void { + const statusEntry = this.getInitializationStatus(filePath); + const now = Date.now() / 1000; + statusEntry.status = status; + statusEntry.progress = progress; + statusEntry.stage = stage; + statusEntry.current_activity = activity; + statusEntry.sections_processed = sectionsProcessed; + statusEntry.total_sections = totalSections; + statusEntry.last_update = now; + + if (status === ProcessingStatus.INDEXING && !statusEntry.start_time) { + statusEntry.start_time = now; + } + + if (statusEntry.start_time && progress > 0) { + const elapsed = now - statusEntry.start_time; + const estimatedTotal = elapsed * (100 / progress); + statusEntry.estimated_completion = statusEntry.start_time + estimatedTotal; + } + } + + private touch(filePath: string): void { + this.metadata.set(filePath, { lastAccess: Date.now() }); + } + + private async maybeEvictStaleCaches(): Promise { + const now = Date.now(); + if (now - this.lastEvictionCheck < EVICTION_CHECK_INTERVAL_MS) { + return; + } + + this.lastEvictionCheck = now; + if (this.evictionPromise) { + await this.evictionPromise; + return; + } + + this.evictionPromise = this.evictStaleCaches(now).finally(() => { + this.evictionPromise = null; + }); + await this.evictionPromise; + } + + private async evictStaleCaches(referenceTime: number): Promise { + const cutoff = referenceTime - this.cacheTtlMs; + const staleEntries: string[] = []; + + for (const [filePath, data] of this.metadata.entries()) { + if (data.lastAccess < cutoff) { + staleEntries.push(filePath); + } + } + + if (!staleEntries.length) { + return; + } + + for (const filePath of staleEntries) { + const metadata = this.metadata.get(filePath); + this.metadata.delete(filePath); + + const readerPromise = this.readerCache.get(filePath); + this.readerCache.delete(filePath); + this.processingCache.delete(filePath); + this.statusCache.delete(filePath); + + if (readerPromise) { + try { + const reader = await readerPromise; + await reader.close(); + } catch (error) { + logger.warning(`Failed to close reader for ${filePath} during eviction: ${(error as Error).message}`); + } + } + + const idleMinutes = metadata ? Math.round((referenceTime - metadata.lastAccess) / 60000) : "unknown"; + logger.info(`Evicted cpinfo cache for ${filePath} after ${idleMinutes} minutes of inactivity`); + } + } + + private startEvictionTimer(): void { + if (this.evictionTimer) { + return; + } + this.evictionTimer = setInterval(() => { + void this.maybeEvictStaleCaches(); + }, EVICTION_CHECK_INTERVAL_MS).unref?.() ?? null; + logger.debug("Started cpinfo cache eviction timer"); + } + + dispose(): void { + if (this.evictionTimer) { + clearInterval(this.evictionTimer); + this.evictionTimer = null; + logger.debug("Stopped cpinfo cache eviction timer"); + } + } + + private async precacheSystemInfo(reader: CpInfoReader): Promise { + const sections = await reader.getSectionsByType(SectionType.SYSTEM_INFO); + const cache: BasicCache = { + sections_count: sections.length, + sections: [], + summary: {} + }; + + for (const section of sections.slice(0, 10)) { + try { + const size = section.endOffset ? section.endOffset - section.startOffset : 8192; + const content = await reader.readSectionByOffset(section.startOffset, Math.min(8192, size)); + cache.sections.push({ + name: section.name, + content, + offset: section.startOffset, + size + }); + } catch (error) { + logger.warning(`Failed to cache system section ${section.name}: ${(error as Error).message}`); + } + } + + return cache; + } + + private async precachePerformanceData(reader: CpInfoReader): Promise { + const sections = await reader.getSectionsByType(SectionType.PERFORMANCE); + const cache: PerformanceCache = { + sections_count: sections.length, + sections: [], + summary: {}, + has_cpu_spikes: false, + has_memory_issues: false + }; + + for (const section of sections.slice(0, 5)) { + try { + const size = section.endOffset ? section.endOffset - section.startOffset : 4000; + const content = await reader.readSectionByOffset(section.startOffset, Math.min(4000, size)); + cache.sections.push({ name: section.name, content, offset: section.startOffset }); + const lower = content.toLowerCase(); + if (lower.includes("cpu") && (lower.includes("spike") || lower.includes("high"))) { + cache.has_cpu_spikes = true; + } + if (lower.includes("memory") && (lower.includes("leak") || lower.includes("high"))) { + cache.has_memory_issues = true; + } + } catch { + // ignore + } + } + + return cache; + } + + private async precacheLicensingInfo(reader: CpInfoReader): Promise { + const sections = await reader.getSectionsByType(SectionType.LICENSING); + const cache: LicensingCache = { + sections_count: sections.length, + sections: [], + summary: {}, + license_tables: [], + has_expired_licenses: false + }; + + for (const section of sections) { + try { + const size = section.endOffset ? section.endOffset - section.startOffset : 8192; + const content = await reader.readSectionByOffset(section.startOffset, size); + cache.sections.push({ name: section.name, content, offset: section.startOffset }); + const lower = content.toLowerCase(); + if (content.includes("|") && lower.includes("blade")) { + cache.license_tables.push(section.name); + } + if (lower.includes("expir") || lower.includes("expired")) { + cache.has_expired_licenses = true; + } + } catch { + // ignore + } + } + + return cache; + } + + private async precacheSecurityInfo(reader: CpInfoReader): Promise { + const sections = await reader.getSectionsByType(SectionType.SECURITY); + const cache: SecurityCache = { + sections_count: sections.length, + sections: [], + summary: {}, + has_user_info: false, + has_permission_info: false + }; + + for (const section of sections.slice(0, 8)) { + try { + const size = section.endOffset ? section.endOffset - section.startOffset : 6000; + const content = await reader.readSectionByOffset(section.startOffset, Math.min(6000, size)); + cache.sections.push({ name: section.name, content, offset: section.startOffset }); + const lower = content.toLowerCase(); + if (lower.includes("user")) { + cache.has_user_info = true; + } + if (lower.includes("permission") || lower.includes("group")) { + cache.has_permission_info = true; + } + } catch { + // ignore + } + } + + return cache; + } + + private async precacheCoreDumps(reader: CpInfoReader): Promise { + const sections = await reader.getSectionsByType(SectionType.CORE_DUMPS); + const cache: CoreDumpCache = { + sections_count: sections.length, + sections: [], + summary: {}, + has_crashes: sections.length > 0, + crash_summary: [] + }; + + for (const section of sections) { + try { + const size = section.endOffset ? section.endOffset - section.startOffset : 4000; + const content = await reader.readSectionByOffset(section.startOffset, Math.min(4000, size)); + cache.sections.push({ name: section.name, content, offset: section.startOffset }); + const lines = content.split("\n"); + for (const line of lines.slice(0, 10)) { + const lower = line.toLowerCase(); + if (lower.includes("crash") || lower.includes("core") || lower.includes("signal") || lower.includes("segfault")) { + cache.crash_summary.push(line.trim()); + } + } + } catch { + // ignore + } + } + + return cache; + } + + private async precacheNetworkInfo(reader: CpInfoReader): Promise { + const sections = await reader.getSectionsByType(SectionType.NETWORK); + const cache: NetworkCache = { + sections_count: sections.length, + sections: [], + summary: {}, + interfaces: [], + config_issues: [] + }; + + for (const section of sections.slice(0, 10)) { + try { + const size = section.endOffset ? section.endOffset - section.startOffset : 6000; + const content = await reader.readSectionByOffset(section.startOffset, Math.min(6000, size)); + cache.sections.push({ name: section.name, content, offset: section.startOffset }); + const lines = content.split("\n").map((line) => line.trim()).filter(Boolean); + + for (const line of lines.slice(0, 30)) { + const lower = line.toLowerCase(); + if (lower.includes("interface") && lower.includes("status")) { + cache.interfaces.push({ name: line, status: line }); + } + if (lower.includes("down") || lower.includes("error") || lower.includes("fail")) { + cache.config_issues.push(line); + } + } + } catch { + // ignore + } + } + + return cache; + } +} diff --git a/packages/cpinfo-analysis/src/index.ts b/packages/cpinfo-analysis/src/index.ts new file mode 100644 index 0000000..415c501 --- /dev/null +++ b/packages/cpinfo-analysis/src/index.ts @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { + launchMCPServer, + createServerModule, + createApiRunner +} from "@chkp/mcp-utils"; +import { readFileSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; +import { CpInfoAPIManager } from "./api-manager.js"; +import { Settings } from "./settings.js"; +import { CpInfoService } from "./cpinfo-service.js"; +import { registerCpinfoTools } from "./tool-handlers.js"; +import { logger } from "./logger.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const pkg = JSON.parse( + readFileSync(join(__dirname, "../package.json"), "utf-8") +); + +// Log startup info +logger.info("CPInfo MCP Server starting up"); +logger.info(`Version: ${pkg.version}`); +logger.info(`Log file: ${logger.getLogFilePath()}`); +logger.info(`Working directory: ${process.cwd()}`); + +process.env.CP_MCP_MAIN_PKG = `${pkg.name} v${pkg.version}`; + +const server = new McpServer({ + name: "cpinfo-analysis", + description: "Semantic CPInfo analysis server", + version: pkg.version || "1.0.0" +}); + +const serverModule = createServerModule( + server, + Settings, + pkg, + CpInfoAPIManager +); + +const runApi = createApiRunner(serverModule); + +const service = new CpInfoService(); +const registeredTools = registerCpinfoTools(server, service); + +logger.info(`Registering ${registeredTools} tools`); + +const main = async () => { + logger.info("Launching MCP server..."); + await launchMCPServer( + join(__dirname, "server-config.json"), + serverModule + ); + logger.info("MCP server launched successfully"); +}; + +main().catch((error) => { + logger.error("Fatal error during startup", error); + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/packages/cpinfo-analysis/src/logger.ts b/packages/cpinfo-analysis/src/logger.ts new file mode 100644 index 0000000..6301948 --- /dev/null +++ b/packages/cpinfo-analysis/src/logger.ts @@ -0,0 +1,106 @@ +import { writeFileSync, appendFileSync, mkdirSync, existsSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARNING = 2, + ERROR = 3 +} + +class Logger { + private logFilePath?: string; + private logLevel: LogLevel = LogLevel.INFO; + private name: string; + + constructor(name: string) { + this.name = name; + this.initializeLogFile(); + } + + private initializeLogFile(): void { + try { + // Create logs directory if it doesn't exist + const logsDir = join(__dirname, "../logs"); + if (!existsSync(logsDir)) { + mkdirSync(logsDir, { recursive: true }); + } + + // Create log file with timestamp + const timestamp = new Date().toISOString().replace(/[:.]/g, "-").substring(0, 19); + this.logFilePath = join(logsDir, `cpinfo_server_${timestamp}.log`); + + // Write initial header + const header = `=== CPInfo Server Log Started at ${new Date().toISOString()} ===\n`; + writeFileSync(this.logFilePath, header, "utf-8"); + } catch (error) { + console.error("Failed to initialize log file:", error); + } + } + + private formatMessage(level: string, message: string): string { + const timestamp = new Date().toISOString(); + return `${timestamp} - ${this.name} - ${level} - ${message}`; + } + + private writeLog(level: string, message: string): void { + const formatted = this.formatMessage(level, message); + + // Always write to stderr (like Python's StreamHandler) + console.error(formatted); + + // Also write to file if available + if (this.logFilePath) { + try { + appendFileSync(this.logFilePath, formatted + "\n", "utf-8"); + } catch (error) { + console.error("Failed to write to log file:", error); + } + } + } + + setLevel(level: LogLevel): void { + this.logLevel = level; + } + + debug(message: string): void { + if (this.logLevel <= LogLevel.DEBUG) { + this.writeLog("DEBUG", message); + } + } + + info(message: string): void { + if (this.logLevel <= LogLevel.INFO) { + this.writeLog("INFO", message); + } + } + + warning(message: string): void { + if (this.logLevel <= LogLevel.WARNING) { + this.writeLog("WARNING", message); + } + } + + error(message: string, error?: Error): void { + if (this.logLevel <= LogLevel.ERROR) { + const errorDetails = error ? `\n${error.stack || error.message}` : ""; + this.writeLog("ERROR", message + errorDetails); + } + } + + getLogFilePath(): string | undefined { + return this.logFilePath; + } +} + +// Create default logger instances for each module +export function createLogger(name: string): Logger { + return new Logger(name); +} + +// Default logger for the package +export const logger = createLogger("cpinfo-analysis"); diff --git a/packages/cpinfo-analysis/src/server-config.json b/packages/cpinfo-analysis/src/server-config.json new file mode 100644 index 0000000..7bd07c6 --- /dev/null +++ b/packages/cpinfo-analysis/src/server-config.json @@ -0,0 +1,5 @@ +{ + "name": "cpinfo-analysis", + "description": "Advanced CPInfo analysis MCP server providing semantic insights", + "options": [] +} diff --git a/packages/cpinfo-analysis/src/settings.ts b/packages/cpinfo-analysis/src/settings.ts new file mode 100644 index 0000000..f57650a --- /dev/null +++ b/packages/cpinfo-analysis/src/settings.ts @@ -0,0 +1,20 @@ +import { getHeaderValue } from "@chkp/mcp-utils"; + +export class Settings { + constructor( + public readonly defaultEncoding: string = "utf-8" + ) {} + + validate(): boolean { + return true; + } + + static fromArgs(options: { encoding?: string } = {}): Settings { + return new Settings(options.encoding || "utf-8"); + } + + static fromHeaders(headers: Record): Settings { + const encoding = getHeaderValue(headers, "CPINFO-ENCODING") || "utf-8"; + return new Settings(encoding); + } +} diff --git a/packages/cpinfo-analysis/src/tool-handlers.ts b/packages/cpinfo-analysis/src/tool-handlers.ts new file mode 100644 index 0000000..a7048ff --- /dev/null +++ b/packages/cpinfo-analysis/src/tool-handlers.ts @@ -0,0 +1,1175 @@ +import { basename } from "path"; +import { z } from "zod"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { CpInfoService } from "./cpinfo-service.js"; +import { CpInfoAdvancedIndex } from "./cpinfo-index.js"; +import { CpInfoReader } from "./cpinfo-reader.js"; +import { + BasicCache, + CoreDumpCache, + FileProcessingCache, + InitializationStatus, + LicensingCache, + NetworkCache, + PerformanceCache, + ProcessingStatus, + SectionCacheEntry, + SectionInfo, + SectionType, + SecurityCache +} from "./types.js"; + +interface McpContent { + type: "text"; + text: string; +} + +interface ToolResult { + content: McpContent[]; +} + +const SECTION_TYPE_OPTIONS = [ + "system_info", + "performance", + "diagnostics", + "security", + "licensing", + "network", + "log_files", + "command_output", + "core_dumps", + "configuration", + "vpn", + "firewall", + "monitoring", + "database", + "processes", + "unknown" +] as const; + +type SectionTypeString = (typeof SECTION_TYPE_OPTIONS)[number]; + +type CachedSearchResult = { + lines: string[]; + scanned: number; +}; + +interface ContentMatch { + text: string; + lineNumber: number; +} + +const DEFAULT_SECTION_PAGE_SIZE = 30; +const SECTION_SEARCH_CHUNK_BYTES = 64 * 1024; // 64KB chunks keep memory bounded during scans +const SECTION_SEARCH_MAX_BYTES = 50 * 1024 * 1024; // Guardrail for extremely large sections (50MB) +const SECTION_MATCH_LINES_LIMIT = 5; + +function textResult(lines: string[]): ToolResult { + return { + content: [ + { + type: "text", + text: lines.join("\n") + } + ] + }; +} + +function formatNumber(value: number): string { + return value.toLocaleString("en-US"); +} + +function formatBytes(value: number): string { + if (value === 0) { + return "0 B"; + } + const units = ["B", "KB", "MB", "GB", "TB"]; + let size = value; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex += 1; + } + return unitIndex === 0 ? `${Math.round(size)} ${units[unitIndex]}` : `${size.toFixed(2)} ${units[unitIndex]}`; +} + +function paginate(items: T[], page: number, pageSize: number): { pageItems: T[]; totalPages: number } { + const totalPages = Math.max(1, Math.ceil(items.length / pageSize)); + const currentPage = Math.min(Math.max(page, 1), totalPages); + const start = (currentPage - 1) * pageSize; + const pageItems = items.slice(start, start + pageSize); + return { pageItems, totalPages }; +} + +function sectionTypeFromString(value: SectionTypeString): SectionType { + return SectionType[value.toUpperCase() as keyof typeof SectionType]; +} + +function debugToolsEnabled(): boolean { + const flag = process.env.CPINFO_ENABLE_DEBUG_TOOLS ?? process.env.CPINFO_DEBUG_TOOLS; + if (!flag) { + return false; + } + return flag.trim().toLowerCase() === "true"; +} + +function describeCache(cache: BasicCache | null, title: string): string[] { + if (!cache) { + return [`${title}: not cached`]; + } + const lines = [`${title}: ${formatNumber(cache.sections_count)} sections (${formatNumber(cache.sections.length)} cached)`]; + if (cache.summary && Object.keys(cache.summary).length > 0) { + lines.push(`Summary: ${JSON.stringify(cache.summary)}`); + } + return lines; +} + +function buildOverview(cache: FileProcessingCache, fileName: string, showStats: boolean): string[] { + const lines: string[] = []; + const summary = cache.semantic_analysis; + lines.push(`# CPInfo Analysis - ${fileName}`); + + if (!summary) { + lines.push("File not initialized. Run analysis first."); + return lines; + } + + lines.push("## File Overview"); + lines.push(`Total sections: ${formatNumber(summary.total_sections)}`); + lines.push(`File size: ${formatBytes(summary.file_size)}`); + lines.push(""); + + lines.push("## Section Categories"); + Object.entries(summary.categories).forEach(([category, count]) => { + lines.push(`- ${category.replace(/_/g, " ")}: ${formatNumber(count)}`); + }); + lines.push(""); + + lines.push("## Recommended Next Steps"); + if (summary.categories.system_info) { + lines.push("- Use extract_system_details for OS and platform information."); + } + if (summary.categories.performance) { + lines.push("- Use analyze_performance_metrics for CPU and memory stats."); + } + if (summary.categories.security) { + lines.push("- Use audit_security_settings for user and permissions review."); + } + if (summary.categories.licensing) { + lines.push("- Use extract_license_information to inspect blade entitlements."); + } + lines.push("- Use smart_content_search for keyword-based queries."); + lines.push("- Use read_section_content for detailed section review."); + lines.push(""); + + if (showStats) { + lines.push("## Cache Statistics"); + lines.push(...describeCache(cache.system_info_cache, "System info")); + lines.push(...describeCache(cache.performance_cache, "Performance")); + lines.push(...describeCache(cache.security_cache, "Security")); + lines.push(...describeCache(cache.licensing_cache, "Licensing")); + if (cache.core_dumps_cache) { + lines.push(`Core dumps cached: ${formatNumber(cache.core_dumps_cache.sections.length)}`); + } + } + + return lines; +} + +function summarizeSection(section: SectionInfo, index: number): string[] { + const lines = [`${index}. ${section.name}`]; + lines.push(` Offset: ${formatNumber(section.startOffset)}`); + if (section.endOffset) { + lines.push(` Size: ${formatNumber(section.endOffset - section.startOffset)} bytes`); + } + if (section.metadata?.pattern_type) { + lines.push(` Pattern: ${section.metadata.pattern_type}`); + } + return lines; +} + +async function buildSectionContent( + service: CpInfoService, + filePath: string, + sectionName: string, + page: number, + pageSize: number +): Promise { + const { reader, cache } = await service.ensureFileInitialized(filePath); + const normalized = sectionName.toLowerCase(); + + const index = reader.getIndex(); + const allSections = index.getAllSections(); + + const locateSection = (): SectionInfo | undefined => { + const byExact = allSections.find((section) => section.name.toLowerCase() === normalized); + if (byExact) { + return byExact; + } + const cachedEntry = cache.section_content_cache.get(normalized); + if (cachedEntry) { + const byOffset = allSections.find((section) => section.startOffset === cachedEntry.offset); + if (byOffset) { + return byOffset; + } + } + return allSections.find((section) => section.name.toLowerCase().includes(normalized)); + }; + + const section = locateSection(); + + if (!section) { + return [`Section '${sectionName}' not found.`]; + } + + const sectionSize = Math.max( + 0, + (section.endOffset ?? reader.fileSizeBytes) - section.startOffset + ); + + let target = cache.section_content_cache.get(normalized); + + const targetBytes = target ? Buffer.byteLength(target.content, reader.encoding) : 0; + const isComplete = Boolean(target?.metadata?.fullContent); + const needsRefresh = !target || target.offset !== section.startOffset || (!isComplete && sectionSize > targetBytes); + + if (needsRefresh) { + const bytesToRead = sectionSize > 0 ? sectionSize : reader.fileSizeBytes - section.startOffset; + const safeBytes = bytesToRead > 0 ? bytesToRead : 5 * 1024 * 1024; + const content = await reader.readSectionByOffset(section.startOffset, safeBytes); + target = { + name: section.name, + content, + offset: section.startOffset, + size: sectionSize || undefined, + metadata: { ...(target?.metadata ?? {}), fullContent: true } + }; + cache.section_content_cache.set(normalized, target); + } else if (target && section.name !== target.name) { + target = { + ...target, + name: section.name + }; + cache.section_content_cache.set(normalized, target); + } + + if (!target) { + return [`Section '${sectionName}' not found.`]; + } + + const contentLines = target.content.split(/\r?\n/); + const totalLineCount = contentLines.length; + const totalPages = Math.max(1, Math.ceil(totalLineCount / pageSize)); + const currentPage = Math.min(Math.max(page, 1), totalPages); + const lineStart = totalLineCount > 0 ? (currentPage - 1) * pageSize : 0; + const lineEnd = totalLineCount > 0 ? Math.min(lineStart + pageSize, totalLineCount) : 0; + const pageLines = contentLines.slice(lineStart, lineEnd); + const pageContent = pageLines.join("\n"); + + const lines: string[] = []; + lines.push(`# Section Content: ${target.name}`); + lines.push(`Offset: ${formatNumber(target.offset)}`); + lines.push(`Page ${currentPage}/${totalPages}`); + lines.push(""); + const nextPageNumber = currentPage < totalPages ? currentPage + 1 : null; + lines.push("READING PROTOCOL"); + lines.push("- If your task requires full coverage of this section, you MUST read every page sequentially."); + lines.push( + "- To fetch the next portion, call read_section_content again with page=" + + (nextPageNumber ?? currentPage) + + "." + ); + lines.push( + "- Stop calling the tool once you have the information you need" + + (totalPages > 1 ? " or after page " + totalPages : "") + + "." + ); + lines.push(""); + lines.push( + `[PAGING] current=${currentPage}; total=${totalPages}; next=${nextPageNumber ?? "n/a"}; has_more=${nextPageNumber !== null}` + ); + lines.push(""); + const displayStartLine = totalLineCount > 0 ? lineStart + 1 : 0; + const displayEndLine = totalLineCount > 0 ? lineEnd : 0; + lines.push(`Lines ${formatNumber(displayStartLine)}-${formatNumber(displayEndLine)} of ${formatNumber(totalLineCount)}`); + lines.push("```"); + lines.push(pageContent); + lines.push("```"); + + return lines; +} + +async function scanSectionForTerm( + reader: CpInfoReader, + section: SectionInfo, + term: string, + caseSensitive: boolean +): Promise { + const matches: ContentMatch[] = []; + const sectionEnd = section.endOffset ?? reader.fileSizeBytes; + const rawSize = Math.max(0, sectionEnd - section.startOffset); + if (rawSize === 0) { + return matches; + } + + const limit = Math.min(rawSize, SECTION_SEARCH_MAX_BYTES); + const chunkSize = Math.min(SECTION_SEARCH_CHUNK_BYTES, limit); + const encoding = reader.encoding ?? "utf-8"; + + let processed = 0; + let remainder = ""; + let lineNumber = 0; + + while (processed < limit && matches.length < SECTION_MATCH_LINES_LIMIT) { + const targetBytes = Math.min(chunkSize, limit - processed); + const chunk = await reader.readSectionByOffset(section.startOffset + processed, targetBytes); + if (!chunk) { + break; + } + + const bytesRead = Buffer.byteLength(chunk, encoding); + if (bytesRead === 0) { + break; + } + processed += bytesRead; + + const combined = remainder + chunk; + const lines = combined.split(/\r?\n/); + const lastLineComplete = combined.endsWith("\n") || combined.endsWith("\r"); + + remainder = ""; + if (!lastLineComplete) { + remainder = lines.pop() ?? ""; + } + + for (const line of lines) { + lineNumber += 1; + const comparison = caseSensitive ? line : line.toLowerCase(); + if (comparison.includes(term)) { + matches.push({ text: line.trim(), lineNumber }); + if (matches.length >= SECTION_MATCH_LINES_LIMIT) { + break; + } + } + } + } + + if (matches.length < SECTION_MATCH_LINES_LIMIT && remainder) { + lineNumber += 1; + const comparison = caseSensitive ? remainder : remainder.toLowerCase(); + if (comparison.includes(term)) { + matches.push({ text: remainder.trim(), lineNumber }); + } + } + + return matches; +} + +function buildPerformanceSummary(cache: PerformanceCache | null): string[] { + if (!cache) { + return ["No performance cache available."]; + } + const lines: string[] = []; + lines.push(`# Performance Analysis`); + lines.push(`Sections cached: ${formatNumber(cache.sections.length)} / ${formatNumber(cache.sections_count)}`); + lines.push(`CPU spikes detected: ${cache.has_cpu_spikes ? "Yes" : "No"}`); + lines.push(`Memory issues detected: ${cache.has_memory_issues ? "Yes" : "No"}`); + lines.push(""); + + cache.sections.slice(0, 5).forEach((entry, index) => { + lines.push(`${index + 1}. ${entry.name}`); + lines.push("---"); + lines.push(entry.content.split("\n").slice(0, 12).join("\n")); + lines.push(""); + }); + + return lines; +} + +function buildLicensingSummary(cache: LicensingCache | null): string[] { + if (!cache) { + return ["No licensing sections found."]; + } + const lines: string[] = []; + lines.push(`# Licensing Information`); + lines.push(`Sections cached: ${formatNumber(cache.sections.length)} / ${formatNumber(cache.sections_count)}`); + lines.push(`License tables detected: ${cache.license_tables.join(", ") || "none"}`); + lines.push(`Expired licenses detected: ${cache.has_expired_licenses ? "Yes" : "No"}`); + lines.push(""); + + cache.sections.forEach((entry, index) => { + lines.push(`${index + 1}. ${entry.name}`); + lines.push("---"); + lines.push(entry.content.split("\n").slice(0, 15).join("\n")); + lines.push(""); + }); + + return lines; +} + +function buildSecuritySummary(cache: SecurityCache | null): string[] { + if (!cache) { + return ["No security sections found."]; + } + const lines: string[] = []; + lines.push(`# Security Settings`); + lines.push(`Sections cached: ${formatNumber(cache.sections.length)} / ${formatNumber(cache.sections_count)}`); + lines.push(`User information present: ${cache.has_user_info ? "Yes" : "No"}`); + lines.push(`Permission information present: ${cache.has_permission_info ? "Yes" : "No"}`); + lines.push(""); + + cache.sections.forEach((entry, index) => { + lines.push(`${index + 1}. ${entry.name}`); + lines.push("---"); + lines.push(entry.content.split("\n").slice(0, 12).join("\n")); + lines.push(""); + }); + + return lines; +} + +function buildCoreDumpSummary(cache: CoreDumpCache | null): string[] { + if (!cache) { + return ["No core dump information available."]; + } + const lines: string[] = []; + lines.push(`# Core Dump Analysis`); + lines.push(`Core dumps found: ${formatNumber(cache.sections_count)}`); + if (!cache.sections.length) { + lines.push("No core dump sections cached."); + return lines; + } + cache.sections.forEach((entry, index) => { + lines.push(`${index + 1}. ${entry.name}`); + lines.push("---"); + lines.push(entry.content.split("\n").slice(0, 12).join("\n")); + lines.push(""); + }); + if (cache.crash_summary.length) { + lines.push("Common crash indicators:"); + cache.crash_summary.slice(0, 5).forEach((line) => lines.push(`- ${line}`)); + } + return lines; +} + +function buildNetworkSummary(cache: NetworkCache | null, page: number, sectionsPerPage: number, includeInterfaces: boolean): string[] { + if (!cache) { + return ["No network configuration sections found."]; + } + const lines: string[] = []; + lines.push(`# Network Configuration`); + lines.push(`Network sections: ${formatNumber(cache.sections_count)}`); + lines.push(`Interfaces detected: ${formatNumber(cache.interfaces.length)}`); + lines.push(`Configuration issues: ${formatNumber(cache.config_issues.length)}`); + lines.push(""); + + if (cache.config_issues.length) { + lines.push("Top configuration issues:"); + cache.config_issues.slice(0, 5).forEach((issue) => lines.push(`- ${issue}`)); + lines.push(""); + } + + if (includeInterfaces && cache.interfaces.length) { + lines.push("Interfaces summary:"); + cache.interfaces.slice(0, 10).forEach((iface) => { + lines.push(`- ${iface.name}`); + }); + lines.push(""); + } + + const { pageItems, totalPages } = paginate(cache.sections, page, sectionsPerPage); + lines.push(`Page ${page}/${totalPages}`); + pageItems.forEach((entry, index) => { + lines.push(`${index + 1 + (page - 1) * sectionsPerPage}. ${entry.name}`); + lines.push("---"); + lines.push(entry.content.split("\n").slice(0, 12).join("\n")); + lines.push(""); + }); + + if (page < totalPages) { + lines.push(`Use page=${page + 1} for the next page.`); + } + + return lines; +} + +export function registerCpinfoTools(server: McpServer, service: CpInfoService): number { + const debugToolsActive = debugToolsEnabled(); + let registeredTools = 0; + + server.tool( + "check_initialization_status", + "Check or ensure a cpinfo file is initialized and get detailed status. Use initialize=true to trigger initialization if not already done, or initialize=false to just check current status without processing.", + { + file_path: z.string().describe("Path to cpinfo file"), + initialize: z.boolean().optional().describe("If true, ensures file is initialized (default: true). If false, only checks current status without triggering initialization.") + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const shouldInitialize = args.initialize !== false; // Default to true + + let cache; + if (shouldInitialize) { + const result = await service.ensureFileInitialized(filePath); + cache = result.cache; + } else { + cache = service.getProcessingSnapshot(filePath); + } + + const status = service.getInitializationStatus(filePath); + const ready = cache.initialized && status.status === ProcessingStatus.COMPLETE; + const lines = formatInitializationStatus(status, ready); + + if (ready && cache.semantic_analysis) { + const summary = cache.semantic_analysis; + lines.push(""); + lines.push("## Summary"); + lines.push(`Total sections: ${formatNumber(summary.total_sections)}`); + lines.push(`File size: ${formatBytes(summary.file_size)}`); + lines.push("Categories:"); + Object.entries(summary.categories).forEach(([category, count]) => { + lines.push(`- ${category}: ${formatNumber(count)}`); + }); + } + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "analyze_cpinfo_overview", + "Run comprehensive analysis with semantic categorization.", + { + file_path: z.string().describe("Path to cpinfo file"), + show_stats: z.boolean().describe("Include cache statistics").optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const showStats = args.show_stats as boolean | undefined; + const { cache } = await service.ensureFileInitialized(filePath); + const lines = buildOverview(cache, basename(filePath), showStats !== false); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "browse_sections_by_category", + "List sections by semantic category.", + { + file_path: z.string().describe("Path to cpinfo file"), + section_type: z.enum(SECTION_TYPE_OPTIONS).describe("Category to browse"), + page: z.number().int().positive().optional(), + page_size: z.number().int().positive().optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const sectionType = args.section_type as SectionTypeString; + const page = (args.page as number | undefined) ?? 1; + const pageSize = (args.page_size as number | undefined) ?? 10; + const { reader } = await service.ensureFileInitialized(filePath); + const sections = await reader.getSectionsByType(sectionTypeFromString(sectionType)); + const { pageItems, totalPages } = paginate(sections, page, pageSize); + const lines: string[] = []; + lines.push(`# Sections: ${sectionType}`); + lines.push(`Page ${page}/${totalPages}`); + pageItems.forEach((section, index) => { + lines.push(...summarizeSection(section, index + 1 + (page - 1) * pageSize)); + }); + if (page < totalPages) { + lines.push(`Use page=${page + 1} for the next page.`); + } + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "extract_system_details", + "Extract key system information sections.", + { + file_path: z.string(), + page: z.number().int().positive().optional(), + page_size: z.number().int().positive().optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const page = (args.page as number | undefined) ?? 1; + const pageSize = (args.page_size as number | undefined) ?? 5; + const { cache } = await service.ensureFileInitialized(filePath); + const systemCache = cache.system_info_cache; + if (!systemCache) { + return textResult(["No system information sections cached."]); + } + const { pageItems, totalPages } = paginate(systemCache.sections, page, pageSize); + const lines: string[] = []; + lines.push(`# System Information`); + lines.push(`Sections cached: ${formatNumber(systemCache.sections.length)} / ${formatNumber(systemCache.sections_count)}`); + lines.push(`Page ${page}/${totalPages}`); + pageItems.forEach((entry, index) => { + lines.push(`${index + 1 + (page - 1) * pageSize}. ${entry.name}`); + lines.push("---"); + lines.push(entry.content.split("\n").slice(0, 12).join("\n")); + lines.push(""); + }); + if (page < totalPages) { + lines.push(`Use page=${page + 1} for more.`); + } + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "analyze_performance_metrics", + "Summarize performance related sections.", + { + file_path: z.string() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const { cache } = await service.ensureFileInitialized(filePath); + const lines = buildPerformanceSummary(cache.performance_cache); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "extract_license_information", + "Summarize licensing information.", + { + file_path: z.string() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const { cache } = await service.ensureFileInitialized(filePath); + const lines = buildLicensingSummary(cache.licensing_cache); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "audit_security_settings", + "Summarize security configuration and users.", + { + file_path: z.string() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const { cache } = await service.ensureFileInitialized(filePath); + const lines = buildSecuritySummary(cache.security_cache); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "detect_system_crashes", + "Review core dump information for crashes.", + { + file_path: z.string() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const { cache } = await service.ensureFileInitialized(filePath); + const lines = buildCoreDumpSummary(cache.core_dumps_cache); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "extract_network_config", + "Inspect network configuration sections.", + { + file_path: z.string(), + include_interfaces: z.boolean().optional(), + page: z.number().int().positive().optional(), + sections_per_page: z.number().int().positive().optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const includeInterfaces = (args.include_interfaces as boolean | undefined) ?? true; + const page = (args.page as number | undefined) ?? 1; + const sectionsPerPage = (args.sections_per_page as number | undefined) ?? 5; + const { cache } = await service.ensureFileInitialized(filePath); + const lines = buildNetworkSummary(cache.network_cache, page, sectionsPerPage, includeInterfaces); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "read_section_content", + "Read raw section content with pagination.", + { + file_path: z.string(), + section_name: z.string(), + page: z.number().int().positive().optional(), + page_size: z + .number() + .int() + .positive() + .describe("Lines per page (default 30)") + .optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const sectionName = args.section_name as string; + const page = (args.page as number | undefined) ?? 1; + const pageSize = (args.page_size as number | undefined) ?? 30; + const lines = await buildSectionContent(service, filePath, sectionName, page, pageSize); + return textResult(lines); + } + ); + registeredTools += 1; + + server.tool( + "smart_content_search", + "Search sections by keyword with optional filtering.", + { + file_path: z.string(), + keyword: z.string(), + section_types: z.array(z.enum(SECTION_TYPE_OPTIONS)).optional(), + case_sensitive: z.boolean().optional(), + search_content: z.boolean().optional(), + max_results: z.number().int().positive().optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const keyword = args.keyword as string; + const sectionTypes = (args.section_types as SectionTypeString[] | undefined) ?? []; + const caseSensitive = (args.case_sensitive as boolean | undefined) ?? false; + const searchContent = (args.search_content as boolean | undefined) ?? true; + const maxResults = (args.max_results as number | undefined) ?? 50; + const { reader, cache } = await service.ensureFileInitialized(filePath); + const cacheKey = JSON.stringify({ keyword, sectionTypes, caseSensitive, searchContent, maxResults }); + const cached = cache.search_cache.get(cacheKey) as CachedSearchResult | undefined; + if (cached) { + return textResult(cached.lines); + } + const searchTerm = caseSensitive ? keyword : keyword.toLowerCase(); + const sectionsToSearch: SectionInfo[] = []; + + if (sectionTypes.length) { + for (const type of sectionTypes) { + const mapped = sectionTypeFromString(type); + const sections = await reader.getSectionsByType(mapped); + sectionsToSearch.push(...sections); + } + } else { + for (const type of Object.values(SectionType)) { + const sections = await reader.getSectionsByType(type as SectionType); + sectionsToSearch.push(...sections); + } + } + + const matches: { section: SectionInfo; hits: ContentMatch[] }[] = []; + let scanned = 0; + + for (const section of sectionsToSearch) { + if (matches.length >= maxResults) { + break; + } + scanned += 1; + const name = caseSensitive ? section.name : section.name.toLowerCase(); + const nameMatch = name.includes(searchTerm); + let contentMatches: ContentMatch[] = []; + + if (searchContent) { + contentMatches = await scanSectionForTerm(reader, section, searchTerm, caseSensitive); + } + + if (nameMatch || contentMatches.length > 0) { + matches.push({ section, hits: contentMatches }); + } + } + + const lines: string[] = []; + lines.push(`# Search Results for '${keyword}'`); + lines.push(`Sections scanned: ${formatNumber(scanned)}`); + lines.push(`Matches found: ${formatNumber(matches.length)}`); + if (matches.length >= maxResults) { + lines.push(`Results truncated at ${maxResults}. Refine your query for more specific matches.`); + } + lines.push(""); + + matches.forEach(({ section, hits }, index) => { + lines.push(`${index + 1}. ${section.name}`); + lines.push(` Type: ${section.sectionType}`); + lines.push(` Offset: ${formatNumber(section.startOffset)}`); + if (hits.length) { + lines.push(" Matching lines:"); + hits.forEach(({ text, lineNumber }) => { + // Note: Page numbers removed due to inconsistency with user-specified page_size + // Use line numbers to navigate. To calculate page: Math.floor((line-1)/page_size)+1 + lines.push(` - [line ${lineNumber}] ${text}`); + }); + } + lines.push(""); + }); + + cache.search_cache.set(cacheKey, { lines, scanned }); + + return textResult(lines); + } + ); + registeredTools += 1; + + if (debugToolsActive) { + server.tool( + "manage_unknown_sections", + "Manage sections that could not be automatically categorized.", + { + file_path: z.string().describe("Path to cpinfo file"), + action: z.enum(["list", "suggest", "reclassify", "bulk_reclassify"]).describe("Action to perform"), + section_name: z.string().optional().describe("Section name for suggest or reclassify"), + new_category: z.string().optional().describe("New category for reclassify"), + pattern_mappings: z + .record(z.string()) + .optional() + .describe("Pattern to category mappings for bulk reclassify"), + page: z.number().int().positive().optional(), + page_size: z.number().int().positive().optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const action = args.action as "list" | "suggest" | "reclassify" | "bulk_reclassify"; + const sectionName = args.section_name as string | undefined; + const newCategory = args.new_category as string | undefined; + const patternMappings = args.pattern_mappings as Record | undefined; + const page = (args.page as number | undefined) ?? 1; + const pageSize = (args.page_size as number | undefined) ?? 20; + return handleManageUnknownSections(service, filePath, { + action, + sectionName, + newCategory, + patternMappings, + page, + pageSize + }); + } + ); + registeredTools += 1; + } + + server.tool( + "comprehensive_health_analysis", + "Correlate multiple caches for health insights.", + { + file_path: z.string(), + analysis_type: z.enum(["system", "performance", "licensing", "security"]).describe("Analysis focus"), + include_recommendations: z.boolean().optional() + }, + async (args: Record): Promise => { + const filePath = args.file_path as string; + const analysisType = args.analysis_type as "system" | "performance" | "licensing" | "security"; + const includeRecommendations = (args.include_recommendations as boolean | undefined) ?? true; + const { cache } = await service.ensureFileInitialized(filePath); + let lines: string[]; + switch (analysisType) { + case "system": + lines = buildSystemHealth(cache, includeRecommendations); + break; + case "performance": + lines = buildPerformanceHealth(cache, includeRecommendations); + break; + case "licensing": + lines = buildLicensingHealth(cache, includeRecommendations); + break; + case "security": + default: + lines = buildSecurityHealth(cache, includeRecommendations); + break; + } + return textResult(lines); + } + ); + registeredTools += 1; + + return registeredTools; +} + +function formatInitializationStatus(status: InitializationStatus, ready: boolean): string[] { + const lines: string[] = []; + lines.push(`# Initialization Status`); + lines.push(`Status: ${status.status}`); + lines.push(`Progress: ${status.progress}%`); + lines.push(`Stage: ${status.stage}`); + lines.push(`Activity: ${status.current_activity}`); + lines.push(`Sections processed: ${status.sections_processed}/${status.total_sections}`); + lines.push(`Ready for tools: ${ready ? "YES" : "NO"}`); + if (status.estimated_completion) { + const remaining = Math.max(0, Math.round(status.estimated_completion - Date.now() / 1000)); + lines.push(`Estimated time remaining: ${remaining} seconds`); + } + return lines; +} + +function buildSystemHealth(cache: FileProcessingCache, includeRecommendations: boolean): string[] { + const lines: string[] = []; + lines.push(`# System Health Overview`); + const summary = cache.semantic_analysis; + if (!summary) { + lines.push("Semantic summary unavailable."); + return lines; + } + lines.push(`Total sections: ${formatNumber(summary.total_sections)}`); + lines.push(`Known categories: ${Object.keys(summary.categories).length}`); + lines.push(""); + lines.push("Key categories:"); + Object.entries(summary.categories) + .filter(([category]) => ["system_info", "network", "performance"].includes(category)) + .forEach(([category, count]) => lines.push(`- ${category}: ${formatNumber(count)}`)); + if (includeRecommendations) { + lines.push(""); + lines.push("Recommendations:"); + lines.push("- Review system_info for OS and hardware configuration."); + lines.push("- Inspect network sections for interface status and routing."); + } + return lines; +} + +function buildPerformanceHealth(cache: FileProcessingCache, includeRecommendations: boolean): string[] { + const lines = buildPerformanceSummary(cache.performance_cache); + if (includeRecommendations) { + lines.push("Recommendations:"); + lines.push("- Investigate interfaces with high CPU or memory usage."); + lines.push("- Correlate with system load averages."); + } + return lines; +} + +function buildLicensingHealth(cache: FileProcessingCache, includeRecommendations: boolean): string[] { + const lines = buildLicensingSummary(cache.licensing_cache); + if (includeRecommendations) { + lines.push("Recommendations:"); + lines.push("- Renew expired licenses promptly."); + lines.push("- Verify blade enablement aligns with deployment requirements."); + } + return lines; +} + +function buildSecurityHealth(cache: FileProcessingCache, includeRecommendations: boolean): string[] { + const lines = buildSecuritySummary(cache.security_cache); + if (includeRecommendations) { + lines.push("Recommendations:"); + lines.push("- Review privileged users and permission assignments."); + lines.push("- Audit authentication logs for anomalies."); + } + return lines; +} + + +const MANUAL_CATEGORY_HINTS: Record = { + system_info: "**system_info** - System information, version, hardware", + performance: "**performance** - CPU, memory, performance metrics", + network: "**network** - Network interfaces, routing, connectivity", + security: "**security** - User accounts, permissions, security settings", + licensing: "**licensing** - License information, blade entitlements", + configuration: "**configuration** - System configuration, policies", + vpn: "**vpn** - VPN tunnels, IPSec configuration", + firewall: "**firewall** - Firewall rules, access control", + processes: "**processes** - Process information, services", + database: "**database** - Database information", + monitoring: "**monitoring** - Monitoring, alerts, SNMP", + log_files: "**log_files** - Log files and log analysis", + diagnostics: "**diagnostics** - Diagnostic information", + command_output: "**command_output** - Command outputs and results", + core_dumps: "**core_dumps** - Core dumps and crash information" +}; + +interface ManageUnknownOptions { + action: "list" | "suggest" | "reclassify" | "bulk_reclassify"; + sectionName?: string; + newCategory?: string; + patternMappings?: Record; + page?: number; + pageSize?: number; +} + +interface CategorizationSuggestion { + section: string; + current_type: SectionType; + suggestions: Array<{ type: SectionType; display_name: string; confidence: number }>; + pattern_matches: string[]; +} + +function getCategorizationSuggestionsSafely( + index: CpInfoAdvancedIndex, + section: SectionInfo +): CategorizationSuggestion { + const fn = (index as unknown as { getCategorizationSuggestions?: (section: SectionInfo) => CategorizationSuggestion }) + .getCategorizationSuggestions; + + if (typeof fn === "function") { + return fn.call(index, section); + } + + return { + section: section.name, + current_type: section.sectionType, + suggestions: [], + pattern_matches: [] + }; +} + +function buildManageUnknownList(index: CpInfoAdvancedIndex, sections: SectionInfo[], page: number, pageSize: number): string[] { + if (!sections.length) { + return ["# Unknown Sections", "All sections have been successfully categorized."]; + } + const { pageItems, totalPages } = paginate(sections, page, pageSize); + const lines: string[] = []; + lines.push(`# Unknown Sections`); + lines.push(`Total: ${formatNumber(sections.length)}`); + lines.push(`Page ${page}/${totalPages}`); + lines.push(""); + + pageItems.forEach((section, idx) => { + lines.push(`${idx + 1 + (page - 1) * pageSize}. ${section.name}`); + lines.push(` Offset: ${formatNumber(section.startOffset)}`); + if (section.endOffset) { + lines.push(` Size: ${formatNumber(section.endOffset - section.startOffset)} bytes`); + } + if (section.metadata?.pattern_type) { + lines.push(` Pattern: ${section.metadata.pattern_type}`); + } + const suggestions = getCategorizationSuggestionsSafely(index, section); + const top = suggestions.suggestions?.[0]; + if (top) { + lines.push(` Suggested: ${top.display_name} (confidence ${top.confidence})`); + } + lines.push(""); + }); + + if (totalPages > 1 && page < totalPages) { + lines.push(`Use page=${page + 1} for more results.`); + } + + return lines; +} + +function buildCategorizationSuggestion(index: CpInfoAdvancedIndex, section: SectionInfo): string[] { + const details = getCategorizationSuggestionsSafely(index, section); + const lines: string[] = []; + lines.push(`# Categorization Suggestions - ${section.name}`); + lines.push(""); + lines.push(`Current category: ${section.sectionType}`); + lines.push(`Pattern type: ${section.metadata?.pattern_type ?? "unknown"}`); + lines.push(""); + + if (!details.suggestions.length) { + lines.push("No suggestions based on section name patterns."); + lines.push(""); + lines.push("Manual classification options:"); + Object.values(MANUAL_CATEGORY_HINTS).forEach((hint) => lines.push(`- ${hint}`)); + return lines; + } + + lines.push("Suggested categories:"); + details.suggestions.forEach((entry) => { + lines.push(`- ${entry.display_name} (confidence ${entry.confidence})`); + }); + + if (details.pattern_matches && details.pattern_matches.length) { + lines.push(""); + lines.push("Pattern matches:"); + details.pattern_matches.forEach((pattern) => lines.push(`- ${pattern}`)); + } + + return lines; +} + +async function handleManageUnknownSections( + service: CpInfoService, + filePath: string, + options: ManageUnknownOptions +): Promise { + const { reader, cache } = await service.ensureFileInitialized(filePath); + const index = reader.getIndex(); + const unknownSections = index.getUnknownSections(); + + switch (options.action) { + case "list": { + const lines = buildManageUnknownList(index, unknownSections, options.page ?? 1, options.pageSize ?? 20); + return textResult(lines); + } + case "suggest": { + if (!options.sectionName) { + return textResult(["section_name is required for the suggest action."]); + } + const targetName = options.sectionName.toLowerCase(); + const target = unknownSections.find((section) => section.name.toLowerCase() === targetName); + if (!target) { + return textResult([`Unknown section '${options.sectionName}' not found.`]); + } + const lines = buildCategorizationSuggestion(index, target); + return textResult(lines); + } + case "reclassify": { + if (!options.sectionName || !options.newCategory) { + return textResult(["section_name and new_category are required for the reclassify action."]); + } + const targetName = options.sectionName.toLowerCase(); + const target = unknownSections.find((section) => section.name.toLowerCase() === targetName); + if (!target) { + return textResult([`Unknown section '${options.sectionName}' not found.`]); + } + const key = options.newCategory.toUpperCase() as keyof typeof SectionType; + const newType = SectionType[key]; + if (!newType) { + return textResult([`Invalid category '${options.newCategory}'.`]); + } + if (!index.reclassifySection(target.name, newType)) { + return textResult([`Failed to reclassify '${target.name}'.`]); + } + const updatedCache = await service.recomputeSemanticSummary(filePath); + updatedCache.search_cache.clear(); + updatedCache.section_content_cache.clear(); + const lines: string[] = []; + lines.push(`# Reclassification Complete`); + lines.push(`Section '${target.name}' reassigned to ${newType}.`); + lines.push(`Remaining unknown sections: ${formatNumber(index.getUnknownSections().length)}`); + return textResult(lines); + } + case "bulk_reclassify": { + if (!options.patternMappings || Object.keys(options.patternMappings).length === 0) { + return textResult(["pattern_mappings is required for the bulk_reclassify action."]); + } + const result = index.bulkReclassifyUnknown(options.patternMappings); + const updatedCache = await service.recomputeSemanticSummary(filePath); + updatedCache.search_cache.clear(); + updatedCache.section_content_cache.clear(); + const lines: string[] = []; + lines.push(`# Bulk Reclassification Results`); + lines.push(`Total unknown before: ${formatNumber(result.total_unknown)}`); + lines.push(`Reclassified: ${formatNumber(result.reclassified)}`); + lines.push(`Failed: ${formatNumber(result.failed)}`); + lines.push(`Remaining unknown sections: ${formatNumber(index.getUnknownSections().length)}`); + if (result.details.length) { + lines.push(""); + lines.push("Details:"); + result.details.slice(0, 20).forEach((detail) => { + const info = detail as Record; + const section = typeof info.section === "string" ? info.section : "unknown"; + const pattern = typeof info.pattern === "string" ? info.pattern : ""; + const newType = typeof info.new_type === "string" ? info.new_type : ""; + const successFlag = Boolean(info.success); + const errorMessage = typeof info.error === "string" ? info.error : ""; + const descriptor = successFlag ? "success" : `failed${errorMessage ? ` - ${errorMessage}` : ""}`; + const patternDisplay = pattern ? ` (${pattern})` : ""; + const targetType = newType || "unchanged"; + lines.push(`- ${section}${patternDisplay} -> ${targetType} : ${descriptor}`); + }); + if (result.details.length > 20) { + lines.push(`... ${result.details.length - 20} additional entries truncated.`); + } + } + return textResult(lines); + } + default: + return textResult([`Unsupported action '${options.action}'.`]); + } +} diff --git a/packages/cpinfo-analysis/src/types.ts b/packages/cpinfo-analysis/src/types.ts new file mode 100644 index 0000000..e018b73 --- /dev/null +++ b/packages/cpinfo-analysis/src/types.ts @@ -0,0 +1,124 @@ +export enum ProcessingStatus { + NOT_STARTED = "not_started", + INDEXING = "indexing", + CATEGORIZING = "categorizing", + ANALYZING = "analyzing", + COMPLETE = "complete", + ERROR = "error" +} + +export enum SectionType { + SYSTEM_INFO = "system_info", + PERFORMANCE = "performance", + DIAGNOSTICS = "diagnostics", + SECURITY = "security", + LICENSING = "licensing", + NETWORK = "network", + LOG_FILES = "log_files", + COMMAND_OUTPUT = "command_output", + CORE_DUMPS = "core_dumps", + CONFIGURATION = "configuration", + VPN = "vpn", + FIREWALL = "firewall", + MONITORING = "monitoring", + DATABASE = "database", + PROCESSES = "processes", + UNKNOWN = "unknown" +} + +export interface SectionInfo { + name: string; + sectionType: SectionType; + startOffset: number; + endOffset?: number; + parentPath?: string[]; + metadata: Record; +} + +export interface SectionCacheEntry { + name: string; + content: string; + offset: number; + size?: number; + metadata?: Record; +} + +export interface BasicCache { + sections_count: number; + sections: SectionCacheEntry[]; + summary?: Record; +} + +export interface PerformanceCache extends BasicCache { + has_cpu_spikes: boolean; + has_memory_issues: boolean; +} + +export interface LicensingCache extends BasicCache { + license_tables: string[]; + has_expired_licenses: boolean; +} + +export interface SecurityCache extends BasicCache { + has_user_info: boolean; + has_permission_info: boolean; +} + +export interface CoreDumpCache extends BasicCache { + has_crashes: boolean; + crash_summary: string[]; +} + +export interface NetworkInterfaceInfo { + name: string; + status: string; + ip?: string; + type?: string; +} + +export interface NetworkCache extends BasicCache { + interfaces: NetworkInterfaceInfo[]; + config_issues: string[]; +} + +export interface SemanticSummary { + total_sections: number; + section_types: Record; + file_size: number; + processing_time: number; + categories: Record; +} + +export interface FileProcessingCache { + semantic_analysis: SemanticSummary | null; + system_info_cache: BasicCache | null; + performance_cache: PerformanceCache | null; + licensing_cache: LicensingCache | null; + security_cache: SecurityCache | null; + core_dumps_cache: CoreDumpCache | null; + network_cache: NetworkCache | null; + search_cache: Map; + section_content_cache: Map; + cross_analysis_cache: Map; + initialized: boolean; + cache_timestamp: number | null; +} + +export interface InitializationStatus { + status: ProcessingStatus; + progress: number; + stage: string; + current_activity: string; + sections_processed: number; + total_sections: number; + start_time?: number; + estimated_completion?: number; + last_update?: number; +} + +export interface SearchMatch { + section: SectionInfo; + matchingLines: string[]; + nameMatched: boolean; + contentMatched: boolean; +} diff --git a/packages/cpinfo-analysis/tsconfig.json b/packages/cpinfo-analysis/tsconfig.json new file mode 100644 index 0000000..69d11db --- /dev/null +++ b/packages/cpinfo-analysis/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "/*.test.ts", "/*.spec.ts"] +} diff --git a/packages/documentation-tool/package.json b/packages/documentation-tool/package.json index 1c27e70..7b64e3e 100644 --- a/packages/documentation-tool/package.json +++ b/packages/documentation-tool/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/documentation-mcp", - "version": "0.1.4", + "version": "0.1.5", "description": "Check Point Documentation Tool Service - API integration with Check Point Documentation Tool", "type": "module", "main": "dist/index.js", @@ -19,7 +19,9 @@ }, "scripts": { "clean": "rimraf dist", - "build": "tsc && chmod +x ./dist/index.js && cp src/server-config.json dist/", + "build": "npm run build:bundle && npx shx chmod +x ./dist/index.js && npx shx cp src/server-config.json dist/", + "build:bundle": "node ../../bundle-mcp.js", + "build:tsc": "npx tsc && npx shx chmod +x ./dist/index.js && npx shx cp src/server-config.json dist/", "dev": "tsc --watch", "start": "node dist/index.js" }, diff --git a/packages/gw-cli-connection-analysis/package.json b/packages/gw-cli-connection-analysis/package.json index 66e2894..285e22a 100644 --- a/packages/gw-cli-connection-analysis/package.json +++ b/packages/gw-cli-connection-analysis/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/quantum-gw-connection-analysis-mcp", - "version": "0.4.4", + "version": "0.4.5", "description": "Model Context Protocol (MCP) server for Running Connection Analysis on a Check Point gateway.", "type": "module", "main": "dist/index.js", diff --git a/packages/gw-cli-connection-analysis/src/scripts/connection-debug.ts b/packages/gw-cli-connection-analysis/src/scripts/connection-debug.ts index a5ab530..d08359b 100644 --- a/packages/gw-cli-connection-analysis/src/scripts/connection-debug.ts +++ b/packages/gw-cli-connection-analysis/src/scripts/connection-debug.ts @@ -241,7 +241,7 @@ export class StopConnectionDebugScript extends ConnectionDebugScript { ); if (resultSuccess) { - const [taskSuccess, taskOutput] = await this.apiManager.getTaskResult(tasks.tasks[0]); + const [taskSuccess, taskOutput] = await this.apiManager.getTaskResult(this.targetGateway, tasks.tasks[0]); results.push(taskOutput); scriptsRun.push(scriptType); } else { diff --git a/packages/https-inspection/package.json b/packages/https-inspection/package.json index 8a3cab9..c4e8fcc 100644 --- a/packages/https-inspection/package.json +++ b/packages/https-inspection/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/https-inspection-mcp", - "version": "0.7.4", + "version": "0.7.5", "description": "HTTPS Inspection MCP server for Check Point products", "type": "module", "main": "dist/index.js", diff --git a/packages/https-inspection/src/index.ts b/packages/https-inspection/src/index.ts index a4faa55..2e822fa 100644 --- a/packages/https-inspection/src/index.ts +++ b/packages/https-inspection/src/index.ts @@ -40,7 +40,7 @@ const runApi = createApiRunner(serverModule); // HTTPS Inspection Tools server.tool( - 'init', + 'https-inspection__init', 'Verify, login and initialize management connection. Use this tool on your first interaction with the server.', {}, async (args: Record, extra: any) => { @@ -274,7 +274,7 @@ server.tool( server.tool( - 'show_gateways_and_servers', + 'https-inspection__show_gateways_and_servers', 'Retrieve multiple gateway and server objects with optional filtering and pagination. Use this to get the currently installed policies only gateways.', { filter: z.string().optional(), @@ -302,7 +302,7 @@ server.tool( ); server.tool( - 'show_objects', + 'https-inspection__show_objects', 'Retrieve multiple generic objects with filtering and pagination. Can use type (e.g host, service-tcp, network, address-range...) to get objects of a certain type.', { uids: z.array(z.string()).optional(), @@ -340,7 +340,7 @@ server.tool( // Tool: show_object server.tool( - 'show_object', + 'https-inspection__show_object', 'Retrieve a generic object by UID.', { uid: z.string(), diff --git a/packages/management-logs/package.json b/packages/management-logs/package.json index e70fe73..5c511a1 100644 --- a/packages/management-logs/package.json +++ b/packages/management-logs/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/management-logs-mcp", - "version": "0.6.4", + "version": "0.6.6", "description": "Management Logs MCP server for Check Point products", "type": "module", "main": "dist/index.js", diff --git a/packages/management-logs/src/index.ts b/packages/management-logs/src/index.ts index e372ffa..fa0cc42 100644 --- a/packages/management-logs/src/index.ts +++ b/packages/management-logs/src/index.ts @@ -37,7 +37,7 @@ const serverModule = createServerModule( const runApi = createApiRunner(serverModule); server.tool( - 'init', + 'management-logs__init', 'Verify, login and initialize management connection. Use this tool on your first interaction with the server.', {}, async (args: Record, extra: any) => { @@ -82,34 +82,27 @@ server.tool( ); -// Show logs tool +// Run logs query tool - for creating new queries server.tool( - 'show_logs', - 'Retrieve logs based on a given filter. It can ignore warnings if specified and can also run a new query. An example usage is to show logs with a new query that retrieves the top 10 logs based on the "blades" field.', + 'run_logs_query', + 'Run a new logs query with specified filters and parameters. Returns the first page of results and a query ID for pagination. Use this to start a new logs search.', { - 'ignore-warnings': z.boolean().optional(), - // New query parameters - 'new-query': z.object({ - filter: z.string().optional(), - 'time-frame': z.enum(['last-7-days', 'last-hour', 'today', 'last-24-hours', 'yesterday', 'this-week', 'this-month', 'last-30-days', 'all-time', 'custom']).optional(), - 'custom-start': z.string().optional(), - 'custom-end': z.string().optional(), - 'max-logs-per-request': z.number().min(1).max(100).optional(), - top: z.object({ - count: z.number().min(1).max(50), - field: z.enum(['sources', 'destinations', 'services', 'actions', 'blades', 'origins', 'users', 'applications']) - }).optional(), - type: z.enum(['logs', 'audit']).optional(), - 'log-servers': z.array(z.string()).optional() - }).optional(), - // Alternative: query by ID for pagination - 'query-id': z.string().optional(), - domain: z.string().optional(), + filter: z.string().optional().describe('The filter as entered in SmartConsole/SmartView for querying specific logs. Use function build_logs_query_filter before using this field.'), + 'time-frame': z.enum(['last-7-days', 'last-hour', 'today', 'last-24-hours', 'yesterday', 'this-week', 'this-month', 'last-30-days', 'all-time', 'custom']).describe('Specify the time frame to query logs. Use "custom" with custom-start and custom-end for specific date ranges.'), + 'custom-start': z.string().optional().describe('Start date in ISO8601 format (e.g., 2023-01-01T00:00:00Z). Only applicable when time-frame is "custom".'), + 'custom-end': z.string().optional().describe('End date in ISO8601 format (e.g., 2023-01-31T23:59:59Z). Only applicable when time-frame is "custom".'), + 'max-logs-per-request': z.number().min(1).max(100).optional().describe('Limit the number of logs to be retrieved per request (1-100, default: 100).'), + top: z.object({ + count: z.number().min(1).max(50).describe('The number of top results to retrieve (1-50, default: 10).'), + field: z.enum(['sources', 'destinations', 'services', 'actions', 'blades', 'origins', 'users', 'applications']).describe('The field on which the top command is executed to aggregate results.') + }).optional().describe('Top results configuration for aggregating logs by a specific field.'), + type: z.enum(['logs', 'audit']).optional().describe('Type of logs to return: "logs" for regular logs or "audit" for audit logs (default: logs).'), + 'log-servers': z.array(z.string()).optional().describe('List of IP addresses of log servers to query (default: all servers).'), + 'ignore-warnings': z.boolean().optional().describe('Whether to ignore warnings during query execution.'), + domain: z.string().optional().describe('Domain name for Multi-Domain environments.'), }, async (args: Record, extra: any) => { const ignoreWarnings = typeof args['ignore-warnings'] === 'boolean' ? args['ignore-warnings'] : undefined; - const newQuery = args['new-query'] as any; - const queryId = typeof args['query-id'] === 'string' ? args['query-id'] : undefined; const domain = typeof args.domain === 'string' && args.domain.trim() !== '' ? args.domain : undefined; const params: Record = {}; @@ -118,23 +111,53 @@ server.tool( params['ignore-warnings'] = ignoreWarnings; } - if (newQuery) { - const newQueryParams: Record = {}; - - if (newQuery.filter) newQueryParams.filter = newQuery.filter; - if (newQuery['time-frame']) newQueryParams['time-frame'] = newQuery['time-frame']; - if (newQuery['custom-start']) newQueryParams['custom-start'] = newQuery['custom-start']; - if (newQuery['custom-end']) newQueryParams['custom-end'] = newQuery['custom-end']; - if (newQuery['max-logs-per-request']) newQueryParams['max-logs-per-request'] = newQuery['max-logs-per-request']; - if (newQuery.top) newQueryParams.top = newQuery.top; - if (newQuery.type) newQueryParams.type = newQuery.type; - if (newQuery['log-servers']) newQueryParams['log-servers'] = newQuery['log-servers']; - - params['new-query'] = newQueryParams; + // Build new-query object from the exposed parameters + const newQueryParams: Record = {}; + + if (args.filter) newQueryParams.filter = args.filter; + + // Set time-frame, automatically override to 'custom' if custom dates are provided + let timeFrame = args['time-frame'] as string; + if (args['custom-start'] || args['custom-end']) { + timeFrame = 'custom'; } + newQueryParams['time-frame'] = timeFrame; + + if (args['custom-start']) newQueryParams['custom-start'] = args['custom-start']; + if (args['custom-end']) newQueryParams['custom-end'] = args['custom-end']; + if (args['max-logs-per-request']) newQueryParams['max-logs-per-request'] = args['max-logs-per-request']; + if (args.top) newQueryParams.top = args.top; + if (args.type) newQueryParams.type = args.type; + if (args['log-servers']) newQueryParams['log-servers'] = args['log-servers']; + + params['new-query'] = newQueryParams; + + const apiManager = SessionContext.getAPIManager(serverModule, extra); + const resp = await apiManager.callApi('POST', 'show-logs', params, domain); + return { content: [{ type: 'text', text: JSON.stringify(resp, null, 2) }] }; + } +); - if (queryId) { - params['query-id'] = queryId; +// Get next query page tool - for pagination using existing query ID +server.tool( + 'get_next_query_page', + 'Get the next page of results for an existing logs query using the query ID. Use this to paginate through results from a previous run_logs_query call.', + { + 'query-id': z.string(), + 'ignore-warnings': z.boolean().optional(), + domain: z.string().optional(), + }, + async (args: Record, extra: any) => { + const queryId = args['query-id'] as string; + const ignoreWarnings = typeof args['ignore-warnings'] === 'boolean' ? args['ignore-warnings'] : undefined; + const domain = typeof args.domain === 'string' && args.domain.trim() !== '' ? args.domain : undefined; + + const params: Record = { + 'query-id': queryId + }; + + if (ignoreWarnings !== undefined) { + params['ignore-warnings'] = ignoreWarnings; } const apiManager = SessionContext.getAPIManager(serverModule, extra); @@ -143,9 +166,104 @@ server.tool( } ); +// Build logs query filter - for constructing filter strings +server.tool( + 'build_logs_query_filter', + 'Build a query filter string using the Check Point query language. Supports field keywords, Boolean operators (AND, OR, NOT), wildcards, grouping with parentheses, and multiple values per field. Returns a filter string to use in run_logs_query. Examples: "blade:Firewall AND action:block", "source:(192.168.1.1 OR 192.168.1.2)", "(blade:IPS OR blade:VPN) AND NOT action:drop"', + { + conditions: z.array(z.object({ + field: z.enum([ + 'severity', 'app_risk', 'protection', 'protection_type', 'confidence_level', + 'action', 'blade', 'product', 'destination', 'dst', 'origin', 'orig', + 'service', 'source', 'src', 'user', 'rule' + ]).optional().describe('Field name to filter on. If omitted, searches across all fields for free text.'), + value: z.union([ + z.string(), + z.array(z.string()) + ]).describe('Value(s) to search for. Single string for one value, or array of strings for multiple values with OR between them. Use quotes for phrases with spaces. Supports wildcards: * (matches string) and ? (matches one character).'), + operator: z.enum(['AND', 'OR', 'NOT']).optional().describe('Boolean operator to combine with the next condition. If omitted, AND is implied. Use NOT to exclude conditions.'), + group: z.boolean().optional().describe('If true, wraps this condition in parentheses for grouping. Useful for complex queries with multiple OR conditions.') + })).describe('Array of filter conditions. Each condition can specify a field, value(s), operator, and grouping.') + }, + async (args: Record, extra: any) => { + const conditions = args.conditions as Array<{ + field?: string; + value: string | string[]; + operator?: string; + group?: boolean; + }>; + + if (!conditions || conditions.length === 0) { + return { + content: [{ + type: 'text', + text: 'Error: At least one condition is required to build a query filter.' + }] + }; + } + + // Helper function to format a value + const formatValue = (val: string): string => { + // Quote if contains spaces and not already quoted or contains parentheses + if (val.includes(' ') && !val.startsWith('"') && !val.endsWith('"') && !val.includes('(')) { + return `"${val}"`; + } + return val; + }; + + // Build the query string + const queryParts: string[] = []; + + for (let i = 0; i < conditions.length; i++) { + const condition = conditions[i]; + let part = ''; + + // Handle array of values (OR within field) + if (Array.isArray(condition.value)) { + const values = condition.value.map(formatValue).join(' OR '); + if (condition.field) { + part = `${condition.field}:(${values})`; + } else { + part = `(${values})`; + } + } else { + // Single value + const value = formatValue(condition.value); + if (condition.field) { + part = `${condition.field}:${value}`; + } else { + part = value; + } + } + + // Apply grouping if requested + if (condition.group && !part.startsWith('(')) { + part = `(${part})`; + } + + queryParts.push(part); + + // Add operator if not the last condition + if (i < conditions.length - 1) { + const operator = condition.operator || 'AND'; + queryParts.push(operator); + } + } + + const filterString = queryParts.join(' '); + + return { + content: [{ + type: 'text', + text: `Filter query: ${filterString}\n\nYou can now use this filter string with the run_logs_query tool by passing it as the 'filter' parameter.\n\nExamples of what this tool can build:\n- Field with single value: blade:Firewall AND action:block\n- Multiple IPs (free text): 192.168.2.133 10.19.136.101\n- Multiple values in field: source:(192.168.2.1 OR 192.168.2.2)\n- Grouped conditions: (blade:Firewall OR blade:IPS) AND NOT action:drop` + }] + }; + } +); + // Generic object tools server.tool( - 'show_gateways_and_servers', + 'management-logs__show_gateways_and_servers', 'Retrieve multiple gateway and server objects with optional filtering and pagination. Use this to get the currently installed policies only gateways.', { filter: z.string().optional(), @@ -173,7 +291,7 @@ server.tool( ); server.tool( - 'show_objects', + 'management-logs__show_objects', 'Retrieve multiple generic objects with filtering and pagination. Can use type (e.g host, service-tcp, network, address-range...) to get objects of a certain type.', { uids: z.array(z.string()).optional(), @@ -211,7 +329,7 @@ server.tool( // Tool: show_object server.tool( - 'show_object', + 'management-logs__show_object', 'Retrieve a generic object by UID.', { uid: z.string(), diff --git a/packages/management/package.json b/packages/management/package.json index b7e4da3..e641ff4 100644 --- a/packages/management/package.json +++ b/packages/management/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/quantum-management-mcp", - "version": "0.8.0", + "version": "0.8.1", "bin": { "quantum-management-mcp": "dist/index.js" }, diff --git a/packages/management/src/index.ts b/packages/management/src/index.ts index 027f5dd..b97f955 100644 --- a/packages/management/src/index.ts +++ b/packages/management/src/index.ts @@ -139,7 +139,7 @@ server.prompt( // --- TOOLS --- server.tool( - 'init', + 'management__init', 'Verify, login and initialize management connection. Use this tool on your first interaction with the server.', {}, async (args: Record, extra: any) => { @@ -742,7 +742,7 @@ server.tool( ); server.tool( - 'show_gateways_and_servers', + 'management__show_gateways_and_servers', 'Retrieve multiple gateway and server objects with optional filtering and pagination. Use this to get the currently installed policies only gateways.', { filter: z.string().optional(), @@ -1686,7 +1686,7 @@ server.tool( ); server.tool( - 'show_objects', + 'management__show_objects', 'Retrieve multiple generic objects with filtering and pagination. Can use type (e.g host, service-tcp, network, address-range...) to get objects of a certain type.', { uids: z.array(z.string()).optional(), @@ -1721,7 +1721,7 @@ server.tool( // Tool: show_object server.tool( - 'show_object', + 'management__show_object', 'Retrieve a generic object by UID.', { uid: z.string() diff --git a/packages/reputation-service/package.json b/packages/reputation-service/package.json index 2ea5202..dd1077c 100644 --- a/packages/reputation-service/package.json +++ b/packages/reputation-service/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/reputation-service-mcp", - "version": "0.2.4", + "version": "0.2.5", "bin": { "reputation-service-mcp": "dist/index.js" }, diff --git a/packages/reputation-service/src/index.ts b/packages/reputation-service/src/index.ts index 02aa726..94a9b76 100644 --- a/packages/reputation-service/src/index.ts +++ b/packages/reputation-service/src/index.ts @@ -37,20 +37,20 @@ const serverModule = createServerModule( server.tool( 'reputation_url', - 'Get a reputation of a URL', + 'Get a reputation of a URL or a domain', { - url: z.string(), + resource: z.string().describe('The URL or domain to check reputation for'), }, async (args: Record, extra: any) => { - if (typeof args.url !== 'string' || args.url.trim() === '') { - return { content: [{ type: 'text', text: JSON.stringify({ error: 'URL must be provided and cannot be empty' }, null, 2) }] }; + if (typeof args.resource !== 'string' || args.resource.trim() === '') { + return { content: [{ type: 'text', text: JSON.stringify({ error: 'Resource must be provided and cannot be empty' }, null, 2) }] }; } try { // Get settings from session context const settings = SessionContext.getSettings(serverModule, extra); const client = new ReputationClient(settings); - const result = await client.getReputation('url', args.url as string); + const result = await client.getReputation('url', args.resource as string); const verdict = getReputationVerdict(result.risk, result.confidence); return { diff --git a/packages/threat-prevention/package.json b/packages/threat-prevention/package.json index 8f2a197..85d6b1c 100644 --- a/packages/threat-prevention/package.json +++ b/packages/threat-prevention/package.json @@ -1,6 +1,6 @@ { "name": "@chkp/threat-prevention-mcp", - "version": "0.7.4", + "version": "0.7.5", "description": "Threat Prevention MCP server for Check Point products", "type": "module", "main": "dist/index.js", diff --git a/packages/threat-prevention/src/index.ts b/packages/threat-prevention/src/index.ts index 89f6feb..59b0195 100644 --- a/packages/threat-prevention/src/index.ts +++ b/packages/threat-prevention/src/index.ts @@ -37,7 +37,7 @@ const runApi = createApiRunner(serverModule); server.tool( - 'init', + 'threat-prevention__init', 'Verify, login and initialize management connection. Use this tool on your first interaction with the server.', {}, async (args: Record, extra: any) => { @@ -630,7 +630,7 @@ server.tool( ); server.tool( - 'show_gateways_and_servers', + 'threat-prevention__show_gateways_and_servers', 'Retrieve multiple gateway and server objects with optional filtering and pagination. Use this to get the currently installed policies only gateways.', { filter: z.string().optional(), @@ -658,7 +658,7 @@ server.tool( ); server.tool( - 'show_objects', + 'threat-prevention__show_objects', 'Retrieve multiple generic objects with filtering and pagination. Can use type (e.g host, service-tcp, network, address-range...) to get objects of a certain type.', { uids: z.array(z.string()).optional(), @@ -696,7 +696,7 @@ server.tool( // Tool: show_object server.tool( - 'show_object', + 'threat-prevention__show_object', 'Retrieve a generic object by UID.', { uid: z.string(), diff --git a/tsconfig.json b/tsconfig.json index e8fe57e..0c6a4c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,7 +31,8 @@ { "path": "./packages/gw-cli-connection-analysis" }, { "path": "./packages/gaia" }, { "path": "./packages/spark-management" }, - { "path": "./packages/documentation-tool" } + { "path": "./packages/documentation-tool" }, + { "path": "./packages/cpinfo-analysis" } ], "files": [] }