From 312151736c1953668cfadc7bf0d8289ac3acd363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Sun, 4 Jan 2026 12:19:40 +0800 Subject: [PATCH 01/15] fix(bing): include _EDGE_V cookie --- src/engines/bing/bing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index bd16e5c..d84be63 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -24,7 +24,7 @@ export async function searchBing(query: string, limit: number): Promise Date: Wed, 25 Feb 2026 14:03:25 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E7=94=A8=20puppeteer=20=E9=87=8D?= =?UTF-8?q?=E5=86=99=20Bing=20=E6=90=9C=E7=B4=A2=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 原实现使用 axios 直接请求,存在过期 cookie、错误选择器、无 URL 解码等问题 - 新实现通过 puppeteer-core 驱动系统 Edge/Chrome 浏览器进行搜索 - 添加会话预热机制,解决 cn.bing.com 对多词中文查询的反爬虫问题 - 手动 spawn 浏览器进程并通过 WebSocket 连接,避免与 MCP 服务器的 stdio 冲突 - 添加 decodeBingUrl 函数解码 Bing 重定向 URL - 每次搜索使用唯一临时目录,搜索完成后自动清理 --- package-lock.json | 822 ++++++++++++++++++++++++++++++++++++++- package.json | 3 +- src/engines/bing/bing.ts | 249 +++++++++--- 3 files changed, 1015 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index edb057c..ed4b043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "web-search", + "name": "open-websearch", "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "web-search", + "name": "open-websearch", "version": "1.2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.11.2", @@ -17,10 +17,11 @@ "express": "^4.18.2", "https-proxy-agent": "^7.0.6", "jsdom": "^26.1.0", - "npx": "^10.2.2" + "npx": "^10.2.2", + "puppeteer-core": "^24.37.5" }, "bin": { - "web-search": "build/index.js" + "open-websearch": "build/index.js" }, "devDependencies": { "@types/cors": "^2.8.19", @@ -127,6 +128,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -149,6 +151,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -414,6 +417,33 @@ "node": ">= 0.6" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", + "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/axios": { "version": "0.14.4", "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.4.tgz", @@ -566,6 +596,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -604,12 +644,48 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -625,6 +701,121 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", + "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", + "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -696,6 +887,15 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -773,6 +973,51 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -885,6 +1130,15 @@ "node": ">=18" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -899,9 +1153,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -921,6 +1175,20 @@ "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", "license": "MIT" }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -947,6 +1215,13 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1566079", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", + "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1018,6 +1293,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1039,6 +1320,15 @@ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1080,12 +1370,73 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1095,6 +1446,15 @@ "node": ">= 0.6" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -1121,6 +1481,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1192,18 +1553,53 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -1296,6 +1692,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1333,6 +1738,35 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1456,6 +1890,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1465,6 +1908,15 @@ "node": ">= 0.10" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -1601,6 +2053,12 @@ "node": ">= 0.6" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1616,6 +2074,15 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/npx": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/npx/-/npx-10.2.2.tgz", @@ -6225,6 +6692,38 @@ "wrappy": "1" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -6283,6 +6782,12 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -6292,6 +6797,15 @@ "node": ">=16.20.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -6305,11 +6819,49 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6319,6 +6871,24 @@ "node": ">=6" } }, + "node_modules/puppeteer-core": { + "version": "24.37.5", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.5.tgz", + "integrity": "sha512-ybL7iE78YPN4T6J+sPLO7r0lSByp/0NN6PvfBEql219cOnttoTFzCWKiBOjstXSqi/OKpwae623DWAsL7cn2MQ==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.0", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1566079", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -6357,6 +6927,15 @@ "node": ">= 0.8" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -6425,6 +7004,18 @@ "node": ">=v12.22.7" } }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -6586,6 +7177,54 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -6594,12 +7233,93 @@ "node": ">= 0.8" } }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -6650,6 +7370,12 @@ "node": ">=18" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6663,6 +7389,12 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -6736,6 +7468,12 @@ "node": ">=18" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -6792,6 +7530,23 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -6799,9 +7554,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -6834,10 +7589,57 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/zod": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 920716e..c7781ea 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "express": "^4.18.2", "https-proxy-agent": "^7.0.6", "jsdom": "^26.1.0", - "npx": "^10.2.2" + "npx": "^10.2.2", + "puppeteer-core": "^24.37.5" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index d84be63..27fe781 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -1,54 +1,201 @@ -import axios from 'axios'; +import puppeteer, { type Browser } from 'puppeteer-core'; import * as cheerio from 'cheerio'; +import { existsSync, mkdtempSync, rmSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { spawn, type ChildProcess } from 'child_process'; +import { createServer } from 'net'; import { SearchResult } from '../../types.js'; -export async function searchBing(query: string, limit: number): Promise { - let allResults: SearchResult[] = []; - let pn = 0; - - while (allResults.length < limit) { - const response = await axios.get('https://www.bing.com/search', { - params: { - q: query, - first: 1 + pn * 10 - }, - headers: { - "authority": "www.bing.com", - "ect": "3g", - "pragma": "no-cache", - "sec-ch-ua-arch": "\"x86\"", - "sec-ch-ua-bitness": "\"64\"", - "sec-ch-ua-full-version": "\"112.0.5615.50\"", - "sec-ch-ua-full-version-list": "\"Chromium\";v=\"112.0.5615.50\", \"Google Chrome\";v=\"112.0.5615.50\", \"Not:A-Brand\";v=\"99.0.0.0\"", - "sec-ch-ua-model": "\"\"", - "sec-ch-ua-platform-version": "\"15.0.0\"", - "sec-fetch-user": "?1", - "upgrade-insecure-requests": "1", - "Cookie": "_EDGE_V=1; MUID=3727DBB14FD763511D80CDBD4ED262EF; MSPTC=5UlNf4UsLqV53oFqqdHiR26FwDDL8zSW3kC74kIJQfM; _EDGE_S=SID=132F08F578E06F832D931EE779E16E2D; MUIDB=3727DBB14FD763511D80CDBD4ED262EF; SRCHD=AF=NOFORM; SRCHUID=V=2&GUID=B3AFD0E41DB649E39803C690946C3B65&dmnchg=1; ak_bmsc=578AE2B7DA55FA9F332ADCDFBA0B9B64~000000000000000000000000000000~YAAQZCg0F9XLkYGXAQAAjywwkhxcD6Pm2nguBmpB14hnmCR3kz9Mfau5cZ7pwHxdU2Uog9+6hOkBmzpOV3UoTOhi52nB725xM7zN90mRDv0zQtJdO/llaKlt2zqTmB4F5kd+GzPjXLAN4Zmj4KwpAjLK1T4TexH/9WlQTkRamdJTKuR47IZWHHebqsbNqHoYncHhxICO9Rnu51vhlps/rrhPBtgPgbrQnDfr6YzAQWmSqc5g9hk03sM9nnWUyVbRV0ZVsgke7BCYX5V1JD5L0Zf8/FWdntBpjpd2IcmehBz38ChGThPrBEWNCZQbCS6lE4OaQanrrdmBHf/r5YEf2LeIqZy0bJGIiSQaSh6d7KFO2haTQk/JscZAs+V5kNsAOxIGreRve+E=; _UR=QS=0&TQS=0&Pn=0; BFBUSR=BFBHP=0; SRCHUSR=DOB=20250621&DS=1; _Rwho=u=d&ts=2025-06-21; ipv6=hit=1750507922628&t=4; BFPRResults=FirstPageUrls=C5E678E900F98310F0D3DB1F3EB96D99%2CB5A20FAE72B0C3019A56409EAC7AF3FB%2C7A44A77FF42EDF11CC9BF5CFE08B179A%2C6ED615E5E634BD5AFC7BB2A0A77F8FF8%2CA993E7AAF4890BEC06882621CA376D00%2C49CF0FC3C203D5E918A76258506B0CF4%2C7F03D5026C1D046F66B11D525095BF8B%2C058BB67A6B7F15E58D3A19B897BC57F8%2C1B886024FDE703428D24A41AFA1E62AF%2C5A8B56DC0AE03A8B94643DEA2A22DBAC&FPIG=05F126AA95514CF5AD5E33E4AEBA474D; _HPVN=CS=eyJQbiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiUCJ9LCJTYyI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiSCJ9LCJReiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiVCJ9LCJBcCI6dHJ1ZSwiTXV0ZSI6dHJ1ZSwiTGFkIjoiMjAyNS0wNi0yMVQwMDowMDowMFoiLCJJb3RkIjowLCJHd2IiOjAsIlRucyI6MCwiRGZ0IjpudWxsLCJNdnMiOjAsIkZsdCI6MCwiSW1wIjoxNSwiVG9ibiI6MH0=; _C_ETH=1; _RwBf=r=0&ilt=15&ihpd=1&ispd=14&rc=36&rb=0&rg=200&pc=36&mtu=0&rbb=0&clo=0&v=15&l=2025-06-21T07:00:00.0000000Z&lft=0001-01-01T00:00:00.0000000&aof=0&ard=0001-01-01T00:00:00.0000000&rwdbt=0&rwflt=0&rwaul2=0&g=&o=2&p=&c=&t=0&s=0001-01-01T00:00:00.0000000+00:00&ts=2025-06-21T11:36:08.7064260+00:00&rwred=0&wls=&wlb=&wle=&ccp=&cpt=&lka=0&lkt=0&aad=0&TH=&cid=0&gb=; _SS=SID=132F08F578E06F832D931EE779E16E2D&R=36&RB=0&GB=0&RG=200&RP=36; SRCHHPGUSR=SRCHLANG=zh-Hans&IG=63A0A44F5D2F4499AD165A366D073C03&DM=0&BRW=N&BRH=T&CW=1202&CH=1289&SCW=1185&SCH=2279&DPR=1.0&UTC=480&HV=1750505768&HVE=notFound&WTS=63886101120&PV=15.0.0&PRVCW=1202&PRVCH=1289&EXLTT=13; SRCHHPGUSR=SRCHLANG=en&IG=9A53F826E9C9432497327CA995144E14&DM=0&BRW=N&BRH=T&CW=1202&CH=1289&SCW=1185&SCH=2279&DPR=1.0&UTC=480&HV=1750505768&HVE=notFound&WTS=63886101120&PV=15.0.0&PRVCW=1202&PRVCH=1289&EXLTT=13", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", - "Accept": "*/*", - "Host": "cn.bing.com", - "Connection": "keep-alive" +/** + * 解码 Bing 重定向 URL,提取实际目标地址。 + * Bing URL 格式: https://www.bing.com/ck/a?...&u=a1 + * 参数 'u' 的值以 'a1' 开头,后接 Base64 编码的原始 URL。 + */ +function decodeBingUrl(bingUrl: string): string { + try { + const url = new URL(bingUrl); + const encodedUrl = url.searchParams.get('u'); + if (!encodedUrl) { + return bingUrl; + } + const base64Part = encodedUrl.substring(2); + const decodedUrl = Buffer.from(base64Part, 'base64').toString('utf-8'); + if (decodedUrl.startsWith('http')) { + return decodedUrl; + } + return bingUrl; + } catch { + return bingUrl; + } +} + +let cachedBrowserPath: string | null = null; + +function getBrowserPath(): string { + if (cachedBrowserPath) return cachedBrowserPath; + + const candidates: string[] = []; + + // Windows 硬编码常见路径(MCP 环境下环境变量可能缺失时的后备方案) + candidates.push('C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push('C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'); + candidates.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'); + + // 基于环境变量的路径 + const pf86 = process.env['PROGRAMFILES(X86)']; + const pf = process.env['PROGRAMFILES']; + const localAppData = process.env['LOCALAPPDATA']; + if (pf86) { + candidates.push(pf86 + '\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push(pf86 + '\\Google\\Chrome\\Application\\chrome.exe'); + } + if (pf) { + candidates.push(pf + '\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push(pf + '\\Google\\Chrome\\Application\\chrome.exe'); + } + if (localAppData) { + candidates.push(localAppData + '\\Google\\Chrome\\Application\\chrome.exe'); + } + + // Linux/macOS 路径 + candidates.push('/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/microsoft-edge'); + candidates.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'); + candidates.push('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'); + + // 去重 + const unique = [...new Set(candidates)]; + for (const p of unique) { + if (existsSync(p)) { + console.error(`[bing] Found browser: ${p}`); + cachedBrowserPath = p; + return p; + } + } + throw new Error('未找到 Chromium 内核浏览器,请安装 Chrome 或 Edge。'); +} + +/** 查找可用的 TCP 端口 */ +function findFreePort(): Promise { + return new Promise((resolve, reject) => { + const srv = createServer(); + srv.listen(0, '127.0.0.1', () => { + const addr = srv.address(); + if (addr && typeof addr === 'object') { + const port = addr.port; + srv.close(() => resolve(port)); + } else { + srv.close(() => reject(new Error('Could not determine port'))); } }); + srv.on('error', reject); + }); +} - const $ = cheerio.load(response.data); - const results: SearchResult[] = []; +async function launchBrowser(): Promise<{ browser: Browser; tempDir: string; childProc: ChildProcess }> { + const browserPath = getBrowserPath(); + const tempDir = mkdtempSync(join(tmpdir(), 'bing-search-')); + const port = await findFreePort(); - $('#b_content').children() - .find('#b_results').children() - .each((i, element) => { - const titleElement = $(element).find('h2'); - const linkElement = $(element).find('a'); - const snippetElement = $(element).find('p').first(); + console.error(`[bing] Spawning browser on port ${port}, profile: ${tempDir}`); - if (titleElement.length && linkElement.length) { - const url = linkElement.attr('href'); - if (url && url.startsWith('http')) { + // 手动启动浏览器进程,完全隔离 stdio, + // 避免与 MCP 服务器的管道化 stdin/stdout 冲突。 + const childProc = spawn(browserPath, [ + `--remote-debugging-port=${port}`, + '--headless=new', + `--user-data-dir=${tempDir}`, + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-gpu', + '--disable-dev-shm-usage', + '--disable-extensions', + '--no-first-run', + '--no-default-browser-check', + ], { + stdio: ['ignore', 'ignore', 'pipe'], // 仅保留 stderr + detached: false, + windowsHide: true, + }); + + // 轮询调试端点,等待浏览器就绪 + const debugUrl = `http://127.0.0.1:${port}/json/version`; + let wsUrl: string | null = null; + for (let i = 0; i < 30; i++) { + await new Promise(r => setTimeout(r, 200)); + try { + const resp = await fetch(debugUrl); + const data = await resp.json() as { webSocketDebuggerUrl?: string }; + if (data.webSocketDebuggerUrl) { + wsUrl = data.webSocketDebuggerUrl; + break; + } + } catch { + // 浏览器尚未就绪 + } + } + + if (!wsUrl) { + childProc.kill(); + throw new Error('浏览器启动失败:无法获取 WebSocket URL'); + } + + console.error(`[bing] Browser ready, connecting via ${wsUrl}`); + const browser = await puppeteer.connect({ browserWSEndpoint: wsUrl }); + return { browser, tempDir, childProc }; +} + +/** + * 预热请求:先访问 cn.bing.com 建立有效的搜索会话。 + * cn.bing.com 对多词中文查询需要有效的会话 cookie, + * 否则会返回随机的无关内容。 + */ +async function warmUpSession(browser: Browser): Promise { + const page = await browser.newPage(); + try { + await page.goto('https://cn.bing.com/search?q=test', { waitUntil: 'networkidle2', timeout: 15000 }); + await new Promise(r => setTimeout(r, 500)); + } finally { + await page.close(); + } +} + +export async function searchBing(query: string, limit: number): Promise { + const { browser, tempDir, childProc } = await launchBrowser(); + + try { + // 预热会话,从 cn.bing.com 获取有效 cookie + await warmUpSession(browser); + + let allResults: SearchResult[] = []; + let pn = 0; + + while (allResults.length < limit) { + const page = await browser.newPage(); + + const searchUrl = `https://cn.bing.com/search?q=${encodeURIComponent(query)}&first=${1 + pn * 10}`; + await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 15000 }); + await new Promise(r => setTimeout(r, 1000)); + + const html = await page.content(); + await page.close(); + + const $ = cheerio.load(html); + const results: SearchResult[] = []; + + $('#b_results h2').each((i, element) => { + const linkElement = $(element).find('a').first(); + if (linkElement.length) { + const rawUrl = linkElement.attr('href'); + if (rawUrl && rawUrl.startsWith('http')) { + const url = decodeBingUrl(rawUrl); + const parentLi = $(element).closest('li'); + const snippetElement = parentLi.find('p').first(); + const sourceElement = parentLi.find('.b_tpcn'); - const sourceElement = $(element).find('.b_tpcn'); results.push({ - title: titleElement.text(), + title: linkElement.text().trim(), url: url, description: snippetElement.text().trim() || '', source: sourceElement.text().trim() || '', @@ -58,15 +205,21 @@ export async function searchBing(query: string, limit: number): Promise {}); + childProc.kill(); + // 清理临时配置文件目录 + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } - - return allResults.slice(0, limit); // 截取最多 limit 个 } From d929ea9a2df8cf61cf08f5a47026d3f806e73e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 15:11:55 +0800 Subject: [PATCH 03/15] =?UTF-8?q?refactor(bing):=20WMI=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E7=BB=95=E8=BF=87Job=20Object?= =?UTF-8?q?=E3=80=81=E5=A4=8D=E7=94=A8=E4=BC=9A=E8=AF=9D=E3=80=81=E5=BA=94?= =?UTF-8?q?=E7=94=A8Copilot=20review=E5=BB=BA=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Windows下改用WMI创建浏览器进程,避免VS Code Job Object终止子进程 - Linux/macOS下使用detached spawn - 缓存浏览器会话,跨搜索复用而非每次新建 - 进程退出时自动清理浏览器与临时目录 - [Copilot review #3] fetch轮询添加AbortController超时 - [Copilot review #6] Linux spawn添加error事件监听 - [Copilot review #7] puppeteer.connect失败时清理资源 - [Copilot review #1,#2,#4,#5] 不适用/已解决,注释说明原因 --- src/engines/bing/bing.ts | 152 ++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 27 deletions(-) diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index 27fe781..ff30034 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -3,7 +3,7 @@ import * as cheerio from 'cheerio'; import { existsSync, mkdtempSync, rmSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; -import { spawn, type ChildProcess } from 'child_process'; +import { spawn, execFileSync } from 'child_process'; import { createServer } from 'net'; import { SearchResult } from '../../types.js'; @@ -93,18 +93,17 @@ function findFreePort(): Promise { }); } -async function launchBrowser(): Promise<{ browser: Browser; tempDir: string; childProc: ChildProcess }> { +/** 缓存的浏览器会话,跨搜索复用 */ +let cachedSession: { browser: Browser; tempDir: string; browserPid?: number; warmedUp: boolean } | null = null; + +async function launchBrowser(): Promise<{ browser: Browser; tempDir: string; browserPid?: number }> { const browserPath = getBrowserPath(); const tempDir = mkdtempSync(join(tmpdir(), 'bing-search-')); const port = await findFreePort(); - console.error(`[bing] Spawning browser on port ${port}, profile: ${tempDir}`); - - // 手动启动浏览器进程,完全隔离 stdio, - // 避免与 MCP 服务器的管道化 stdin/stdout 冲突。 - const childProc = spawn(browserPath, [ - `--remote-debugging-port=${port}`, + const args = [ '--headless=new', + `--remote-debugging-port=${port}`, `--user-data-dir=${tempDir}`, '--no-sandbox', '--disable-setuid-sandbox', @@ -113,37 +112,125 @@ async function launchBrowser(): Promise<{ browser: Browser; tempDir: string; chi '--disable-extensions', '--no-first-run', '--no-default-browser-check', - ], { - stdio: ['ignore', 'ignore', 'pipe'], // 仅保留 stderr - detached: false, - windowsHide: true, - }); + ]; + + console.error(`[bing] Spawning browser on port ${port}, profile: ${tempDir}`); + + let browserPid: number | undefined; - // 轮询调试端点,等待浏览器就绪 + if (process.platform === 'win32') { + // Windows: 通过 WMI (Win32_Process.Create) 创建浏览器进程。 + // VS Code 的 MCP 服务器环境使用 Job Object 管控子进程, + // 导致 spawn/exec 创建的浏览器被立即终止(退出码 0)。 + // WMI 创建的进程完全独立于父进程的 Job Object,不受此限制。 + const cmdLine = `"${browserPath}" ${args.join(' ')}`; + const psScript = [ + `$r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, + `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'}`, + `; if($r.ReturnValue -eq 0){$r.ProcessId}else{throw "WMI error: $($r.ReturnValue)"}`, + ].join(' '); + try { + const output = execFileSync('powershell.exe', [ + '-NoProfile', '-NonInteractive', '-Command', psScript, + ], { encoding: 'utf8', windowsHide: true, timeout: 10000 }); + browserPid = parseInt(output.trim()); + console.error(`[bing] Browser started via WMI, PID: ${browserPid}`); + } catch (err: any) { + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} + throw new Error(`通过 WMI 启动浏览器失败: ${err.message}`); + } + } else { + // Linux/macOS: 直接启动浏览器 + const child = spawn(browserPath, args, { + stdio: 'ignore', + detached: true, + }); + // [Copilot review #6] 监听 error 事件,避免 spawn 失败(如 ENOENT)时 + // 未处理的 error 事件导致进程崩溃 + child.on('error', () => {}); + child.unref(); + browserPid = child.pid; + } + + // [Copilot review #2] Copilot 建议用 --remote-debugging-port=0 + stderr 解析取代端口轮询, + // 但当前架构(Windows WMI / Linux detached spawn)均无法获取 stderr,只能轮询。 const debugUrl = `http://127.0.0.1:${port}/json/version`; let wsUrl: string | null = null; for (let i = 0; i < 30; i++) { await new Promise(r => setTimeout(r, 200)); + // [Copilot review #3] 为每次 fetch 添加超时,避免端口被劫持或连接卡住时 + // 单次请求无限挂起,使 30 次重试上限失效。 + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 2000); try { - const resp = await fetch(debugUrl); + const resp = await fetch(debugUrl, { signal: controller.signal }); const data = await resp.json() as { webSocketDebuggerUrl?: string }; if (data.webSocketDebuggerUrl) { wsUrl = data.webSocketDebuggerUrl; break; } } catch { - // 浏览器尚未就绪 + // 浏览器尚未就绪或请求超时 + } finally { + clearTimeout(timeoutId); } } if (!wsUrl) { - childProc.kill(); + if (browserPid) try { process.kill(browserPid); } catch {} + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} throw new Error('浏览器启动失败:无法获取 WebSocket URL'); } console.error(`[bing] Browser ready, connecting via ${wsUrl}`); - const browser = await puppeteer.connect({ browserWSEndpoint: wsUrl }); - return { browser, tempDir, childProc }; + // [Copilot review #7] puppeteer.connect 失败时清理子进程和临时目录, + // 避免 launchBrowser 抛出后调用方无法清理的资源泄漏 + try { + const browser = await puppeteer.connect({ browserWSEndpoint: wsUrl }); + return { browser, tempDir, browserPid }; + } catch (err) { + if (browserPid) try { process.kill(browserPid); } catch {} + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} + throw err; + } +} + +/** 获取或复用浏览器会话 */ +async function getBrowser(): Promise<{ browser: Browser; tempDir: string; browserPid?: number }> { + // 检查已缓存的浏览器是否仍然可用 + if (cachedSession) { + try { + // 尝试获取版本信息来验证连接是否正常 + await cachedSession.browser.version(); + return cachedSession; + } catch { + console.error('[bing] Cached browser session is dead, relaunching...'); + cleanupSession(cachedSession); + cachedSession = null; + } + } + + const session = await launchBrowser(); + cachedSession = { ...session, warmedUp: false }; + + // 进程退出时清理浏览器 + const cleanup = () => { + if (cachedSession) { + cleanupSession(cachedSession); + cachedSession = null; + } + }; + process.once('exit', cleanup); + process.once('SIGINT', cleanup); + process.once('SIGTERM', cleanup); + + return cachedSession; +} + +function cleanupSession(session: { browser: Browser; tempDir: string; browserPid?: number }) { + try { session.browser.close(); } catch {} + if (session.browserPid) try { process.kill(session.browserPid); } catch {} + try { rmSync(session.tempDir, { recursive: true, force: true }); } catch {} } /** @@ -162,15 +249,24 @@ async function warmUpSession(browser: Browser): Promise { } export async function searchBing(query: string, limit: number): Promise { - const { browser, tempDir, childProc } = await launchBrowser(); + const { browser } = await getBrowser(); try { - // 预热会话,从 cn.bing.com 获取有效 cookie - await warmUpSession(browser); + // 首次使用时预热会话,从 cn.bing.com 获取有效 cookie + if (cachedSession && !cachedSession.warmedUp) { + await warmUpSession(browser); + cachedSession.warmedUp = true; + } let allResults: SearchResult[] = []; let pn = 0; + // [Copilot review #1] Copilot 建议为每个 page 添加 try/finally, + // 但异常会直接传播到外层 finally 的 browser.close(),所有页面随之销毁,无需单独处理。 + // [Copilot review #4] Copilot 建议按环境变量条件化 --no-sandbox, + // 但 --no-sandbox 是 headless 自动化的标准做法,且 Docker 容器内通常需要,条件化会增加复杂度。 + // [Copilot review #5] Copilot 建议 kill() 添加 try/catch, + // 当前代码已使用 try { process.kill(browserPid); } catch {} 容错处理。 while (allResults.length < limit) { const page = await browser.newPage(); @@ -216,10 +312,12 @@ export async function searchBing(query: string, limit: number): Promise {}); - childProc.kill(); - // 清理临时配置文件目录 - try { rmSync(tempDir, { recursive: true, force: true }); } catch {} + } catch (err) { + // 搜索出错时销毁缓存的浏览器会话,下次重新启动 + if (cachedSession) { + cleanupSession(cachedSession); + cachedSession = null; + } + throw err; } } From 1a22143492b4853e3d6221a1bb65228fa5557e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 16:45:12 +0800 Subject: [PATCH 04/15] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=99=BE?= =?UTF-8?q?=E5=BA=A6=E6=90=9C=E7=B4=A2=E8=A2=AB=E5=B0=81=E7=A6=81=E5=92=8C?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E5=90=8D=E5=A4=A7=E5=B0=8F=E5=86=99=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20(Issue=20#19,=20#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #29 - 百度搜索被封禁: - 将百度引擎从 axios HTTP 改为 puppeteer 共享浏览器 - 添加反自动化检测 (disable-blink-features, CDP隐藏webdriver) - 热门关键词(天气预报/Python教程等)现可正常返回结果 Issue #19 - 引擎名大小写敏感: - 移除 z.enum() 约束,改用 z.string() + transform 归一化 - 支持任意大小写输入(如 Bing/BAIDU/DuckDuckGo) - MCPO 代理等发送首字母大写引擎名不再报错 架构优化: - 新增全局共享浏览器模块 (engines/shared/browser.ts) - Bing 和 Baidu 共用同一浏览器实例,减少资源开销 - Bing 引擎代码从约330行精简至约110行 测试: - 新增百度可用性测试 (test-baidu-availability) - 新增引擎名大小写测试 (test-engine-case) - 新增搜索结果相关性测试 (test-search-relevance) --- README-zh.md | 13 +- README.md | 108 +++++++++- package-lock.json | 3 +- package.json | 3 +- src/config.ts | 6 +- src/engines/baidu/baidu.ts | 118 +++++----- src/engines/bing/bing.ts | 237 +-------------------- src/engines/juejin/juejin.ts | 4 +- src/engines/linuxdo/fetchLinuxDoArticle.ts | 24 +-- src/engines/shared/browser.ts | 214 +++++++++++++++++++ src/test/fetchLinuxDoArticleTests.ts | 58 +++++ src/test/test-baidu-availability.ts | 90 ++++++++ src/test/test-baidu.ts | 3 +- src/test/test-bing.ts | 3 +- src/test/test-description-length.ts | 89 ++++++++ src/test/test-engine-case.ts | 55 +++++ src/test/test-search-relevance.ts | 95 +++++++++ src/tools/setupTools.ts | 23 +- 18 files changed, 815 insertions(+), 331 deletions(-) create mode 100644 src/engines/shared/browser.ts create mode 100644 src/test/fetchLinuxDoArticleTests.ts create mode 100644 src/test/test-baidu-availability.ts create mode 100644 src/test/test-description-length.ts create mode 100644 src/test/test-engine-case.ts create mode 100644 src/test/test-search-relevance.ts diff --git a/README-zh.md b/README-zh.md index 384947f..b237f54 100644 --- a/README-zh.md +++ b/README-zh.md @@ -3,6 +3,7 @@ # Open-WebSearch MCP 服务器 [![ModelScope](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Aas-ee/3af09e0f4c7821fb2e9acb96483a5ff0/raw/badge.json&color=%23de5a16)](https://www.modelscope.cn/mcp/servers/Aasee1/open-webSearch) +[![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Aas-ee/open-webSearch)](https://archestra.ai/mcp-catalog/aas-ee__open-websearch) [![smithery badge](https://smithery.ai/badge/@Aas-ee/open-websearch)](https://smithery.ai/server/@Aas-ee/open-websearch) ![Version](https://img.shields.io/github/v/release/Aas-ee/open-websearch) ![License](https://img.shields.io/github/license/Aas-ee/open-websearch) @@ -238,16 +239,12 @@ npm run build { "mcpServers": { "web-search": { - "transport": { - "type": "streamableHttp", - "url": "http://localhost:3000/mcp" - } + "type": "http", + "url": "http://localhost:3000/mcp" }, "web-search-sse": { - "transport": { - "type": "sse", - "url": "http://localhost:3000/sse" - } + "type": "sse", + "url": "http://localhost:3000/sse" } } } diff --git a/README.md b/README.md index 7c6328a..449f1e4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,97 @@ A Model Context Protocol (MCP) server based on multi-engine search results, supporting free web search without API keys. + +
+ Click to expand simple demo (deepseek-v3) + +--- + +### 🧩 Demo Start + +### prompt: + +> Use the websearch tool to search "Open-WebSearch MCP", using the csdn engine, query 20 records, tell me the engine returned by the tool, along with related info, then use the URL to fetch articles by author Aasee (if there are multiple, query them sequentially, not simultaneously). Format the output properly. + +--- + +### assistant: + +I will use the MCP_search tool to query "Open-WebSearch MCP" using the CSDN engine to fetch 20 records. + + + +### Query Result Analysis + +1. **Search Engine Used**: CSDN +2. **Total Records**: 20 + +### Articles by Author Aasee +The search results show 2 articles by author "Aasee.": + +1. **Article Title**: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" + **URL**: https://blog.csdn.net/weixin_45801664/article/details/149000138 + **Summary**: This project provides free, controllable web search capabilities for AI plugin development, supporting Bing, Baidu, CSDN multi-engine combined search... + +2. **Article Title**: "Web Seach 搜索 MCP 启动!" + **URL**: https://blog.csdn.net/weixin_45801664/article/details/148813614 + **Summary**: Open-source Web Search MCP server Open-WebSearch launched, solving the problem of AI models unable to access latest web information... + +### Fetching First Article Content +I will first fetch the full content of the first article: + + + +# Search Result Analysis + +## Engine Info Returned by Tool +Used the CSDN search engine for the query, returning 20 related records. + +## Articles by Author Aasee + +### Article 1: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" +``` +🚀 Open-source AI Web Search Tool: Open-WebSearch MCP New Upgrade, Multi-engine + Streaming Response! + +💡 "Let your AI plugins truly access the web" — No API Key needed, controllable search results, ready to use! + +Project Highlights: +✅ Multi-engine Real-time Search: Supports Bing, Baidu, CSDN, DuckDuckGo, Exa, Brave +✅ Streaming Response (MCP Protocol): Compatible with Claude plugin (MCP) protocol +✅ Article Content Extraction: Supports extracting full CSDN blog articles +✅ Completely Free & No API Key Required +✅ One-click Docker Deployment + +Installation: +- Git clone: git clone https://github.com/Aas-ee/open-webSearch +- Or use Docker: docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest + +MCP Tool Support: +🔍 search: Multi-engine search tool +📄 fetchCsdnArticle: Extract CSDN article full text +🧪 fetchLinuxDoArticle: Fetch Linux.do articles (in development) + +Project Links: +🧩 GitHub: https://github.com/Aas-ee/open-webSearch +📦 Docker: ghcr.io/aas-ee/open-web-search:latest +``` + +### Article 2: "Web Seach 搜索 MCP 启动!" +``` +Open-source Web Search MCP server Open-WebSearch launched, solving the problem of AI models +unable to access latest web information. +The project provides free Baidu search results API, supports structured JSON return format, +compatible with Claude and other AI tools' MCP protocol. +Users only need simple installation to use, no API keys required... +``` + +Would you like me to look up other articles by author Aasee? I can continue searching for you. + +### ✅ Demo End + +--- +
+ ## Features - Web search using multi-engine results @@ -61,6 +152,7 @@ $env:DEFAULT_SEARCH_ENGINE="duckduckgo"; $env:ENABLE_CORS="true"; npx open-webse set MODE=stdio && set DEFAULT_SEARCH_ENGINE=duckduckgo && npx open-websearch@latest # Cross-platform (requires cross-env, Used for local development) +# Global install npm install -g open-websearch npx cross-env DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true open-websearch ``` @@ -92,6 +184,10 @@ USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 npx open-websearch@latest DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 PORT=8080 npx open-websearch@latest ``` +**Windows Users Note:** +- In PowerShell, use `$env:VAR="value"; ` syntax +- For local development, it's recommended to use `npx cross-env` for cross-platform compatibility + ### Local Installation 1. Clone or download this repository @@ -145,16 +241,12 @@ npm run build { "mcpServers": { "web-search": { - "transport": { - "type": "streamableHttp", - "url": "http://localhost:3000/mcp" - } + "type": "http", + "url": "http://localhost:3000/mcp" }, "web-search-sse": { - "transport": { - "type": "sse", - "url": "http://localhost:3000/sse" - } + "type": "sse", + "url": "http://localhost:3000/sse" } } } diff --git a/package-lock.json b/package-lock.json index ed4b043..820883b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "https-proxy-agent": "^7.0.6", "jsdom": "^26.1.0", "npx": "^10.2.2", - "puppeteer-core": "^24.37.5" + "puppeteer-core": "^24.37.5", + "zod": "^3.23.0" }, "bin": { "open-websearch": "build/index.js" diff --git a/package.json b/package.json index c7781ea..d27ba85 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "https-proxy-agent": "^7.0.6", "jsdom": "^26.1.0", "npx": "^10.2.2", - "puppeteer-core": "^24.37.5" + "puppeteer-core": "^24.37.5", + "zod": "^3.23.0" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/src/config.ts b/src/config.ts index d8fae2c..ccc95b3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,6 +12,8 @@ export interface AppConfig { corsOrigin: string; // Server configuration (determined by MODE env var: 'both', 'http', or 'stdio') enableHttpServer: boolean; + // 搜索结果描述最大长度(undefined = 不限制) + maxDescriptionLength?: number; } // Read from environment variables or use defaults @@ -30,7 +32,9 @@ export const config: AppConfig = { corsOrigin: process.env.CORS_ORIGIN || '*', // Server configuration - determined by MODE environment variable // Modes: 'both' (default), 'http', 'stdio' - enableHttpServer: process.env.MODE ? ['both', 'http'].includes(process.env.MODE) : true + enableHttpServer: process.env.MODE ? ['both', 'http'].includes(process.env.MODE) : true, + // 搜索结果描述最大长度 + maxDescriptionLength: process.env.MAX_DESCRIPTION_LENGTH ? parseInt(process.env.MAX_DESCRIPTION_LENGTH, 10) : undefined }; // Valid search engines list diff --git a/src/engines/baidu/baidu.ts b/src/engines/baidu/baidu.ts index 13ce471..646e925 100644 --- a/src/engines/baidu/baidu.ts +++ b/src/engines/baidu/baidu.ts @@ -1,72 +1,78 @@ -import axios from 'axios'; import * as cheerio from 'cheerio'; import { SearchResult } from '../../types.js'; +import { getSharedBrowser, destroySharedBrowser } from '../shared/browser.js'; export async function searchBaidu(query: string, limit: number): Promise { - let allResults: SearchResult[] = []; - let pn = 0; + try { + const browser = await getSharedBrowser(); + let allResults: SearchResult[] = []; + let pn = 0; - while (allResults.length < limit) { - const response = await axios.get('https://www.baidu.com/s', { - params: { - wd: query, - pn: pn.toString(), - ie: "utf-8", - mod: "1", - isbd: "1", - isid: "f7ba1776007bcf9e", - oq: query, - tn: "88093251_62_hao_pg", - usm: "1", - fenlei: "256", - rsv_idx: "1", - rsv_pq: "f7ba1776007bcf9e", - rsv_t: "8179fxGiNMUh/0dXHrLsJXPlKYbkj9S5QH6rOLHY6pG6OGQ81YqzRTIGjjeMwEfiYQTSiTQIhCJj", - bs: query, - rsv_sid: undefined, - _ss: "1", - f4s: "1", - csor: "5", - _cr1: "30385", - }, - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + while (allResults.length < limit) { + const page = await browser.newPage(); + + // 通过 CDP 隐藏 webdriver/自动化特征,绕过百度反爬检测 + const client = await page.createCDPSession(); + await client.send('Page.addScriptToEvaluateOnNewDocument', { + source: ` + Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); + delete navigator.__proto__.webdriver; + ` + }); + + const searchUrl = `https://www.baidu.com/s?wd=${encodeURIComponent(query)}&pn=${pn}&ie=utf-8`; + + await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 15000 }); + await new Promise(r => setTimeout(r, 1000)); + + const html = await page.content(); + await page.close(); + + const $ = cheerio.load(html); + + // 检测百度安全验证页面 + const title = $('title').text(); + if (title.includes('安全验证')) { + console.error('⚠️ Baidu security verification detected, no results for this page.'); + break; } - }); - const $ = cheerio.load(response.data); - const results: SearchResult[] = []; + const results: SearchResult[] = []; - $('#content_left').children().each((i, element) => { - const titleElement = $(element).find('h3'); - const linkElement = $(element).find('a'); - const snippetElement = $(element).find('.cos-row').first(); + $('#content_left').children().each((i, element) => { + const titleElement = $(element).find('h3'); + const linkElement = $(element).find('a'); + const snippetElement = $(element).find('.cos-row').first(); - if (titleElement.length && linkElement.length) { - const url = linkElement.attr('href'); - if (url && url.startsWith('http')) { - const snippetElementBaidu = $(element).find('.c-font-normal.c-color-text').first(); - const sourceElement = $(element).find('.cosc-source'); - results.push({ - title: titleElement.text(), - url: url, - description: snippetElementBaidu.attr('aria-label') || snippetElement.text().trim() || '', - source: sourceElement.text().trim() || '', - engine: 'baidu' - }); + if (titleElement.length && linkElement.length) { + const url = linkElement.attr('href'); + if (url && url.startsWith('http')) { + const snippetElementBaidu = $(element).find('.c-font-normal.c-color-text').first(); + const sourceElement = $(element).find('.cosc-source'); + results.push({ + title: titleElement.text(), + url: url, + description: snippetElementBaidu.attr('aria-label') || snippetElement.text().trim() || '', + source: sourceElement.text().trim() || '', + engine: 'baidu' + }); + } } - } - }); + }); - allResults = allResults.concat(results); + allResults = allResults.concat(results); - if (results.length === 0) { - console.error('⚠️ No more results, ending early....'); - break; + if (results.length === 0) { + console.error('⚠️ No more results, ending early....'); + break; + } + + pn += 10; } - pn += 10; + return allResults.slice(0, limit); + } catch (err) { + destroySharedBrowser(); + throw err; } - - return allResults.slice(0, limit); // 截取最多 limit 个 } diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index ff30034..67cb5fb 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -1,11 +1,6 @@ -import puppeteer, { type Browser } from 'puppeteer-core'; import * as cheerio from 'cheerio'; -import { existsSync, mkdtempSync, rmSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import { spawn, execFileSync } from 'child_process'; -import { createServer } from 'net'; import { SearchResult } from '../../types.js'; +import { getSharedBrowser, destroySharedBrowser } from '../shared/browser.js'; /** * 解码 Bing 重定向 URL,提取实际目标地址。 @@ -30,243 +25,34 @@ function decodeBingUrl(bingUrl: string): string { } } -let cachedBrowserPath: string | null = null; - -function getBrowserPath(): string { - if (cachedBrowserPath) return cachedBrowserPath; - - const candidates: string[] = []; - - // Windows 硬编码常见路径(MCP 环境下环境变量可能缺失时的后备方案) - candidates.push('C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'); - candidates.push('C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe'); - candidates.push('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'); - candidates.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'); - - // 基于环境变量的路径 - const pf86 = process.env['PROGRAMFILES(X86)']; - const pf = process.env['PROGRAMFILES']; - const localAppData = process.env['LOCALAPPDATA']; - if (pf86) { - candidates.push(pf86 + '\\Microsoft\\Edge\\Application\\msedge.exe'); - candidates.push(pf86 + '\\Google\\Chrome\\Application\\chrome.exe'); - } - if (pf) { - candidates.push(pf + '\\Microsoft\\Edge\\Application\\msedge.exe'); - candidates.push(pf + '\\Google\\Chrome\\Application\\chrome.exe'); - } - if (localAppData) { - candidates.push(localAppData + '\\Google\\Chrome\\Application\\chrome.exe'); - } - - // Linux/macOS 路径 - candidates.push('/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/microsoft-edge'); - candidates.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'); - candidates.push('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'); - - // 去重 - const unique = [...new Set(candidates)]; - for (const p of unique) { - if (existsSync(p)) { - console.error(`[bing] Found browser: ${p}`); - cachedBrowserPath = p; - return p; - } - } - throw new Error('未找到 Chromium 内核浏览器,请安装 Chrome 或 Edge。'); -} - -/** 查找可用的 TCP 端口 */ -function findFreePort(): Promise { - return new Promise((resolve, reject) => { - const srv = createServer(); - srv.listen(0, '127.0.0.1', () => { - const addr = srv.address(); - if (addr && typeof addr === 'object') { - const port = addr.port; - srv.close(() => resolve(port)); - } else { - srv.close(() => reject(new Error('Could not determine port'))); - } - }); - srv.on('error', reject); - }); -} - -/** 缓存的浏览器会话,跨搜索复用 */ -let cachedSession: { browser: Browser; tempDir: string; browserPid?: number; warmedUp: boolean } | null = null; - -async function launchBrowser(): Promise<{ browser: Browser; tempDir: string; browserPid?: number }> { - const browserPath = getBrowserPath(); - const tempDir = mkdtempSync(join(tmpdir(), 'bing-search-')); - const port = await findFreePort(); - - const args = [ - '--headless=new', - `--remote-debugging-port=${port}`, - `--user-data-dir=${tempDir}`, - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-gpu', - '--disable-dev-shm-usage', - '--disable-extensions', - '--no-first-run', - '--no-default-browser-check', - ]; - - console.error(`[bing] Spawning browser on port ${port}, profile: ${tempDir}`); - - let browserPid: number | undefined; - - if (process.platform === 'win32') { - // Windows: 通过 WMI (Win32_Process.Create) 创建浏览器进程。 - // VS Code 的 MCP 服务器环境使用 Job Object 管控子进程, - // 导致 spawn/exec 创建的浏览器被立即终止(退出码 0)。 - // WMI 创建的进程完全独立于父进程的 Job Object,不受此限制。 - const cmdLine = `"${browserPath}" ${args.join(' ')}`; - const psScript = [ - `$r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, - `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'}`, - `; if($r.ReturnValue -eq 0){$r.ProcessId}else{throw "WMI error: $($r.ReturnValue)"}`, - ].join(' '); - try { - const output = execFileSync('powershell.exe', [ - '-NoProfile', '-NonInteractive', '-Command', psScript, - ], { encoding: 'utf8', windowsHide: true, timeout: 10000 }); - browserPid = parseInt(output.trim()); - console.error(`[bing] Browser started via WMI, PID: ${browserPid}`); - } catch (err: any) { - try { rmSync(tempDir, { recursive: true, force: true }); } catch {} - throw new Error(`通过 WMI 启动浏览器失败: ${err.message}`); - } - } else { - // Linux/macOS: 直接启动浏览器 - const child = spawn(browserPath, args, { - stdio: 'ignore', - detached: true, - }); - // [Copilot review #6] 监听 error 事件,避免 spawn 失败(如 ENOENT)时 - // 未处理的 error 事件导致进程崩溃 - child.on('error', () => {}); - child.unref(); - browserPid = child.pid; - } - - // [Copilot review #2] Copilot 建议用 --remote-debugging-port=0 + stderr 解析取代端口轮询, - // 但当前架构(Windows WMI / Linux detached spawn)均无法获取 stderr,只能轮询。 - const debugUrl = `http://127.0.0.1:${port}/json/version`; - let wsUrl: string | null = null; - for (let i = 0; i < 30; i++) { - await new Promise(r => setTimeout(r, 200)); - // [Copilot review #3] 为每次 fetch 添加超时,避免端口被劫持或连接卡住时 - // 单次请求无限挂起,使 30 次重试上限失效。 - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 2000); - try { - const resp = await fetch(debugUrl, { signal: controller.signal }); - const data = await resp.json() as { webSocketDebuggerUrl?: string }; - if (data.webSocketDebuggerUrl) { - wsUrl = data.webSocketDebuggerUrl; - break; - } - } catch { - // 浏览器尚未就绪或请求超时 - } finally { - clearTimeout(timeoutId); - } - } - - if (!wsUrl) { - if (browserPid) try { process.kill(browserPid); } catch {} - try { rmSync(tempDir, { recursive: true, force: true }); } catch {} - throw new Error('浏览器启动失败:无法获取 WebSocket URL'); - } - - console.error(`[bing] Browser ready, connecting via ${wsUrl}`); - // [Copilot review #7] puppeteer.connect 失败时清理子进程和临时目录, - // 避免 launchBrowser 抛出后调用方无法清理的资源泄漏 - try { - const browser = await puppeteer.connect({ browserWSEndpoint: wsUrl }); - return { browser, tempDir, browserPid }; - } catch (err) { - if (browserPid) try { process.kill(browserPid); } catch {} - try { rmSync(tempDir, { recursive: true, force: true }); } catch {} - throw err; - } -} - -/** 获取或复用浏览器会话 */ -async function getBrowser(): Promise<{ browser: Browser; tempDir: string; browserPid?: number }> { - // 检查已缓存的浏览器是否仍然可用 - if (cachedSession) { - try { - // 尝试获取版本信息来验证连接是否正常 - await cachedSession.browser.version(); - return cachedSession; - } catch { - console.error('[bing] Cached browser session is dead, relaunching...'); - cleanupSession(cachedSession); - cachedSession = null; - } - } - - const session = await launchBrowser(); - cachedSession = { ...session, warmedUp: false }; - - // 进程退出时清理浏览器 - const cleanup = () => { - if (cachedSession) { - cleanupSession(cachedSession); - cachedSession = null; - } - }; - process.once('exit', cleanup); - process.once('SIGINT', cleanup); - process.once('SIGTERM', cleanup); - - return cachedSession; -} - -function cleanupSession(session: { browser: Browser; tempDir: string; browserPid?: number }) { - try { session.browser.close(); } catch {} - if (session.browserPid) try { process.kill(session.browserPid); } catch {} - try { rmSync(session.tempDir, { recursive: true, force: true }); } catch {} -} +let bingWarmedUp = false; /** * 预热请求:先访问 cn.bing.com 建立有效的搜索会话。 * cn.bing.com 对多词中文查询需要有效的会话 cookie, * 否则会返回随机的无关内容。 */ -async function warmUpSession(browser: Browser): Promise { +async function warmUpBingSession(): Promise { + if (bingWarmedUp) return; + const browser = await getSharedBrowser(); const page = await browser.newPage(); try { await page.goto('https://cn.bing.com/search?q=test', { waitUntil: 'networkidle2', timeout: 15000 }); await new Promise(r => setTimeout(r, 500)); + bingWarmedUp = true; } finally { await page.close(); } } export async function searchBing(query: string, limit: number): Promise { - const { browser } = await getBrowser(); - try { - // 首次使用时预热会话,从 cn.bing.com 获取有效 cookie - if (cachedSession && !cachedSession.warmedUp) { - await warmUpSession(browser); - cachedSession.warmedUp = true; - } + await warmUpBingSession(); + const browser = await getSharedBrowser(); let allResults: SearchResult[] = []; let pn = 0; - // [Copilot review #1] Copilot 建议为每个 page 添加 try/finally, - // 但异常会直接传播到外层 finally 的 browser.close(),所有页面随之销毁,无需单独处理。 - // [Copilot review #4] Copilot 建议按环境变量条件化 --no-sandbox, - // 但 --no-sandbox 是 headless 自动化的标准做法,且 Docker 容器内通常需要,条件化会增加复杂度。 - // [Copilot review #5] Copilot 建议 kill() 添加 try/catch, - // 当前代码已使用 try { process.kill(browserPid); } catch {} 容错处理。 while (allResults.length < limit) { const page = await browser.newPage(); @@ -313,11 +99,8 @@ export async function searchBing(query: string, limit: number): Promise { - const match = url.match(/\/topic\/(\d+)/); + // 同时支持 /topic/123 和 /t/slug/123 两种 URL 格式 + const match = url.match(/(?:\/topic\/|\/)t\/(?:[^\/]+\/)?(\d+)/) || url.match(/\/topic\/(\d+)/); const topicId = match ? match[1] : null; if (!topicId) { @@ -12,25 +13,8 @@ export async function fetchLinuxDoArticle(url: string): Promise<{ content: strin const response = await axios.get(apiUrl, { headers: { - 'accept': 'application/json, text/javascript, */*; q=0.01', - 'accept-language': 'zh-CN,zh;q=0.9', - 'cache-control': 'no-cache', - 'discourse-track-view': 'true', - 'discourse-track-view-topic-id': `${topicId}`, - 'pragma': 'no-cache', - 'referer': 'https://linux.do/search', - 'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', - 'x-csrf-token': 'K7YORqytPH8vZTM48iHLitfzv4NfU9GuiL1992MKuIBoviOCHyJk_w0LvTkfsX2bjn8ueXKzIGU8Uf8tzoxldg', - 'x-requested-with': 'XMLHttpRequest', - 'Cookie': '_ga=GA1.1.1014556084.1750571986; cf_clearance=OHwsuY8kOismHG8rBN1tCKczIEyTdoJrMPH65aPVUSI-1750571989-1.2.1.1-uJ4vrRUBXQtFG8Ws7JrPw0VNT8_YWVWOz1GSvHyAWTCUPPC8PNqnKApl9hVhLHHs4kB.sQ4B0V54VEwG.RT23ewifTx0rifGNIVItA1Tt5Sq1M78h7sqlwaW7p0vWYuAasaSwcZLKElbcwIxDGd4_EU44Lss.jIl0p9PYPa9QWlUCtbwHISkR8lt8zHtX_YIFrU25pjsHLkLqzYgk7mpmEwAaryi4wgxoc7R0u_FqP5kD1Fq4t559mXPdvj3H23004H12XYT95hHNudrfmHUbO6yLzrspsmV0rdUxJHLwCtI_0aK6JvrQNGJpU13_XS0Q8R_WKOLYrVgHLC_wmg_YOJJ2tMRkJFt_yV2pHV0JPLCvN5I986ooXiLXkVAWvNQ; __stripe_mid=45e0bc73-88a1-4392-9a8e-56b3ad60d5017557f5; __stripe_sid=23ed10a8-f6f4-4cd8-948b-386cb239067ad435dc; _ga_1X49KS6K0M=GS2.1.s1750571986$o1$g1$t1750571999$j47$l0$h1911122445', - 'Host': 'linux.do', - 'Connection': 'keep-alive' + 'accept': 'application/json', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', } }); diff --git a/src/engines/shared/browser.ts b/src/engines/shared/browser.ts new file mode 100644 index 0000000..f97f631 --- /dev/null +++ b/src/engines/shared/browser.ts @@ -0,0 +1,214 @@ +/** + * 全局共享的 Puppeteer 浏览器管理模块。 + * 所有需要真实浏览器环境的搜索引擎(Bing、Baidu 等)共用同一个浏览器实例。 + */ +import puppeteer, { type Browser } from 'puppeteer-core'; +import { existsSync, mkdtempSync, rmSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { spawn, execFileSync } from 'child_process'; +import { createServer } from 'net'; + +let cachedBrowserPath: string | null = null; + +function getBrowserPath(): string { + if (cachedBrowserPath) return cachedBrowserPath; + + const candidates: string[] = []; + + // Windows 硬编码常见路径(MCP 环境下环境变量可能缺失时的后备方案) + candidates.push('C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push('C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'); + candidates.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'); + + // 基于环境变量的路径 + const pf86 = process.env['PROGRAMFILES(X86)']; + const pf = process.env['PROGRAMFILES']; + const localAppData = process.env['LOCALAPPDATA']; + if (pf86) { + candidates.push(pf86 + '\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push(pf86 + '\\Google\\Chrome\\Application\\chrome.exe'); + } + if (pf) { + candidates.push(pf + '\\Microsoft\\Edge\\Application\\msedge.exe'); + candidates.push(pf + '\\Google\\Chrome\\Application\\chrome.exe'); + } + if (localAppData) { + candidates.push(localAppData + '\\Google\\Chrome\\Application\\chrome.exe'); + } + + // Linux/macOS 路径 + candidates.push('/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/microsoft-edge'); + candidates.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'); + candidates.push('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'); + + const unique = [...new Set(candidates)]; + for (const p of unique) { + if (existsSync(p)) { + console.error(`[browser] Found browser: ${p}`); + cachedBrowserPath = p; + return p; + } + } + throw new Error('未找到 Chromium 内核浏览器,请安装 Chrome 或 Edge。'); +} + +/** 查找可用的 TCP 端口 */ +function findFreePort(): Promise { + return new Promise((resolve, reject) => { + const srv = createServer(); + srv.listen(0, '127.0.0.1', () => { + const addr = srv.address(); + if (addr && typeof addr === 'object') { + const port = addr.port; + srv.close(() => resolve(port)); + } else { + srv.close(() => reject(new Error('Could not determine port'))); + } + }); + srv.on('error', reject); + }); +} + +interface BrowserSession { + browser: Browser; + tempDir: string; + browserPid?: number; +} + +let cachedSession: BrowserSession | null = null; +let cleanupRegistered = false; + +async function launchBrowser(): Promise { + const browserPath = getBrowserPath(); + const tempDir = mkdtempSync(join(tmpdir(), 'mcp-search-')); + const port = await findFreePort(); + + const args = [ + '--headless=new', + `--remote-debugging-port=${port}`, + `--user-data-dir=${tempDir}`, + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-gpu', + '--disable-dev-shm-usage', + '--disable-extensions', + '--no-first-run', + '--no-default-browser-check', + '--disable-blink-features=AutomationControlled', + ]; + + console.error(`[browser] Spawning browser on port ${port}, profile: ${tempDir}`); + + let browserPid: number | undefined; + + if (process.platform === 'win32') { + const cmdLine = `"${browserPath}" ${args.join(' ')}`; + const psScript = [ + `$r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, + `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'}`, + `; if($r.ReturnValue -eq 0){$r.ProcessId}else{throw "WMI error: $($r.ReturnValue)"}`, + ].join(' '); + try { + const output = execFileSync('powershell.exe', [ + '-NoProfile', '-NonInteractive', '-Command', psScript, + ], { encoding: 'utf8', windowsHide: true, timeout: 10000 }); + browserPid = parseInt(output.trim()); + console.error(`[browser] Browser started via WMI, PID: ${browserPid}`); + } catch (err: any) { + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} + throw new Error(`通过 WMI 启动浏览器失败: ${err.message}`); + } + } else { + const child = spawn(browserPath, args, { + stdio: 'ignore', + detached: true, + }); + child.on('error', () => {}); + child.unref(); + browserPid = child.pid; + } + + const debugUrl = `http://127.0.0.1:${port}/json/version`; + let wsUrl: string | null = null; + for (let i = 0; i < 30; i++) { + await new Promise(r => setTimeout(r, 200)); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 2000); + try { + const resp = await fetch(debugUrl, { signal: controller.signal }); + const data = await resp.json() as { webSocketDebuggerUrl?: string }; + if (data.webSocketDebuggerUrl) { + wsUrl = data.webSocketDebuggerUrl; + break; + } + } catch { + // 浏览器尚未就绪或请求超时 + } finally { + clearTimeout(timeoutId); + } + } + + if (!wsUrl) { + if (browserPid) try { process.kill(browserPid); } catch {} + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} + throw new Error('浏览器启动失败:无法获取 WebSocket URL'); + } + + console.error(`[browser] Browser ready, connecting via ${wsUrl}`); + try { + const browser = await puppeteer.connect({ browserWSEndpoint: wsUrl }); + return { browser, tempDir, browserPid }; + } catch (err) { + if (browserPid) try { process.kill(browserPid); } catch {} + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} + throw err; + } +} + +function cleanupSession(session: BrowserSession) { + try { session.browser.disconnect(); } catch {} + if (session.browserPid) try { process.kill(session.browserPid); } catch {} + try { rmSync(session.tempDir, { recursive: true, force: true }); } catch {} +} + +/** 获取或复用全局共享的浏览器实例 */ +export async function getSharedBrowser(): Promise { + if (cachedSession) { + try { + await cachedSession.browser.version(); + return cachedSession.browser; + } catch { + console.error('[browser] Cached browser session is dead, relaunching...'); + cleanupSession(cachedSession); + cachedSession = null; + } + } + + const session = await launchBrowser(); + cachedSession = session; + + if (!cleanupRegistered) { + cleanupRegistered = true; + const cleanup = () => { + if (cachedSession) { + cleanupSession(cachedSession); + cachedSession = null; + } + }; + process.once('exit', cleanup); + process.once('SIGINT', cleanup); + process.once('SIGTERM', cleanup); + } + + return cachedSession.browser; +} + +/** 销毁全局浏览器会话(搜索出错时调用,下次会重新启动) */ +export function destroySharedBrowser(): void { + if (cachedSession) { + cleanupSession(cachedSession); + cachedSession = null; + } +} diff --git a/src/test/fetchLinuxDoArticleTests.ts b/src/test/fetchLinuxDoArticleTests.ts new file mode 100644 index 0000000..8043c43 --- /dev/null +++ b/src/test/fetchLinuxDoArticleTests.ts @@ -0,0 +1,58 @@ +import { fetchLinuxDoArticle } from "../engines/linuxdo/fetchLinuxDoArticle.js"; + +async function testFetchLinuxDoArticle() { + console.log('🔍 Starting Linux.do article fetch test...'); + + try { + // A real linux.do topic URL (standard Discourse format: /t/slug/id) + const url = 'https://linux.do/t/welcome-to-linux-do/1'; + + console.log(`📝 Fetching article from URL: ${url}`); + + const result = await fetchLinuxDoArticle(url); + + console.log(`🎉 Article fetched successfully!`); + console.log(`\n📄 Content preview (first 200 chars):`); + console.log(` ${result.content.substring(0, 200)}`); + console.log(`\n📊 Total content length: ${result.content.length} characters`); + + return result; + } catch (error) { + console.error('❌ Test failed:', error instanceof Error ? error.message : error); + if (error && typeof error === 'object' && 'response' in error) { + const resp = (error as any).response; + console.error(` HTTP Status: ${resp?.status}`); + console.error(` Status Text: ${resp?.statusText}`); + } + return { content: '' }; + } +} + +async function testInvalidUrl() { + console.log('\n🔍 Testing with invalid URL...'); + + try { + const invalidUrl = 'https://linux.do/invalid_path'; + + console.log(`📝 Attempting to fetch from invalid URL: ${invalidUrl}`); + + const result = await fetchLinuxDoArticle(invalidUrl); + console.log(`🎉 Result: ${result.content.substring(0, 100)}`); + + return result; + } catch (error) { + console.log('✅ Expected error for invalid URL:', error instanceof Error ? error.message : error); + return { content: '' }; + } +} + +async function runTests() { + console.log('🧪 Running tests for fetchLinuxDoArticle function\n'); + + await testFetchLinuxDoArticle(); + await testInvalidUrl(); + + console.log('\n✅ All tests completed'); +} + +runTests().catch(console.error); diff --git a/src/test/test-baidu-availability.ts b/src/test/test-baidu-availability.ts new file mode 100644 index 0000000..ec3f2ba --- /dev/null +++ b/src/test/test-baidu-availability.ts @@ -0,0 +1,90 @@ +import { searchBaidu } from '../engines/baidu/index.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; + +/** + * 测试百度搜索引擎是否正常工作 (Issue #29: baidu被ban了) + * 验证百度搜索能返回结果,且结果结构正确 + */ + +async function testBaiduAvailability() { + console.log('=== Baidu availability test (Issue #29) ===\n'); + + const tests: { name: string; pass: boolean }[] = []; + + // Test 1: 英文查询能否返回结果 + { + const name = 'English query returns results'; + try { + const results = await searchBaidu('websearch mcp', 5); + const pass = results.length > 0; + console.log(`${pass ? '✅ PASS' : '❌ FAIL'}: ${name} — got ${results.length} results`); + tests.push({ name, pass }); + } catch (e) { + console.log(`❌ FAIL: ${name} — error: ${e instanceof Error ? e.message : e}`); + tests.push({ name, pass: false }); + } + } + + // Test 2: 中文热门查询 (Issue #29 的核心场景) + { + const name = 'Chinese hot query returns results (Issue #29)'; + try { + const results = await searchBaidu('天气预报', 5); + const pass = results.length > 0; + console.log(`${pass ? '✅ PASS' : '❌ FAIL'}: ${name} — got ${results.length} results`); + tests.push({ name, pass }); + } catch (e) { + console.log(`❌ FAIL: ${name} — error: ${e instanceof Error ? e.message : e}`); + tests.push({ name, pass: false }); + } + } + + // Test 3: 结果结构验证 + { + const name = 'Results have correct structure'; + try { + const results = await searchBaidu('nodejs', 3); + const pass = results.length > 0 && results.every(r => + typeof r.title === 'string' && r.title.length > 0 && + typeof r.url === 'string' && r.url.startsWith('http') && + typeof r.description === 'string' && + r.engine === 'baidu' + ); + console.log(`${pass ? '✅ PASS' : '❌ FAIL'}: ${name}`); + if (!pass && results.length > 0) { + console.log(' Sample result:', JSON.stringify(results[0], null, 2)); + } + tests.push({ name, pass }); + } catch (e) { + console.log(`❌ FAIL: ${name} — error: ${e instanceof Error ? e.message : e}`); + tests.push({ name, pass: false }); + } + } + + // Test 4: limit 参数生效 + { + const name = 'Limit parameter is respected'; + try { + const results = await searchBaidu('open source', 3); + const pass = results.length > 0 && results.length <= 3; + console.log(`${pass ? '✅ PASS' : '❌ FAIL'}: ${name} — requested 3, got ${results.length}`); + tests.push({ name, pass }); + } catch (e) { + console.log(`❌ FAIL: ${name} — error: ${e instanceof Error ? e.message : e}`); + tests.push({ name, pass: false }); + } + } + + const passed = tests.filter(t => t.pass).length; + const total = tests.length; + + console.log(`\n=== Results: ${passed}/${total} passed ===`); + + if (passed === total) { + console.log('\n✅ 百度搜索运行正常。'); + } + + process.exit(passed === total ? 0 : 1); +} + +testBaiduAvailability().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-baidu.ts b/src/test/test-baidu.ts index 65987a8..c0032f6 100644 --- a/src/test/test-baidu.ts +++ b/src/test/test-baidu.ts @@ -1,4 +1,5 @@ import { searchBaidu } from '../engines/baidu/index.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; async function testBaiduSearch() { console.log('🔍 Starting Baidu search test...'); @@ -28,4 +29,4 @@ async function testBaiduSearch() { } // Run the test -testBaiduSearch().catch(console.error); +testBaiduSearch().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-bing.ts b/src/test/test-bing.ts index 346b457..3813875 100644 --- a/src/test/test-bing.ts +++ b/src/test/test-bing.ts @@ -1,4 +1,5 @@ import { searchBing } from '../engines/bing/index.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; async function testBingSearch() { console.log('🔍 Starting Bing search test...'); @@ -28,4 +29,4 @@ async function testBingSearch() { } // Run the test -testBingSearch().catch(console.error); +testBingSearch().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-description-length.ts b/src/test/test-description-length.ts new file mode 100644 index 0000000..5f319cd --- /dev/null +++ b/src/test/test-description-length.ts @@ -0,0 +1,89 @@ +import { searchBing } from '../engines/bing/index.js'; +import { SearchResult } from '../types.js'; +import { config } from '../config.js'; + +// 与 setupTools.ts 中相同的截断逻辑 +function truncateDescriptions(results: SearchResult[], descLimit?: number): SearchResult[] { + if (!descLimit) return results; + return results.map(r => ({ + ...r, + description: r.description.length > descLimit + ? r.description.slice(0, descLimit) + '...' + : r.description + })); +} + +async function testDescriptionLength() { + console.log('🔍 Starting description length truncation test...\n'); + + let passed = 0; + let failed = 0; + + function assert(condition: boolean, message: string) { + if (condition) { + console.log(` ✅ ${message}`); + passed++; + } else { + console.error(` ❌ ${message}`); + failed++; + } + } + + // ===== 单元测试:截断逻辑 ===== + console.log('--- 单元测试:截断逻辑 ---'); + + const mockResults: SearchResult[] = [ + { title: 'A', url: 'http://a.com', description: 'Short', source: 'bing', engine: 'bing' }, + { title: 'B', url: 'http://b.com', description: 'This is a much longer description that exceeds the limit', source: 'bing', engine: 'bing' }, + { title: 'C', url: 'http://c.com', description: '', source: 'bing', engine: 'bing' }, + ]; + + // 测试1:无限制时不截断 + const noLimit = truncateDescriptions(mockResults, undefined); + assert(noLimit[0].description === 'Short', '无限制时短描述不变'); + assert(noLimit[1].description === 'This is a much longer description that exceeds the limit', '无限制时长描述不变'); + assert(noLimit[2].description === '', '无限制时空描述不变'); + + // 测试2:设置限制后截断 + const limited = truncateDescriptions(mockResults, 10); + assert(limited[0].description === 'Short', '短于限制的描述不变'); + assert(limited[1].description === 'This is a ...', '超过限制的描述被截断并加省略号'); + assert(limited[1].description.length === 13, `截断后长度为 10+3=13 (实际: ${limited[1].description.length})`); + assert(limited[2].description === '', '空描述保持为空'); + + // 测试3:限制恰好等于描述长度时不截断 + const exact = truncateDescriptions(mockResults, 5); + assert(exact[0].description === 'Short', '长度恰好等于限制时不截断'); + + // 测试4:限制为1 + const one = truncateDescriptions(mockResults, 1); + assert(one[1].description === 'T...', '限制为1时只保留1个字符加省略号'); + + // 测试5:全局配置默认值 + console.log('\n--- 全局配置检查 ---'); + assert(config.maxDescriptionLength === undefined, `默认全局配置为 undefined (实际: ${config.maxDescriptionLength})`); + + // ===== 集成测试:实际搜索 + 截断 ===== + console.log('\n--- 集成测试:实际搜索 + 截断 ---'); + try { + const results = await searchBing('typescript', 3); + assert(results.length > 0, `Bing 搜索返回了 ${results.length} 条结果`); + + const truncated = truncateDescriptions(results, 20); + for (const r of truncated) { + const ok = r.description.length <= 23; // 20 + '...' + assert(ok, `"${r.title}" 描述长度 ${r.description.length} <= 23`); + } + } catch (error) { + console.error(` ⚠️ 集成测试跳过 (搜索失败): ${error instanceof Error ? error.message : error}`); + } + + // ===== 结果汇总 ===== + console.log(`\n📊 结果: ${passed} 通过, ${failed} 失败`); + if (failed > 0) process.exit(1); +} + +testDescriptionLength().catch(e => { + console.error('❌ 测试异常:', e); + process.exit(1); +}); diff --git a/src/test/test-engine-case.ts b/src/test/test-engine-case.ts new file mode 100644 index 0000000..31c434a --- /dev/null +++ b/src/test/test-engine-case.ts @@ -0,0 +1,55 @@ +import { z } from 'zod'; + +// 复现 setupTools.ts 中的引擎 schema 逻辑(与实际代码一致) +const SUPPORTED_ENGINES = ['baidu', 'bing', 'linuxdo', 'csdn', 'duckduckgo', 'exa', 'brave', 'juejin'] as const; + +const getAllowedEngines = () => [...SUPPORTED_ENGINES] as string[]; + +const enginesSchema = z.array(z.string()) + .min(1).default(['bing']) + .transform(requestedEngines => { + const allowed = getAllowedEngines(); + const normalized = requestedEngines.map(e => e.toLowerCase()); + const valid = normalized.filter(e => allowed.includes(e)); + if (valid.length === 0) { + throw new Error(`No valid engine found. Allowed engines: ${allowed.join(', ')}`); + } + return valid; + }); + +function test(name: string, input: unknown, expected: string[] | 'error') { + try { + const result = enginesSchema.parse(input); + if (expected === 'error') { + console.log(`❌ FAIL: ${name} — expected error but got ${JSON.stringify(result)}`); + return false; + } + const pass = JSON.stringify(result) === JSON.stringify(expected); + console.log(`${pass ? '✅ PASS' : '❌ FAIL'}: ${name} — got ${JSON.stringify(result)}`); + return pass; + } catch (e) { + if (expected === 'error') { + console.log(`✅ PASS: ${name} — correctly rejected`); + return true; + } + console.log(`❌ FAIL: ${name} — unexpected error: ${e instanceof Error ? e.message : e}`); + return false; + } +} + +console.log('=== Engine name case-insensitivity tests ===\n'); + +const results = [ + test('lowercase "bing"', ['bing'], ['bing']), + test('uppercase first letter "Bing"', ['Bing'], ['bing']), + test('all caps "DUCKDUCKGO"', ['DUCKDUCKGO'], ['duckduckgo']), + test('mixed case "BaiDu"', ['BaiDu'], ['baidu']), + test('multiple mixed case engines', ['Bing', 'BRAVE', 'exa'], ['bing', 'brave', 'exa']), + test('invalid engine name', ['invalid'], 'error'), + test('empty array', [], 'error'), +]; + +const passed = results.filter(Boolean).length; +const total = results.length; +console.log(`\n=== Results: ${passed}/${total} passed ===`); +process.exit(passed === total ? 0 : 1); diff --git a/src/test/test-search-relevance.ts b/src/test/test-search-relevance.ts new file mode 100644 index 0000000..8258860 --- /dev/null +++ b/src/test/test-search-relevance.ts @@ -0,0 +1,95 @@ +import { searchBaidu } from '../engines/baidu/index.js'; +import { searchBing } from '../engines/bing/index.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; + +interface TestCase { + engine: 'baidu' | 'bing'; + query: string; + mustContainAny: string[]; + description: string; +} + +const tests: TestCase[] = [ + { + engine: 'baidu', + query: '天气预报', + mustContainAny: ['天气', '预报', 'weather'], + description: 'Baidu "天气预报" (Issue #29 核心问题词)', + }, + { + engine: 'baidu', + query: 'Python教程', + mustContainAny: ['python', 'Python', '教程', '编程'], + description: 'Baidu "Python教程" (Issue #29 问题词)', + }, + { + engine: 'bing', + query: 'websearch mcp server', + mustContainAny: ['websearch', 'mcp', 'search', 'MCP'], + description: 'Bing "websearch mcp server" 结果相关性', + }, + { + engine: 'bing', + query: 'TypeScript tutorial', + mustContainAny: ['typescript', 'TypeScript', 'tutorial', '教程'], + description: 'Bing "TypeScript tutorial" 结果相关性', + }, +]; + +async function testSearchRelevance() { + console.log('=== 搜索结果相关性测试 ===\n'); + + let passed = 0; + let failed = 0; + + for (const tc of tests) { + console.log(`--- ${tc.description} ---`); + console.log(`查询: "${tc.query}" (${tc.engine})`); + + try { + const results = tc.engine === 'baidu' + ? await searchBaidu(tc.query, 5) + : await searchBing(tc.query, 5); + + if (results.length === 0) { + console.log(`❌ FAIL: 返回 0 条结果`); + failed++; + continue; + } + + results.forEach((r, i) => { + console.log(` ${i + 1}. ${r.title}`); + }); + + const keywords = tc.mustContainAny.map(k => k.toLowerCase()); + let relevantCount = 0; + for (const r of results) { + const text = `${r.title} ${r.url} ${r.description}`.toLowerCase(); + if (keywords.some(k => text.includes(k))) { + relevantCount++; + } + } + + const ratio = relevantCount / results.length; + if (ratio >= 0.5) { + console.log(`✅ PASS: ${relevantCount}/${results.length} 条结果相关 (${(ratio * 100).toFixed(0)}%)`); + passed++; + } else { + console.log(`❌ FAIL: ${relevantCount}/${results.length} 条结果相关 (${(ratio * 100).toFixed(0)}%) — 相关性不足`); + failed++; + } + } catch (err: any) { + console.log(`❌ FAIL: ${err.message}`); + failed++; + } + console.log(); + } + + console.log(`=== 结果: ${passed}/${passed + failed} 通过 ===`); + if (failed > 0) { + console.log(`⚠️ ${failed} 项未通过`); + process.exitCode = 1; + } +} + +testSearchRelevance().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); diff --git a/src/tools/setupTools.ts b/src/tools/setupTools.ts index d5cac1f..3ff8074 100644 --- a/src/tools/setupTools.ts +++ b/src/tools/setupTools.ts @@ -87,7 +87,7 @@ const validateArticleUrl = (url: string, type: 'linuxdo' | 'csdn' | 'juejin'): b switch (type) { case 'linuxdo': - return urlObj.hostname === 'linux.do' && url.includes('.json'); + return urlObj.hostname === 'linux.do' && /\/t(opic)?\//.test(url); case 'csdn': return urlObj.hostname === 'blog.csdn.net' && url.includes('/article/details/'); case 'juejin': @@ -185,7 +185,9 @@ export const setupTools = (server: McpServer): void => { { query: z.string().min(1, "Search query must not be empty"), limit: z.number().min(1).max(50).default(10), - engines: z.array(getEnginesEnum()).min(1).default([config.defaultSearchEngine]) + maxDescriptionLength: z.number().min(1).optional() + .describe("Maximum length of each result's description. Defaults to the global MAX_DESCRIPTION_LENGTH setting. Omit for no limit."), + engines: z.array(z.string().transform(s => s.toLowerCase()).pipe(getEnginesEnum())).min(1).default([config.defaultSearchEngine]) .transform(requestedEngines => { // 如果有配置允许的搜索引擎,过滤请求的引擎 if (config.allowedSearchEngines.length > 0) { @@ -200,20 +202,31 @@ export const setupTools = (server: McpServer): void => { return requestedEngines; }) }, - async ({query, limit = 10, engines = ['bing']}) => { + async ({query, limit = 10, maxDescriptionLength, engines = ['bing']}) => { try { console.error(`Searching for "${query}" using engines: ${engines.join(', ')}`); const results = await executeSearch(query.trim(), engines, limit); + // 应用描述长度限制:调用参数 > 全局配置 > 不限制 + const descLimit = maxDescriptionLength ?? config.maxDescriptionLength; + const truncatedResults = descLimit + ? results.map(r => ({ + ...r, + description: r.description.length > descLimit + ? r.description.slice(0, descLimit) + '...' + : r.description + })) + : results; + return { content: [{ type: 'text', text: JSON.stringify({ query: query.trim(), engines: engines, - totalResults: results.length, - results: results + totalResults: truncatedResults.length, + results: truncatedResults }, null, 2) }] }; From 105d62f821b351b12a4170d3c0ed29c8dc23ebbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 16:51:41 +0800 Subject: [PATCH 05/15] =?UTF-8?q?docs:=20=E5=B0=86=E4=B8=AD=E6=96=87README?= =?UTF-8?q?=E8=AE=BE=E4=B8=BA=E9=BB=98=E8=AE=A4=EF=BC=8C=E8=8B=B1=E6=96=87?= =?UTF-8?q?=E7=A7=BB=E8=87=B3README-en.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-en.md | 602 ++++++++++++++++++++++++++++++++++++++++++++++++++ README-zh.md | 605 --------------------------------------------------- README.md | 445 ++++++++++++++++++------------------- 3 files changed, 826 insertions(+), 826 deletions(-) create mode 100644 README-en.md delete mode 100644 README-zh.md diff --git a/README-en.md b/README-en.md new file mode 100644 index 0000000..58a65c5 --- /dev/null +++ b/README-en.md @@ -0,0 +1,602 @@ +
+ +# Open-WebSearch MCP Server + +[![ModelScope](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Aas-ee/3af09e0f4c7821fb2e9acb96483a5ff0/raw/badge.json&color=%23de5a16)](https://www.modelscope.cn/mcp/servers/Aasee1/open-webSearch) +[![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Aas-ee/open-webSearch)](https://archestra.ai/mcp-catalog/aas-ee__open-websearch) +[![smithery badge](https://smithery.ai/badge/@Aas-ee/open-websearch)](https://smithery.ai/server/@Aas-ee/open-websearch) +![Version](https://img.shields.io/github/v/release/Aas-ee/open-websearch) +![License](https://img.shields.io/github/license/Aas-ee/open-websearch) +![Issues](https://img.shields.io/github/issues/Aas-ee/open-websearch) + +**[🇨🇳 中文](./README.md) | 🇺🇸 English** + +
+ +A Model Context Protocol (MCP) server based on multi-engine search results, supporting free web search without API keys. + + +
+ Click to expand simple demo (deepseek-v3) + +--- + +### 🧩 Demo Start + +### prompt: + +> Use the websearch tool to search "Open-WebSearch MCP", using the csdn engine, query 20 records, tell me the engine returned by the tool, along with related info, then use the URL to fetch articles by author Aasee (if there are multiple, query them sequentially, not simultaneously). Format the output properly. + +--- + +### assistant: + +I will use the MCP_search tool to query "Open-WebSearch MCP" using the CSDN engine to fetch 20 records. + + + +### Query Result Analysis + +1. **Search Engine Used**: CSDN +2. **Total Records**: 20 + +### Articles by Author Aasee +The search results show 2 articles by author "Aasee.": + +1. **Article Title**: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" + **URL**: https://blog.csdn.net/weixin_45801664/article/details/149000138 + **Summary**: This project provides free, controllable web search capabilities for AI plugin development, supporting Bing, Baidu, CSDN multi-engine combined search... + +2. **Article Title**: "Web Seach 搜索 MCP 启动!" + **URL**: https://blog.csdn.net/weixin_45801664/article/details/148813614 + **Summary**: Open-source Web Search MCP server Open-WebSearch launched, solving the problem of AI models unable to access latest web information... + +### Fetching First Article Content +I will first fetch the full content of the first article: + + + +# Search Result Analysis + +## Engine Info Returned by Tool +Used the CSDN search engine for the query, returning 20 related records. + +## Articles by Author Aasee + +### Article 1: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" +``` +🚀 Open-source AI Web Search Tool: Open-WebSearch MCP New Upgrade, Multi-engine + Streaming Response! + +💡 "Let your AI plugins truly access the web" — No API Key needed, controllable search results, ready to use! + +Project Highlights: +✅ Multi-engine Real-time Search: Supports Bing, Baidu, CSDN, DuckDuckGo, Exa, Brave +✅ Streaming Response (MCP Protocol): Compatible with Claude plugin (MCP) protocol +✅ Article Content Extraction: Supports extracting full CSDN blog articles +✅ Completely Free & No API Key Required +✅ One-click Docker Deployment + +Installation: +- Git clone: git clone https://github.com/Aas-ee/open-webSearch +- Or use Docker: docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest + +MCP Tool Support: +🔍 search: Multi-engine search tool +📄 fetchCsdnArticle: Extract CSDN article full text +🧪 fetchLinuxDoArticle: Fetch Linux.do articles (in development) + +Project Links: +🧩 GitHub: https://github.com/Aas-ee/open-webSearch +📦 Docker: ghcr.io/aas-ee/open-web-search:latest +``` + +### Article 2: "Web Seach 搜索 MCP 启动!" +``` +Open-source Web Search MCP server Open-WebSearch launched, solving the problem of AI models +unable to access latest web information. +The project provides free Baidu search results API, supports structured JSON return format, +compatible with Claude and other AI tools' MCP protocol. +Users only need simple installation to use, no API keys required... +``` + +Would you like me to look up other articles by author Aasee? I can continue searching for you. + +### ✅ Demo End + +--- +
+ +## Features + +- Web search using multi-engine results + - bing + - baidu + - ~~linux.do~~ temporarily unsupported + - csdn + - duckduckgo + - exa + - brave + - juejin +- HTTP proxy configuration support for accessing restricted resources +- No API keys or authentication required +- Returns structured results with titles, URLs, and descriptions +- Configurable number of results per search +- Customizable default search engine +- Support for fetching individual article content + - csdn + - github (README files) + +## TODO +- Support for ~~Bing~~ (already supported), ~~DuckDuckGo~~ (already supported), ~~Exa~~ (already supported), ~~Brave~~ (already supported), Google and other search engines +- Support for more blogs, forums, and social platforms +- Optimize article content extraction, add support for more sites +- ~~Support for GitHub README fetching~~ (already supported) + +## Installation Guide + +### NPX Quick Start (Recommended) + +The fastest way to get started: + +```bash +# Basic usage +npx open-websearch@latest + +# With environment variables (Linux/macOS) +DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true npx open-websearch@latest + +# Windows PowerShell +$env:DEFAULT_SEARCH_ENGINE="duckduckgo"; $env:ENABLE_CORS="true"; npx open-websearch@latest + +# Windows CMD +set MODE=stdio && set DEFAULT_SEARCH_ENGINE=duckduckgo && npx open-websearch@latest + +# Cross-platform (requires cross-env, Used for local development) +# Global install +npm install -g open-websearch +npx cross-env DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true open-websearch +``` + +**Environment Variables:** + +| Variable | Default | Options | Description | +|----------|-------------------------|---------|-------------| +| `ENABLE_CORS` | `false` | `true`, `false` | Enable CORS | +| `CORS_ORIGIN` | `*` | Any valid origin | CORS origin configuration | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin` | Default search engine | +| `USE_PROXY` | `false` | `true`, `false` | Enable HTTP proxy | +| `PROXY_URL` | `http://127.0.0.1:7890` | Any valid URL | Proxy server URL | +| `MODE` | `both` | `both`, `http`, `stdio` | Server mode: both HTTP+STDIO, HTTP only, or STDIO only | +| `PORT` | `3000` | 1-65535 | Server port | +| `ALLOWED_SEARCH_ENGINES` | empty (all available) | Comma-separated engine names | Limit which search engines can be used; if the default engine is not in this list, the first allowed engine becomes the default | +| `MCP_TOOL_SEARCH_NAME` | `search` | Valid MCP tool name | Custom name for the search tool | +| `MCP_TOOL_FETCH_LINUXDO_NAME` | `fetchLinuxDoArticle` | Valid MCP tool name | Custom name for the Linux.do article fetch tool | +| `MCP_TOOL_FETCH_CSDN_NAME` | `fetchCsdnArticle` | Valid MCP tool name | Custom name for the CSDN article fetch tool | +| `MCP_TOOL_FETCH_GITHUB_NAME` | `fetchGithubReadme` | Valid MCP tool name | Custom name for the GitHub README fetch tool | +| `MCP_TOOL_FETCH_JUEJIN_NAME` | `fetchJuejinArticle` | Valid MCP tool name | Custom name for the Juejin article fetch tool | + +**Common configurations:** +```bash +# Enable proxy for restricted regions +USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 npx open-websearch@latest + +# Full configuration +DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 PORT=8080 npx open-websearch@latest +``` + +**Windows Users Note:** +- In PowerShell, use `$env:VAR="value"; ` syntax +- For local development, it's recommended to use `npx cross-env` for cross-platform compatibility + +### Local Installation + +1. Clone or download this repository +2. Install dependencies: +```bash +npm install +``` +3. Build the server: +```bash +npm run build +``` +4. Add the server to your MCP configuration: + +**Cherry Studio:** +```json +{ + "mcpServers": { + "web-search": { + "name": "Web Search MCP", + "type": "streamableHttp", + "description": "Multi-engine web search with article fetching", + "isActive": true, + "baseUrl": "http://localhost:3000/mcp" + } + } +} +``` + +**VSCode (Claude Dev Extension):** +```json +{ + "mcpServers": { + "web-search": { + "transport": { + "type": "streamableHttp", + "url": "http://localhost:3000/mcp" + } + }, + "web-search-sse": { + "transport": { + "type": "sse", + "url": "http://localhost:3000/sse" + } + } + } +} +``` + +**Claude Desktop:** +```json +{ + "mcpServers": { + "web-search": { + "type": "http", + "url": "http://localhost:3000/mcp" + }, + "web-search-sse": { + "type": "sse", + "url": "http://localhost:3000/sse" + } + } +} +``` + +**NPX Command Line Configuration:** +```json +{ + "mcpServers": { + "web-search": { + "args": [ + "open-websearch@latest" + ], + "command": "npx", + "env": { + "MODE": "stdio", + "DEFAULT_SEARCH_ENGINE": "duckduckgo", + "ALLOWED_SEARCH_ENGINES": "duckduckgo,bing,exa" + } + } + } +} +``` + +**Local STDIO Configuration for Cherry Studio (Windows):** +```json +{ + "mcpServers": { + "open-websearch-local": { + "command": "node", + "args": ["C:/path/to/your/project/build/index.js"], + "env": { + "MODE": "stdio", + "DEFAULT_SEARCH_ENGINE": "duckduckgo", + "ALLOWED_SEARCH_ENGINES": "duckduckgo,bing,exa" + } + } + } +} +``` + +### Docker Deployment + +Quick deployment using Docker Compose: + +```bash +docker-compose up -d +``` + +Or use Docker directly: +```bash +docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest +``` + +Environment variable configuration: + +| Variable | Default | Options | Description | +|----------|-------------------------|---------|-------------| +| `ENABLE_CORS` | `false` | `true`, `false` | Enable CORS | +| `CORS_ORIGIN` | `*` | Any valid origin | CORS origin configuration | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave` | Default search engine | +| `USE_PROXY` | `false` | `true`, `false` | Enable HTTP proxy | +| `PROXY_URL` | `http://127.0.0.1:7890` | Any valid URL | Proxy server URL | +| `PORT` | `3000` | 1-65535 | Server port | + +Then configure in your MCP client: +```json +{ + "mcpServers": { + "web-search": { + "name": "Web Search MCP", + "type": "streamableHttp", + "description": "Multi-engine web search with article fetching", + "isActive": true, + "baseUrl": "http://localhost:3000/mcp" + }, + "web-search-sse": { + "transport": { + "name": "Web Search MCP", + "type": "sse", + "description": "Multi-engine web search with article fetching", + "isActive": true, + "url": "http://localhost:3000/sse" + } + } + } +} +``` + +## Usage Guide + +The server provides four tools: `search`, `fetchLinuxDoArticle`, `fetchCsdnArticle`, and `fetchGithubReadme`. + +### search Tool Usage + +```typescript +{ + "query": string, // Search query + "limit": number, // Optional: Number of results to return (default: 10) + "engines": string[] // Optional: Engines to use (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) default bing +} +``` + +Usage example: +```typescript +use_mcp_tool({ + server_name: "web-search", + tool_name: "search", + arguments: { + query: "search content", + limit: 3, // Optional parameter + engines: ["bing", "csdn", "duckduckgo", "exa", "brave", "juejin"] // Optional parameter, supports multi-engine combined search + } +}) +``` + +Response example: +```json +[ + { + "title": "Example Search Result", + "url": "https://example.com", + "description": "Description text of the search result...", + "source": "Source", + "engine": "Engine used" + } +] +``` + +### fetchCsdnArticle Tool Usage + +Used to fetch complete content of CSDN blog articles. + +```typescript +{ + "url": string // URL from CSDN search results using the search tool +} +``` + +Usage example: +```typescript +use_mcp_tool({ + server_name: "web-search", + tool_name: "fetchCsdnArticle", + arguments: { + url: "https://blog.csdn.net/xxx/article/details/xxx" + } +}) +``` + +Response example: +```json +[ + { + "content": "Example search result" + } +] +``` + +### fetchLinuxDoArticle Tool Usage + +Used to fetch complete content of Linux.do forum articles. + +```typescript +{ + "url": string // URL from linuxdo search results using the search tool +} +``` + +Usage example: +```typescript +use_mcp_tool({ + server_name: "web-search", + tool_name: "fetchLinuxDoArticle", + arguments: { + url: "https://xxxx.json" + } +}) +``` + +Response example: +```json +[ + { + "content": "Example search result" + } +] +``` + +### fetchGithubReadme Tool Usage + +Used to fetch README content from GitHub repositories. + +```typescript +{ + "url": string // GitHub repository URL (supports HTTPS, SSH formats) +} +``` + +Usage example: +```typescript +use_mcp_tool({ + server_name: "web-search", + tool_name: "fetchGithubReadme", + arguments: { + url: "https://github.com/Aas-ee/open-webSearch" + } +}) +``` + +Supported URL formats: +- HTTPS: `https://github.com/owner/repo` +- HTTPS with .git: `https://github.com/owner/repo.git` +- SSH: `git@github.com:owner/repo.git` +- URLs with parameters: `https://github.com/owner/repo?tab=readme` + +Response example: +```json +[ + { + "content": "
\n\n# Open-WebSearch MCP Server..." + } +] +``` + +### fetchJuejinArticle Tool Usage + +Used to fetch complete content of Juejin articles. + +```typescript +{ + "url": string // Juejin article URL from search results +} +``` + +Usage example: +```typescript +use_mcp_tool({ + server_name: "web-search", + tool_name: "fetchJuejinArticle", + arguments: { + url: "https://juejin.cn/post/7520959840199360563" + } +}) +``` + +Supported URL format: +- `https://juejin.cn/post/{article_id}` + +Response example: +```json +[ + { + "content": "🚀 开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应..." + } +] +``` + +## Usage Limitations + +Since this tool works by scraping multi-engine search results, please note the following important limitations: + +1. **Rate Limiting**: + - Too many searches in a short time may cause the used engines to temporarily block requests + - Recommendations: + - Maintain reasonable search frequency + - Use the limit parameter judiciously + - Add delays between searches when necessary + +2. **Result Accuracy**: + - Depends on the HTML structure of corresponding engines, may fail when engines update + - Some results may lack metadata like descriptions + - Complex search operators may not work as expected + +3. **Legal Terms**: + - This tool is for personal use only + - Please comply with the terms of service of corresponding engines + - Implement appropriate rate limiting based on your actual use case + +4. **Search Engine Configuration**: + - Default search engine can be set via the `DEFAULT_SEARCH_ENGINE` environment variable + - Supported engines: bing, duckduckgo, exa, brave + - The default engine is used when searching specific websites + +5. **Proxy Configuration**: + - HTTP proxy can be configured when certain search engines are unavailable in specific regions + - Enable proxy with environment variable `USE_PROXY=true` + - Configure proxy server address with `PROXY_URL` + +## Contributing + +Welcome to submit issue reports and feature improvement suggestions! + +### Contributor Guide + +If you want to fork this repository and publish your own Docker image, you need to make the following configurations: + +#### GitHub Secrets Configuration + +To enable automatic Docker image building and publishing, please add the following secrets in your GitHub repository settings (Settings → Secrets and variables → Actions): + +**Required Secrets:** +- `GITHUB_TOKEN`: Automatically provided by GitHub (no setup needed) + +**Optional Secrets (for Alibaba Cloud ACR):** +- `ACR_REGISTRY`: Your Alibaba Cloud Container Registry URL (e.g., `registry.cn-hangzhou.aliyuncs.com`) +- `ACR_USERNAME`: Your Alibaba Cloud ACR username +- `ACR_PASSWORD`: Your Alibaba Cloud ACR password +- `ACR_IMAGE_NAME`: Your image name in ACR (e.g., `your-namespace/open-web-search`) + +#### CI/CD Workflow + +The repository includes a GitHub Actions workflow (`.github/workflows/docker.yml`) that automatically: + +1. **Trigger Conditions**: + - Push to `main` branch + - Push version tags (`v*`) + - Manual workflow trigger + +2. **Build and Push to**: + - GitHub Container Registry (ghcr.io) - always enabled + - Alibaba Cloud Container Registry - only enabled when ACR secrets are configured + +3. **Image Tags**: + - `ghcr.io/your-username/open-web-search:latest` + - `your-acr-address/your-image-name:latest` (if ACR is configured) + +#### Fork and Publish Steps: + +1. **Fork the repository** to your GitHub account +2. **Configure secrets** (if you need ACR publishing): + - Go to Settings → Secrets and variables → Actions in your forked repository + - Add the ACR-related secrets listed above +3. **Push changes** to the `main` branch or create version tags +4. **GitHub Actions will automatically build and push** your Docker image +5. **Use your image**, update the Docker command: + ```bash + docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/your-username/open-web-search:latest + ``` + +#### Notes: +- If you don't configure ACR secrets, the workflow will only publish to GitHub Container Registry +- Make sure your GitHub repository has Actions enabled +- The workflow will use your GitHub username (converted to lowercase) as the GHCR image name + +
+ +## Star History +If you find this project helpful, please consider giving it a ⭐ Star! + +[![Star History Chart](https://api.star-history.com/svg?repos=Aas-ee/open-webSearch&type=Date)](https://www.star-history.com/#Aas-ee/open-webSearch&Date) + +
diff --git a/README-zh.md b/README-zh.md deleted file mode 100644 index b237f54..0000000 --- a/README-zh.md +++ /dev/null @@ -1,605 +0,0 @@ -
- -# Open-WebSearch MCP 服务器 - -[![ModelScope](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Aas-ee/3af09e0f4c7821fb2e9acb96483a5ff0/raw/badge.json&color=%23de5a16)](https://www.modelscope.cn/mcp/servers/Aasee1/open-webSearch) -[![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Aas-ee/open-webSearch)](https://archestra.ai/mcp-catalog/aas-ee__open-websearch) -[![smithery badge](https://smithery.ai/badge/@Aas-ee/open-websearch)](https://smithery.ai/server/@Aas-ee/open-websearch) -![Version](https://img.shields.io/github/v/release/Aas-ee/open-websearch) -![License](https://img.shields.io/github/license/Aas-ee/open-websearch) -![Issues](https://img.shields.io/github/issues/Aas-ee/open-websearch) - -**🇨🇳 中文 | [🇺🇸 English](./README.md)** - -
- -一个基于多引擎搜索结果的模型上下文协议(MCP)服务器,支持免费网络搜索,无需API密钥。 - - -
- 点击展开查看简单示例效果(deepseek-v3) - ---- - -### 🧩 示例开始 - -### prompt: - -> 使用websearch工具查询 《Open-WebSearch MCP》,用csdn引擎,查20条记录,告诉我工具返回的engine,以及相关信息,再通过url查询作者是Aasee的文章内容(如果有多篇顺序查询,不要同时查询)。规范输出 - ---- - -### assistant: - -我将使用MCP_search工具查询《Open-WebSearch MCP》并使用CSDN引擎获取20条记录。 - - - -### 查询结果分析 - -1. **使用的搜索引擎**: CSDN -2. **总记录数**: 20条 - -### 作者Aasee的文章列表 -搜索结果显示有2篇作者是"Aasee."的文章: - -1. **文章标题**: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" - **URL**: https://blog.csdn.net/weixin_45801664/article/details/149000138?ops_request_misc=%257B%2522request%255Fid%2522%253A%252203ceec270907178223ab139631d9a81f%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=03ceec270907178223ab139631d9a81f&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-149000138-null-null.142^v102^pc_search_result_base6&utm_term=Open-WebSearch%20MCP - **简介**: 该项目为AI插件开发提供免费、可控的联网搜索能力,支持Bing、百度、CSDN多引擎组合搜索... - -2. **文章标题**: "Web Seach 搜索 MCP 启动!" - **URL**: https://blog.csdn.net/weixin_45801664/article/details/148813614?ops_request_misc=%257B%2522request%255Fid%2522%253A%252203ceec270907178223ab139631d9a81f%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=03ceec270907178223ab139631d9a81f&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-4-148813614-null-null.142^v102^pc_search_result_base6&utm_term=Open-WebSearch%20MCP - **简介**: 开源Web搜索MCP服务器Open-WebSearch上线,解决AI模型无法获取最新网络信息的问题... - -### 开始查询第一篇文章内容 -我将先查询第一篇文章的全文内容: - - - -# 搜索结果分析 - -## 工具返回的引擎信息 -使用了CSDN搜索引擎进行查询,返回了20条相关记录。 - -## 作者Aasee的相关文章内容 - -### 文章1: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" -``` -🚀 开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应! - -💡「让你的 AI 插件真正能联网」—— 不需要 API Key,搜索结果可控、开箱即用! - -项目亮点一览: -✅ 多引擎实时搜索:支持 Bing、百度、CSDN、DuckDuckGo、Exa、Brave -✅ 流式响应(MCP协议):兼容 Claude 插件(MCP)协议 -✅ 文章正文抓取:支持提取 CSDN 博客文章全文 -✅ 完全免费 & 无需 API Key -✅ Docker 一键部署 - -安装方法: -- Git克隆项目: git clone https://github.com/Aas-ee/open-webSearch -- 或使用Docker: docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest - -MCP工具支持: -🔍 search:多引擎搜索工具 -📄 fetchCsdnArticle:提取 CSDN 文章全文 -🧪 fetchLinuxDoArticle:抓取 Linux.do 文章(开发中) - -项目地址: -🧩 GitHub: https://github.com/Aas-ee/open-webSearch -📦 Docker: ghcr.io/aas-ee/open-web-search:latest -``` - -### 文章2: "Web Seach 搜索 MCP 启动!" -``` -开源Web搜索MCP服务器Open-WebSearch上线,解决AI模型无法获取最新网络信息的问题。 -该项目免费提供百度搜索结果API,支持结构化JSON返回格式,兼容Claude等AI工具的MCP协议。 -用户仅需简单安装即可使用,无需API密钥... -``` - -需要查询其他Aasee作者的文章内容吗?我可以继续为您检索。 - -### ✅ 示例结束 - ---- -
- -## 功能特性 - -- 使用多引擎搜索结果进行网络检索 - - bing - - baidu - - ~~linux.do~~ 暂不支持 - - csdn - - duckduckgo - - exa - - brave - - juejin -- 支持HTTP代理配置,轻松解决网络访问限制 -- 无需API密钥或身份验证 -- 返回带标题、URL和描述的结构化结果 -- 可配置每次搜索返回的结果数量 -- 可自定义默认搜索引擎 -- 支持获取单篇文章内容 - - csdn - - github(README文件) - -## TODO -- 支持~~Bing~~(已支持),~~DuckDuckGo~~(已支持),~~Exa~~(已支持),~~Brave~~(已支持),Google等搜索引擎 -- 支持更多博客论坛、社交软件 -- 优化文章内容提取功能,增加更多站点支持 -- ~~支持GitHub README获取~~(已支持) - -## 安装指南 - -### NPX 快速启动(推荐) - -最快的使用方式: - -```bash -# 基本使用 -npx open-websearch@latest - -# 带环境变量(Linux/macOS) -DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true npx open-websearch@latest - -# Windows PowerShell -$env:DEFAULT_SEARCH_ENGINE="duckduckgo"; $env:ENABLE_CORS="true"; npx open-websearch@latest - -# Windows CMD -set MODE=stdio && set DEFAULT_SEARCH_ENGINE=duckduckgo && npx open-websearch@latest - -# 跨平台(需要 cross-env,用于本地开发) -# 全局安装 -npm install -g open-websearch -npx cross-env DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true open-websearch -``` - -**环境变量说明:** - -| 变量名 | 默认值 | 可选值 | 说明 | -|--------|-------------------------|--------|--------------------------------------| -| `ENABLE_CORS` | `false` | `true`, `false` | 启用CORS | -| `CORS_ORIGIN` | `*` | 任何有效来源 | CORS来源配置 | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin` | 默认搜索引擎 | -| `USE_PROXY` | `false` | `true`, `false` | 启用HTTP代理 | -| `PROXY_URL` | `http://127.0.0.1:7890` | 任何有效URL | 代理服务器URL | -| `MODE` | `both` | `both`, `http`, `stdio` | 服务器模式:同时支持HTTP+STDIO、仅HTTP或仅STDIO | -| `PORT` | `3000` | 1-65535 | 服务器端口 | -| `ALLOWED_SEARCH_ENGINES` | 空(全部可用) | 逗号分隔的引擎名称 | 限制可使用的搜索引擎,如默认搜索引擎不在范围,则默认第一个为默认搜索引擎 | -| `MCP_TOOL_SEARCH_NAME` | `search` | 有效的MCP工具名称 | 搜索工具的自定义名称 | -| `MCP_TOOL_FETCH_LINUXDO_NAME` | `fetchLinuxDoArticle` | 有效的MCP工具名称 | Linux.do文章获取工具的自定义名称 | -| `MCP_TOOL_FETCH_CSDN_NAME` | `fetchCsdnArticle` | 有效的MCP工具名称 | CSDN文章获取工具的自定义名称 | -| `MCP_TOOL_FETCH_GITHUB_NAME` | `fetchGithubReadme` | 有效的MCP工具名称 | GitHub README获取工具的自定义名称 | -| `MCP_TOOL_FETCH_JUEJIN_NAME` | `fetchJuejinArticle` | 有效的MCP工具名称 | 掘金文章获取工具的自定义名称 | - -**常用配置示例:** -```bash -# 启用代理(适用于网络受限地区) -USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 npx open-websearch@latest - -# 完整配置 -DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 PORT=8080 npx open-websearch@latest -``` - -**Windows 用户注意事项:** -- 在 PowerShell 中使用 `$env:VAR="value"; ` 语法 -- 本地开发推荐使用 `npx cross-env` 实现跨平台兼容 - -### 本地安装 - -1. 克隆或下载本仓库 -2. 安装依赖项: -```bash -npm install -``` -3. 构建服务器: -```bash -npm run build -``` -4. 将服务器添加到您的MCP配置中: - -**Cherry Studio:** -```json -{ - "mcpServers": { - "web-search": { - "name": "Web Search MCP", - "type": "streamableHttp", - "description": "Multi-engine web search with article fetching", - "isActive": true, - "baseUrl": "http://localhost:3000/mcp" - } - } -} -``` - -**VSCode版(Claude开发扩展):** -```json -{ - "mcpServers": { - "web-search": { - "transport": { - "type": "streamableHttp", - "url": "http://localhost:3000/mcp" - } - }, - "web-search-sse": { - "transport": { - "type": "sse", - "url": "http://localhost:3000/sse" - } - } - } -} -``` - -**Claude桌面版:** -```json -{ - "mcpServers": { - "web-search": { - "type": "http", - "url": "http://localhost:3000/mcp" - }, - "web-search-sse": { - "type": "sse", - "url": "http://localhost:3000/sse" - } - } -} -``` - -**NPX命令行配置示例:** -```json -{ - "mcpServers": { - "web-search": { - "args": [ - "open-websearch@latest" - ], - "command": "npx", - "env": { - "MODE": "stdio", - "DEFAULT_SEARCH_ENGINE": "duckduckgo", - "ALLOWED_SEARCH_ENGINES": "duckduckgo,bing,exa" - } - } - } -} -``` - -**Cherry Studio 本地 STDIO 配置 (Windows):** -```json -{ - "mcpServers": { - "open-websearch-local": { - "command": "node", - "args": ["C:/你的项目路径/build/index.js"], - "env": { - "MODE": "stdio", - "DEFAULT_SEARCH_ENGINE": "duckduckgo", - "ALLOWED_SEARCH_ENGINES": "duckduckgo,bing,exa" - } - } - } -} -``` - -### Docker部署 - -使用Docker Compose快速部署: - -```bash -docker-compose up -d -``` - -或者直接使用Docker: -```bash -docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest -``` - -配置环境变量说明: - -| 变量名 | 默认值 | 可选值 | 说明 | -|--------|-------------------------|--------|------| -| `ENABLE_CORS` | `false` | `true`, `false` | 启用CORS | -| `CORS_ORIGIN` | `*` | 任何有效来源 | CORS来源配置 | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave` | 默认搜索引擎 | -| `USE_PROXY` | `false` | `true`, `false` | 启用HTTP代理 | -| `PROXY_URL` | `http://127.0.0.1:7890` | 任何有效URL | 代理服务器URL | -| `PORT` | `3000` | 1-65535 | 服务器端口 | - -然后在MCP客户端中配置: -```json -{ - "mcpServers": { - "web-search": { - "name": "Web Search MCP", - "type": "streamableHttp", - "description": "Multi-engine web search with article fetching", - "isActive": true, - "baseUrl": "http://localhost:3000/mcp" - }, - "web-search-sse": { - "transport": { - "name": "Web Search MCP", - "type": "sse", - "description": "Multi-engine web search with article fetching", - "isActive": true, - "url": "http://localhost:3000/sse" - } - } - } -} -``` - -## 使用说明 - -服务器提供四个工具:`search`、`fetchLinuxDoArticle`、`fetchCsdnArticle` 和 `fetchGithubReadme`。 - -### search工具使用说明 - -```typescript -{ - "query": string, // 搜索查询词 - "limit": number, // 可选:返回结果数量(默认:10) - "engines": string[] // 可选:使用的引擎 (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) 默认bing -} -``` - -使用示例: -```typescript -use_mcp_tool({ - server_name: "web-search", - tool_name: "search", - arguments: { - query: "搜索内容", - limit: 3, // 可选参数 - engines: ["bing", "csdn", "duckduckgo", "exa", "brave", "juejin"] // 可选参数,支持多引擎组合搜索 - } -}) -``` - -返回示例: -```json -[ - { - "title": "示例搜索结果", - "url": "https://example.com", - "description": "搜索结果的描述文本...", - "source": "来源", - "engine": "使用的引擎" - } -] -``` - - -### fetchCsdnArticle工具使用说明 - -用于获取CSDN博客文章的完整内容。 - -```typescript -{ - "url": string // search 工具使用csdn查询出的url -} -``` - -使用示例: -```typescript -use_mcp_tool({ - server_name: "web-search", - tool_name: "fetchCsdnArticle", - arguments: { - url: "https://blog.csdn.net/xxx/article/details/xxx" - } -}) -``` - -返回示例: -```json -[ - { - "content": "示例搜索结果" - } -] -``` - -### fetchLinuxDoArticle工具使用说明 - -用于获取Linux.do论坛文章的完整内容。 - -```typescript -{ - "url": string // search 工具使用linuxdo查询出的url -} -``` - -使用示例: -```typescript -use_mcp_tool({ - server_name: "web-search", - tool_name: "fetchLinuxDoArticle", - arguments: { - url: "https://xxxx.json" - } -}) -``` - -返回示例: -```json -[ - { - "content": "示例搜索结果" - } -] - -``` - - -### fetchGithubReadme工具使用说明 - -用于获取GitHub仓库的README文件内容。 - -```typescript -{ - "url": string // GitHub仓库URL(支持HTTPS、SSH格式) -} -``` - -使用示例: -```typescript -use_mcp_tool({ - server_name: "web-search", - tool_name: "fetchGithubReadme", - arguments: { - url: "https://github.com/Aas-ee/open-webSearch" - } -}) -``` - -支持的URL格式: -- HTTPS: `https://github.com/owner/repo` -- HTTPS with .git: `https://github.com/owner/repo.git` -- SSH: `git@github.com:owner/repo.git` -- 带参数的URL: `https://github.com/owner/repo?tab=readme` - -返回示例: -```json -[ - { - "content": "
\n\n# Open-WebSearch MCP Server..." - } -] -``` - - -### fetchJuejinArticle工具使用说明 - -用于获取掘金文章的完整内容。 - -```typescript -{ - "url": string // 掘金文章URL -} -``` - -使用示例: -```typescript -use_mcp_tool({ - server_name: "web-search", - tool_name: "fetchJuejinArticle", - arguments: { - url: "https://juejin.cn/post/7520959840199360563" - } -}) -``` - -支持的URL格式: -- `https://juejin.cn/post/{文章ID}` - -返回示例: -```json -[ - { - "content": "🚀 开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应..." - } -] -``` - - -## 使用限制 - -由于本工具通过爬取多引擎搜索结果实现,请注意以下重要限制: - -1. **频率限制**: - - 短时间内搜索次数过多可能导致使用的引擎暂时屏蔽请求 - - 建议: - - 保持合理的搜索频率 - - 审慎使用limit参数 - - 必要时可在搜索间设置延迟 - -2. **结果准确性**: - - 依赖对应引擎的HTML结构,可能随引擎改版失效 - - 部分结果可能缺失描述等元数据 - - 复杂搜索运算符可能无法按预期工作 - -3. **法律条款**: - - 本工具仅限个人使用 - - 请遵守对应引擎的服务条款 - - 建议根据实际使用场景实施适当的频率限制 - -4. **搜索引擎配置**: - - 可通过环境变量`DEFAULT_SEARCH_ENGINE`设置默认搜索引擎 - - 支持的引擎有:bing, duckduckgo, exa, brave - - 当搜索特定网站内容时,会自动使用默认搜索引擎 - -5. **代理服务配置**: - - 当某些搜索引擎在特定地区不可用时,可配置HTTP代理 - - 通过环境变量`USE_PROXY=true`启用代理 - - 使用`PROXY_URL`配置代理服务器地址 - -## 贡献指南 - -欢迎提交问题报告和功能改进建议! - -### 贡献者指南 - -如果您想要fork本仓库并发布自己的Docker镜像,需要进行以下配置: - -#### GitHub Secrets配置 - -要启用自动Docker镜像构建和发布功能,请在您的GitHub仓库设置中添加以下secrets(Settings → Secrets and variables → Actions): - -**必需的Secrets:** -- `GITHUB_TOKEN`: GitHub自动提供(无需设置) - -**可选的Secrets(用于阿里云ACR):** -- `ACR_REGISTRY`: 您的阿里云容器镜像服务URL(例如:`registry.cn-hangzhou.aliyuncs.com`) -- `ACR_USERNAME`: 您的阿里云ACR用户名 -- `ACR_PASSWORD`: 您的阿里云ACR密码 -- `ACR_IMAGE_NAME`: 您在ACR中的镜像名称(例如:`your-namespace/open-web-search`) - -#### CI/CD工作流程 - -仓库包含一个GitHub Actions工作流程(`.github/workflows/docker.yml`),会自动: - -1. **触发条件**: - - 推送到`main`分支 - - 推送版本标签(`v*`) - - 手动触发workflow - -2. **构建并推送到**: - - GitHub Container Registry (ghcr.io) - 始终启用 - - 阿里云容器镜像服务 - 仅在配置ACR secrets时启用 - -3. **镜像标签**: - - `ghcr.io/您的用户名/open-web-search:latest` - - `您的ACR地址/您的镜像名:latest`(如果配置了ACR) - -#### Fork和发布步骤: - -1. **Fork仓库**到您的GitHub账户 -2. **配置secrets**(如果需要ACR发布): - - 进入您fork的仓库的Settings → Secrets and variables → Actions - - 添加上面列出的ACR相关secrets -3. **推送更改**到`main`分支或创建版本标签 -4. **GitHub Actions将自动构建并推送**您的Docker镜像 -5. **使用您的镜像**,更新Docker命令: - ```bash - docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/您的用户名/open-web-search:latest - ``` - -#### 注意事项: -- 如果您不配置ACR secrets,工作流程将只发布到GitHub Container Registry -- 确保您的GitHub仓库已启用Actions功能 -- 工作流程会使用您的GitHub用户名(转换为小写)作为GHCR镜像名称 - -
- -## Star History -如果项目对你有帮助,请考虑给个⭐ Star! - -[![Star History Chart](https://api.star-history.com/svg?repos=Aas-ee/open-webSearch&type=Date)](https://www.star-history.com/#Aas-ee/open-webSearch&Date) - -
diff --git a/README.md b/README.md index 449f1e4..53bc8ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-# Open-WebSearch MCP Server +# Open-WebSearch MCP 服务器 [![ModelScope](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Aas-ee/3af09e0f4c7821fb2e9acb96483a5ff0/raw/badge.json&color=%23de5a16)](https://www.modelscope.cn/mcp/servers/Aasee1/open-webSearch) [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Aas-ee/open-webSearch)](https://archestra.ai/mcp-catalog/aas-ee__open-websearch) @@ -9,140 +9,138 @@ ![License](https://img.shields.io/github/license/Aas-ee/open-websearch) ![Issues](https://img.shields.io/github/issues/Aas-ee/open-websearch) -**[🇨🇳 中文](./README-zh.md) | 🇺🇸 English** +**🇨🇳 中文 | [🇺🇸 English](./README-en.md)**
-A Model Context Protocol (MCP) server based on multi-engine search results, supporting free web search without API keys. +一个基于多引擎搜索结果的模型上下文协议(MCP)服务器,支持免费网络搜索,无需API密钥。
- Click to expand simple demo (deepseek-v3) + 点击展开查看简单示例效果(deepseek-v3) --- -### 🧩 Demo Start +### 🧩 示例开始 ### prompt: -> Use the websearch tool to search "Open-WebSearch MCP", using the csdn engine, query 20 records, tell me the engine returned by the tool, along with related info, then use the URL to fetch articles by author Aasee (if there are multiple, query them sequentially, not simultaneously). Format the output properly. +> 使用websearch工具查询 《Open-WebSearch MCP》,用csdn引擎,查20条记录,告诉我工具返回的engine,以及相关信息,再通过url查询作者是Aasee的文章内容(如果有多篇顺序查询,不要同时查询)。规范输出 --- ### assistant: -I will use the MCP_search tool to query "Open-WebSearch MCP" using the CSDN engine to fetch 20 records. +我将使用MCP_search工具查询《Open-WebSearch MCP》并使用CSDN引擎获取20条记录。 -### Query Result Analysis +### 查询结果分析 -1. **Search Engine Used**: CSDN -2. **Total Records**: 20 +1. **使用的搜索引擎**: CSDN +2. **总记录数**: 20条 -### Articles by Author Aasee -The search results show 2 articles by author "Aasee.": +### 作者Aasee的文章列表 +搜索结果显示有2篇作者是"Aasee."的文章: -1. **Article Title**: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" - **URL**: https://blog.csdn.net/weixin_45801664/article/details/149000138 - **Summary**: This project provides free, controllable web search capabilities for AI plugin development, supporting Bing, Baidu, CSDN multi-engine combined search... +1. **文章标题**: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" + **URL**: https://blog.csdn.net/weixin_45801664/article/details/149000138?ops_request_misc=%257B%2522request%255Fid%2522%253A%252203ceec270907178223ab139631d9a81f%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=03ceec270907178223ab139631d9a81f&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-149000138-null-null.142^v102^pc_search_result_base6&utm_term=Open-WebSearch%20MCP + **简介**: 该项目为AI插件开发提供免费、可控的联网搜索能力,支持Bing、百度、CSDN多引擎组合搜索... -2. **Article Title**: "Web Seach 搜索 MCP 启动!" - **URL**: https://blog.csdn.net/weixin_45801664/article/details/148813614 - **Summary**: Open-source Web Search MCP server Open-WebSearch launched, solving the problem of AI models unable to access latest web information... +2. **文章标题**: "Web Seach 搜索 MCP 启动!" + **URL**: https://blog.csdn.net/weixin_45801664/article/details/148813614?ops_request_misc=%257B%2522request%255Fid%2522%253A%252203ceec270907178223ab139631d9a81f%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=03ceec270907178223ab139631d9a81f&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-4-148813614-null-null.142^v102^pc_search_result_base6&utm_term=Open-WebSearch%20MCP + **简介**: 开源Web搜索MCP服务器Open-WebSearch上线,解决AI模型无法获取最新网络信息的问题... -### Fetching First Article Content -I will first fetch the full content of the first article: +### 开始查询第一篇文章内容 +我将先查询第一篇文章的全文内容: -# Search Result Analysis +# 搜索结果分析 -## Engine Info Returned by Tool -Used the CSDN search engine for the query, returning 20 related records. +## 工具返回的引擎信息 +使用了CSDN搜索引擎进行查询,返回了20条相关记录。 -## Articles by Author Aasee +## 作者Aasee的相关文章内容 -### Article 1: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" +### 文章1: "开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应!" ``` -🚀 Open-source AI Web Search Tool: Open-WebSearch MCP New Upgrade, Multi-engine + Streaming Response! +🚀 开源 AI 联网搜索工具:Open-WebSearch MCP 全新升级,支持多引擎 + 流式响应! -💡 "Let your AI plugins truly access the web" — No API Key needed, controllable search results, ready to use! +💡「让你的 AI 插件真正能联网」—— 不需要 API Key,搜索结果可控、开箱即用! -Project Highlights: -✅ Multi-engine Real-time Search: Supports Bing, Baidu, CSDN, DuckDuckGo, Exa, Brave -✅ Streaming Response (MCP Protocol): Compatible with Claude plugin (MCP) protocol -✅ Article Content Extraction: Supports extracting full CSDN blog articles -✅ Completely Free & No API Key Required -✅ One-click Docker Deployment +项目亮点一览: +✅ 多引擎实时搜索:支持 Bing、百度、CSDN、DuckDuckGo、Exa、Brave +✅ 流式响应(MCP协议):兼容 Claude 插件(MCP)协议 +✅ 文章正文抓取:支持提取 CSDN 博客文章全文 +✅ 完全免费 & 无需 API Key +✅ Docker 一键部署 -Installation: -- Git clone: git clone https://github.com/Aas-ee/open-webSearch -- Or use Docker: docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest +安装方法: +- Git克隆项目: git clone https://github.com/Aas-ee/open-webSearch +- 或使用Docker: docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest -MCP Tool Support: -🔍 search: Multi-engine search tool -📄 fetchCsdnArticle: Extract CSDN article full text -🧪 fetchLinuxDoArticle: Fetch Linux.do articles (in development) +MCP工具支持: +🔍 search:多引擎搜索工具 +📄 fetchCsdnArticle:提取 CSDN 文章全文 +🧪 fetchLinuxDoArticle:抓取 Linux.do 文章(开发中) -Project Links: +项目地址: 🧩 GitHub: https://github.com/Aas-ee/open-webSearch 📦 Docker: ghcr.io/aas-ee/open-web-search:latest ``` -### Article 2: "Web Seach 搜索 MCP 启动!" +### 文章2: "Web Seach 搜索 MCP 启动!" ``` -Open-source Web Search MCP server Open-WebSearch launched, solving the problem of AI models -unable to access latest web information. -The project provides free Baidu search results API, supports structured JSON return format, -compatible with Claude and other AI tools' MCP protocol. -Users only need simple installation to use, no API keys required... +开源Web搜索MCP服务器Open-WebSearch上线,解决AI模型无法获取最新网络信息的问题。 +该项目免费提供百度搜索结果API,支持结构化JSON返回格式,兼容Claude等AI工具的MCP协议。 +用户仅需简单安装即可使用,无需API密钥... ``` -Would you like me to look up other articles by author Aasee? I can continue searching for you. +需要查询其他Aasee作者的文章内容吗?我可以继续为您检索。 -### ✅ Demo End +### ✅ 示例结束 ---
-## Features +## 功能特性 -- Web search using multi-engine results +- 使用多引擎搜索结果进行网络检索 - bing - baidu - - ~~linux.do~~ temporarily unsupported + - ~~linux.do~~ 暂不支持 - csdn - duckduckgo - exa - brave - juejin -- HTTP proxy configuration support for accessing restricted resources -- No API keys or authentication required -- Returns structured results with titles, URLs, and descriptions -- Configurable number of results per search -- Customizable default search engine -- Support for fetching individual article content +- 支持HTTP代理配置,轻松解决网络访问限制 +- 无需API密钥或身份验证 +- 返回带标题、URL和描述的结构化结果 +- 可配置每次搜索返回的结果数量 +- 可自定义默认搜索引擎 +- 支持获取单篇文章内容 - csdn - - github (README files) + - github(README文件) ## TODO -- Support for ~~Bing~~ (already supported), ~~DuckDuckGo~~ (already supported), ~~Exa~~ (already supported), ~~Brave~~ (already supported), Google and other search engines -- Support for more blogs, forums, and social platforms -- Optimize article content extraction, add support for more sites -- ~~Support for GitHub README fetching~~ (already supported) +- 支持~~Bing~~(已支持),~~DuckDuckGo~~(已支持),~~Exa~~(已支持),~~Brave~~(已支持),Google等搜索引擎 +- 支持更多博客论坛、社交软件 +- 优化文章内容提取功能,增加更多站点支持 +- ~~支持GitHub README获取~~(已支持) -## Installation Guide +## 安装指南 -### NPX Quick Start (Recommended) +### NPX 快速启动(推荐) -The fastest way to get started: +最快的使用方式: ```bash -# Basic usage +# 基本使用 npx open-websearch@latest -# With environment variables (Linux/macOS) +# 带环境变量(Linux/macOS) DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true npx open-websearch@latest # Windows PowerShell @@ -151,55 +149,55 @@ $env:DEFAULT_SEARCH_ENGINE="duckduckgo"; $env:ENABLE_CORS="true"; npx open-webse # Windows CMD set MODE=stdio && set DEFAULT_SEARCH_ENGINE=duckduckgo && npx open-websearch@latest -# Cross-platform (requires cross-env, Used for local development) -# Global install +# 跨平台(需要 cross-env,用于本地开发) +# 全局安装 npm install -g open-websearch npx cross-env DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true open-websearch ``` -**Environment Variables:** - -| Variable | Default | Options | Description | -|----------|-------------------------|---------|-------------| -| `ENABLE_CORS` | `false` | `true`, `false` | Enable CORS | -| `CORS_ORIGIN` | `*` | Any valid origin | CORS origin configuration | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin` | Default search engine | -| `USE_PROXY` | `false` | `true`, `false` | Enable HTTP proxy | -| `PROXY_URL` | `http://127.0.0.1:7890` | Any valid URL | Proxy server URL | -| `MODE` | `both` | `both`, `http`, `stdio` | Server mode: both HTTP+STDIO, HTTP only, or STDIO only | -| `PORT` | `3000` | 1-65535 | Server port | -| `ALLOWED_SEARCH_ENGINES` | empty (all available) | Comma-separated engine names | Limit which search engines can be used; if the default engine is not in this list, the first allowed engine becomes the default | -| `MCP_TOOL_SEARCH_NAME` | `search` | Valid MCP tool name | Custom name for the search tool | -| `MCP_TOOL_FETCH_LINUXDO_NAME` | `fetchLinuxDoArticle` | Valid MCP tool name | Custom name for the Linux.do article fetch tool | -| `MCP_TOOL_FETCH_CSDN_NAME` | `fetchCsdnArticle` | Valid MCP tool name | Custom name for the CSDN article fetch tool | -| `MCP_TOOL_FETCH_GITHUB_NAME` | `fetchGithubReadme` | Valid MCP tool name | Custom name for the GitHub README fetch tool | -| `MCP_TOOL_FETCH_JUEJIN_NAME` | `fetchJuejinArticle` | Valid MCP tool name | Custom name for the Juejin article fetch tool | - -**Common configurations:** +**环境变量说明:** + +| 变量名 | 默认值 | 可选值 | 说明 | +|--------|-------------------------|--------|--------------------------------------| +| `ENABLE_CORS` | `false` | `true`, `false` | 启用CORS | +| `CORS_ORIGIN` | `*` | 任何有效来源 | CORS来源配置 | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin` | 默认搜索引擎 | +| `USE_PROXY` | `false` | `true`, `false` | 启用HTTP代理 | +| `PROXY_URL` | `http://127.0.0.1:7890` | 任何有效URL | 代理服务器URL | +| `MODE` | `both` | `both`, `http`, `stdio` | 服务器模式:同时支持HTTP+STDIO、仅HTTP或仅STDIO | +| `PORT` | `3000` | 1-65535 | 服务器端口 | +| `ALLOWED_SEARCH_ENGINES` | 空(全部可用) | 逗号分隔的引擎名称 | 限制可使用的搜索引擎,如默认搜索引擎不在范围,则默认第一个为默认搜索引擎 | +| `MCP_TOOL_SEARCH_NAME` | `search` | 有效的MCP工具名称 | 搜索工具的自定义名称 | +| `MCP_TOOL_FETCH_LINUXDO_NAME` | `fetchLinuxDoArticle` | 有效的MCP工具名称 | Linux.do文章获取工具的自定义名称 | +| `MCP_TOOL_FETCH_CSDN_NAME` | `fetchCsdnArticle` | 有效的MCP工具名称 | CSDN文章获取工具的自定义名称 | +| `MCP_TOOL_FETCH_GITHUB_NAME` | `fetchGithubReadme` | 有效的MCP工具名称 | GitHub README获取工具的自定义名称 | +| `MCP_TOOL_FETCH_JUEJIN_NAME` | `fetchJuejinArticle` | 有效的MCP工具名称 | 掘金文章获取工具的自定义名称 | + +**常用配置示例:** ```bash -# Enable proxy for restricted regions +# 启用代理(适用于网络受限地区) USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 npx open-websearch@latest -# Full configuration +# 完整配置 DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true USE_PROXY=true PROXY_URL=http://127.0.0.1:7890 PORT=8080 npx open-websearch@latest ``` -**Windows Users Note:** -- In PowerShell, use `$env:VAR="value"; ` syntax -- For local development, it's recommended to use `npx cross-env` for cross-platform compatibility +**Windows 用户注意事项:** +- 在 PowerShell 中使用 `$env:VAR="value"; ` 语法 +- 本地开发推荐使用 `npx cross-env` 实现跨平台兼容 -### Local Installation +### 本地安装 -1. Clone or download this repository -2. Install dependencies: +1. 克隆或下载本仓库 +2. 安装依赖项: ```bash npm install ``` -3. Build the server: +3. 构建服务器: ```bash npm run build ``` -4. Add the server to your MCP configuration: +4. 将服务器添加到您的MCP配置中: **Cherry Studio:** ```json @@ -216,7 +214,7 @@ npm run build } ``` -**VSCode (Claude Dev Extension):** +**VSCode版(Claude开发扩展):** ```json { "mcpServers": { @@ -236,7 +234,7 @@ npm run build } ``` -**Claude Desktop:** +**Claude桌面版:** ```json { "mcpServers": { @@ -252,7 +250,7 @@ npm run build } ``` -**NPX Command Line Configuration:** +**NPX命令行配置示例:** ```json { "mcpServers": { @@ -271,13 +269,13 @@ npm run build } ``` -**Local STDIO Configuration for Cherry Studio (Windows):** +**Cherry Studio 本地 STDIO 配置 (Windows):** ```json { "mcpServers": { "open-websearch-local": { "command": "node", - "args": ["C:/path/to/your/project/build/index.js"], + "args": ["C:/你的项目路径/build/index.js"], "env": { "MODE": "stdio", "DEFAULT_SEARCH_ENGINE": "duckduckgo", @@ -288,31 +286,31 @@ npm run build } ``` -### Docker Deployment +### Docker部署 -Quick deployment using Docker Compose: +使用Docker Compose快速部署: ```bash docker-compose up -d ``` -Or use Docker directly: +或者直接使用Docker: ```bash docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/aas-ee/open-web-search:latest ``` -Environment variable configuration: +配置环境变量说明: -| Variable | Default | Options | Description | -|----------|-------------------------|---------|-------------| -| `ENABLE_CORS` | `false` | `true`, `false` | Enable CORS | -| `CORS_ORIGIN` | `*` | Any valid origin | CORS origin configuration | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave` | Default search engine | -| `USE_PROXY` | `false` | `true`, `false` | Enable HTTP proxy | -| `PROXY_URL` | `http://127.0.0.1:7890` | Any valid URL | Proxy server URL | -| `PORT` | `3000` | 1-65535 | Server port | +| 变量名 | 默认值 | 可选值 | 说明 | +|--------|-------------------------|--------|------| +| `ENABLE_CORS` | `false` | `true`, `false` | 启用CORS | +| `CORS_ORIGIN` | `*` | 任何有效来源 | CORS来源配置 | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave` | 默认搜索引擎 | +| `USE_PROXY` | `false` | `true`, `false` | 启用HTTP代理 | +| `PROXY_URL` | `http://127.0.0.1:7890` | 任何有效URL | 代理服务器URL | +| `PORT` | `3000` | 1-65535 | 服务器端口 | -Then configure in your MCP client: +然后在MCP客户端中配置: ```json { "mcpServers": { @@ -336,57 +334,58 @@ Then configure in your MCP client: } ``` -## Usage Guide +## 使用说明 -The server provides four tools: `search`, `fetchLinuxDoArticle`, `fetchCsdnArticle`, and `fetchGithubReadme`. +服务器提供四个工具:`search`、`fetchLinuxDoArticle`、`fetchCsdnArticle` 和 `fetchGithubReadme`。 -### search Tool Usage +### search工具使用说明 ```typescript { - "query": string, // Search query - "limit": number, // Optional: Number of results to return (default: 10) - "engines": string[] // Optional: Engines to use (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) default bing + "query": string, // 搜索查询词 + "limit": number, // 可选:返回结果数量(默认:10) + "engines": string[] // 可选:使用的引擎 (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) 默认bing } ``` -Usage example: +使用示例: ```typescript use_mcp_tool({ server_name: "web-search", tool_name: "search", arguments: { - query: "search content", - limit: 3, // Optional parameter - engines: ["bing", "csdn", "duckduckgo", "exa", "brave", "juejin"] // Optional parameter, supports multi-engine combined search + query: "搜索内容", + limit: 3, // 可选参数 + engines: ["bing", "csdn", "duckduckgo", "exa", "brave", "juejin"] // 可选参数,支持多引擎组合搜索 } }) ``` -Response example: +返回示例: ```json [ { - "title": "Example Search Result", + "title": "示例搜索结果", "url": "https://example.com", - "description": "Description text of the search result...", - "source": "Source", - "engine": "Engine used" + "description": "搜索结果的描述文本...", + "source": "来源", + "engine": "使用的引擎" } ] ``` -### fetchCsdnArticle Tool Usage -Used to fetch complete content of CSDN blog articles. +### fetchCsdnArticle工具使用说明 + +用于获取CSDN博客文章的完整内容。 ```typescript { - "url": string // URL from CSDN search results using the search tool + "url": string // search 工具使用csdn查询出的url } ``` -Usage example: +使用示例: ```typescript use_mcp_tool({ server_name: "web-search", @@ -397,26 +396,26 @@ use_mcp_tool({ }) ``` -Response example: +返回示例: ```json [ { - "content": "Example search result" + "content": "示例搜索结果" } ] ``` -### fetchLinuxDoArticle Tool Usage +### fetchLinuxDoArticle工具使用说明 -Used to fetch complete content of Linux.do forum articles. +用于获取Linux.do论坛文章的完整内容。 ```typescript { - "url": string // URL from linuxdo search results using the search tool + "url": string // search 工具使用linuxdo查询出的url } ``` -Usage example: +使用示例: ```typescript use_mcp_tool({ server_name: "web-search", @@ -427,26 +426,28 @@ use_mcp_tool({ }) ``` -Response example: +返回示例: ```json [ { - "content": "Example search result" + "content": "示例搜索结果" } ] + ``` -### fetchGithubReadme Tool Usage -Used to fetch README content from GitHub repositories. +### fetchGithubReadme工具使用说明 + +用于获取GitHub仓库的README文件内容。 ```typescript { - "url": string // GitHub repository URL (supports HTTPS, SSH formats) + "url": string // GitHub仓库URL(支持HTTPS、SSH格式) } ``` -Usage example: +使用示例: ```typescript use_mcp_tool({ server_name: "web-search", @@ -457,13 +458,13 @@ use_mcp_tool({ }) ``` -Supported URL formats: +支持的URL格式: - HTTPS: `https://github.com/owner/repo` - HTTPS with .git: `https://github.com/owner/repo.git` - SSH: `git@github.com:owner/repo.git` -- URLs with parameters: `https://github.com/owner/repo?tab=readme` +- 带参数的URL: `https://github.com/owner/repo?tab=readme` -Response example: +返回示例: ```json [ { @@ -472,17 +473,18 @@ Response example: ] ``` -### fetchJuejinArticle Tool Usage -Used to fetch complete content of Juejin articles. +### fetchJuejinArticle工具使用说明 + +用于获取掘金文章的完整内容。 ```typescript { - "url": string // Juejin article URL from search results + "url": string // 掘金文章URL } ``` -Usage example: +使用示例: ```typescript use_mcp_tool({ server_name: "web-search", @@ -493,10 +495,10 @@ use_mcp_tool({ }) ``` -Supported URL format: -- `https://juejin.cn/post/{article_id}` +支持的URL格式: +- `https://juejin.cn/post/{文章ID}` -Response example: +返回示例: ```json [ { @@ -505,97 +507,98 @@ Response example: ] ``` -## Usage Limitations -Since this tool works by scraping multi-engine search results, please note the following important limitations: +## 使用限制 + +由于本工具通过爬取多引擎搜索结果实现,请注意以下重要限制: -1. **Rate Limiting**: - - Too many searches in a short time may cause the used engines to temporarily block requests - - Recommendations: - - Maintain reasonable search frequency - - Use the limit parameter judiciously - - Add delays between searches when necessary +1. **频率限制**: + - 短时间内搜索次数过多可能导致使用的引擎暂时屏蔽请求 + - 建议: + - 保持合理的搜索频率 + - 审慎使用limit参数 + - 必要时可在搜索间设置延迟 -2. **Result Accuracy**: - - Depends on the HTML structure of corresponding engines, may fail when engines update - - Some results may lack metadata like descriptions - - Complex search operators may not work as expected +2. **结果准确性**: + - 依赖对应引擎的HTML结构,可能随引擎改版失效 + - 部分结果可能缺失描述等元数据 + - 复杂搜索运算符可能无法按预期工作 -3. **Legal Terms**: - - This tool is for personal use only - - Please comply with the terms of service of corresponding engines - - Implement appropriate rate limiting based on your actual use case +3. **法律条款**: + - 本工具仅限个人使用 + - 请遵守对应引擎的服务条款 + - 建议根据实际使用场景实施适当的频率限制 -4. **Search Engine Configuration**: - - Default search engine can be set via the `DEFAULT_SEARCH_ENGINE` environment variable - - Supported engines: bing, duckduckgo, exa, brave - - The default engine is used when searching specific websites +4. **搜索引擎配置**: + - 可通过环境变量`DEFAULT_SEARCH_ENGINE`设置默认搜索引擎 + - 支持的引擎有:bing, duckduckgo, exa, brave + - 当搜索特定网站内容时,会自动使用默认搜索引擎 -5. **Proxy Configuration**: - - HTTP proxy can be configured when certain search engines are unavailable in specific regions - - Enable proxy with environment variable `USE_PROXY=true` - - Configure proxy server address with `PROXY_URL` +5. **代理服务配置**: + - 当某些搜索引擎在特定地区不可用时,可配置HTTP代理 + - 通过环境变量`USE_PROXY=true`启用代理 + - 使用`PROXY_URL`配置代理服务器地址 -## Contributing +## 贡献指南 -Welcome to submit issue reports and feature improvement suggestions! +欢迎提交问题报告和功能改进建议! -### Contributor Guide +### 贡献者指南 -If you want to fork this repository and publish your own Docker image, you need to make the following configurations: +如果您想要fork本仓库并发布自己的Docker镜像,需要进行以下配置: -#### GitHub Secrets Configuration +#### GitHub Secrets配置 -To enable automatic Docker image building and publishing, please add the following secrets in your GitHub repository settings (Settings → Secrets and variables → Actions): +要启用自动Docker镜像构建和发布功能,请在您的GitHub仓库设置中添加以下secrets(Settings → Secrets and variables → Actions): -**Required Secrets:** -- `GITHUB_TOKEN`: Automatically provided by GitHub (no setup needed) +**必需的Secrets:** +- `GITHUB_TOKEN`: GitHub自动提供(无需设置) -**Optional Secrets (for Alibaba Cloud ACR):** -- `ACR_REGISTRY`: Your Alibaba Cloud Container Registry URL (e.g., `registry.cn-hangzhou.aliyuncs.com`) -- `ACR_USERNAME`: Your Alibaba Cloud ACR username -- `ACR_PASSWORD`: Your Alibaba Cloud ACR password -- `ACR_IMAGE_NAME`: Your image name in ACR (e.g., `your-namespace/open-web-search`) +**可选的Secrets(用于阿里云ACR):** +- `ACR_REGISTRY`: 您的阿里云容器镜像服务URL(例如:`registry.cn-hangzhou.aliyuncs.com`) +- `ACR_USERNAME`: 您的阿里云ACR用户名 +- `ACR_PASSWORD`: 您的阿里云ACR密码 +- `ACR_IMAGE_NAME`: 您在ACR中的镜像名称(例如:`your-namespace/open-web-search`) -#### CI/CD Workflow +#### CI/CD工作流程 -The repository includes a GitHub Actions workflow (`.github/workflows/docker.yml`) that automatically: +仓库包含一个GitHub Actions工作流程(`.github/workflows/docker.yml`),会自动: -1. **Trigger Conditions**: - - Push to `main` branch - - Push version tags (`v*`) - - Manual workflow trigger +1. **触发条件**: + - 推送到`main`分支 + - 推送版本标签(`v*`) + - 手动触发workflow -2. **Build and Push to**: - - GitHub Container Registry (ghcr.io) - always enabled - - Alibaba Cloud Container Registry - only enabled when ACR secrets are configured +2. **构建并推送到**: + - GitHub Container Registry (ghcr.io) - 始终启用 + - 阿里云容器镜像服务 - 仅在配置ACR secrets时启用 -3. **Image Tags**: - - `ghcr.io/your-username/open-web-search:latest` - - `your-acr-address/your-image-name:latest` (if ACR is configured) +3. **镜像标签**: + - `ghcr.io/您的用户名/open-web-search:latest` + - `您的ACR地址/您的镜像名:latest`(如果配置了ACR) -#### Fork and Publish Steps: +#### Fork和发布步骤: -1. **Fork the repository** to your GitHub account -2. **Configure secrets** (if you need ACR publishing): - - Go to Settings → Secrets and variables → Actions in your forked repository - - Add the ACR-related secrets listed above -3. **Push changes** to the `main` branch or create version tags -4. **GitHub Actions will automatically build and push** your Docker image -5. **Use your image**, update the Docker command: +1. **Fork仓库**到您的GitHub账户 +2. **配置secrets**(如果需要ACR发布): + - 进入您fork的仓库的Settings → Secrets and variables → Actions + - 添加上面列出的ACR相关secrets +3. **推送更改**到`main`分支或创建版本标签 +4. **GitHub Actions将自动构建并推送**您的Docker镜像 +5. **使用您的镜像**,更新Docker命令: ```bash - docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/your-username/open-web-search:latest + docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN=* ghcr.io/您的用户名/open-web-search:latest ``` -#### Notes: -- If you don't configure ACR secrets, the workflow will only publish to GitHub Container Registry -- Make sure your GitHub repository has Actions enabled -- The workflow will use your GitHub username (converted to lowercase) as the GHCR image name +#### 注意事项: +- 如果您不配置ACR secrets,工作流程将只发布到GitHub Container Registry +- 确保您的GitHub仓库已启用Actions功能 +- 工作流程会使用您的GitHub用户名(转换为小写)作为GHCR镜像名称
## Star History -If you find this project helpful, please consider giving it a ⭐ Star! +如果项目对你有帮助,请考虑给个⭐ Star! [![Star History Chart](https://api.star-history.com/svg?repos=Aas-ee/open-webSearch&type=Date)](https://www.star-history.com/#Aas-ee/open-webSearch&Date) From d33ac95c277a0dc200f10065831d032d56989752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 17:02:05 +0800 Subject: [PATCH 06/15] =?UTF-8?q?docs:=20=E4=BF=AE=E5=A4=8DREADME=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=B8=8E=E4=BB=A3=E7=A0=81=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 PROXY_URL 默认值为 http://127.0.0.1:10809(与代码一致) - 新增 MAX_DESCRIPTION_LENGTH 环境变量说明 - 新增 search 工具 maxDescriptionLength 参数说明 - 补充 DEFAULT_SEARCH_ENGINE 可选值 linuxdo - 修正工具数量描述:四个→五个(补充 fetchJuejinArticle) - 补充文章获取功能支持的站点:juejin、linux.do - 同步更新 Docker 部署环境变量表(补充缺失的变量和引擎) - 补充 limit 参数范围说明 (1-50) - 补充 engines 参数大小写不敏感说明 - 中英文 README 同步更新 --- README-en.md | 21 ++++++++++++++------- README.md | 21 ++++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README-en.md b/README-en.md index 58a65c5..2f8f354 100644 --- a/README-en.md +++ b/README-en.md @@ -125,6 +125,8 @@ Would you like me to look up other articles by author Aasee? I can continue sear - Support for fetching individual article content - csdn - github (README files) + - juejin + - linux.do ## TODO - Support for ~~Bing~~ (already supported), ~~DuckDuckGo~~ (already supported), ~~Exa~~ (already supported), ~~Brave~~ (already supported), Google and other search engines @@ -163,12 +165,13 @@ npx cross-env DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true open-websearch |----------|-------------------------|---------|-------------| | `ENABLE_CORS` | `false` | `true`, `false` | Enable CORS | | `CORS_ORIGIN` | `*` | Any valid origin | CORS origin configuration | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin` | Default search engine | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin`, `linuxdo` | Default search engine | | `USE_PROXY` | `false` | `true`, `false` | Enable HTTP proxy | -| `PROXY_URL` | `http://127.0.0.1:7890` | Any valid URL | Proxy server URL | +| `PROXY_URL` | `http://127.0.0.1:10809` | Any valid URL | Proxy server URL | | `MODE` | `both` | `both`, `http`, `stdio` | Server mode: both HTTP+STDIO, HTTP only, or STDIO only | | `PORT` | `3000` | 1-65535 | Server port | | `ALLOWED_SEARCH_ENGINES` | empty (all available) | Comma-separated engine names | Limit which search engines can be used; if the default engine is not in this list, the first allowed engine becomes the default | +| `MAX_DESCRIPTION_LENGTH` | unlimited | Positive integer | Global max length for search result descriptions, excess will be truncated | | `MCP_TOOL_SEARCH_NAME` | `search` | Valid MCP tool name | Custom name for the search tool | | `MCP_TOOL_FETCH_LINUXDO_NAME` | `fetchLinuxDoArticle` | Valid MCP tool name | Custom name for the Linux.do article fetch tool | | `MCP_TOOL_FETCH_CSDN_NAME` | `fetchCsdnArticle` | Valid MCP tool name | Custom name for the CSDN article fetch tool | @@ -307,10 +310,13 @@ Environment variable configuration: |----------|-------------------------|---------|-------------| | `ENABLE_CORS` | `false` | `true`, `false` | Enable CORS | | `CORS_ORIGIN` | `*` | Any valid origin | CORS origin configuration | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave` | Default search engine | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin`, `linuxdo` | Default search engine | | `USE_PROXY` | `false` | `true`, `false` | Enable HTTP proxy | -| `PROXY_URL` | `http://127.0.0.1:7890` | Any valid URL | Proxy server URL | +| `PROXY_URL` | `http://127.0.0.1:10809` | Any valid URL | Proxy server URL | +| `MODE` | `both` | `both`, `http`, `stdio` | Server mode | | `PORT` | `3000` | 1-65535 | Server port | +| `ALLOWED_SEARCH_ENGINES` | empty (all available) | Comma-separated engine names | Limit which search engines can be used | +| `MAX_DESCRIPTION_LENGTH` | unlimited | Positive integer | Global max length for search result descriptions | Then configure in your MCP client: ```json @@ -338,15 +344,16 @@ Then configure in your MCP client: ## Usage Guide -The server provides four tools: `search`, `fetchLinuxDoArticle`, `fetchCsdnArticle`, and `fetchGithubReadme`. +The server provides five tools: `search`, `fetchCsdnArticle`, `fetchGithubReadme`, `fetchJuejinArticle`, and `fetchLinuxDoArticle`. ### search Tool Usage ```typescript { "query": string, // Search query - "limit": number, // Optional: Number of results to return (default: 10) - "engines": string[] // Optional: Engines to use (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) default bing + "limit": number, // Optional: Number of results to return (default: 10, range: 1-50) + "engines": string[], // Optional: Engines to use (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) default bing, case-insensitive + "maxDescriptionLength": number // Optional: Max description length per call, overrides global MAX_DESCRIPTION_LENGTH } ``` diff --git a/README.md b/README.md index 53bc8ae..4209380 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,8 @@ MCP工具支持: - 支持获取单篇文章内容 - csdn - github(README文件) + - 掘金(juejin) + - linux.do ## TODO - 支持~~Bing~~(已支持),~~DuckDuckGo~~(已支持),~~Exa~~(已支持),~~Brave~~(已支持),Google等搜索引擎 @@ -161,12 +163,13 @@ npx cross-env DEFAULT_SEARCH_ENGINE=duckduckgo ENABLE_CORS=true open-websearch |--------|-------------------------|--------|--------------------------------------| | `ENABLE_CORS` | `false` | `true`, `false` | 启用CORS | | `CORS_ORIGIN` | `*` | 任何有效来源 | CORS来源配置 | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin` | 默认搜索引擎 | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin`, `linuxdo` | 默认搜索引擎 | | `USE_PROXY` | `false` | `true`, `false` | 启用HTTP代理 | -| `PROXY_URL` | `http://127.0.0.1:7890` | 任何有效URL | 代理服务器URL | +| `PROXY_URL` | `http://127.0.0.1:10809` | 任何有效URL | 代理服务器URL | | `MODE` | `both` | `both`, `http`, `stdio` | 服务器模式:同时支持HTTP+STDIO、仅HTTP或仅STDIO | | `PORT` | `3000` | 1-65535 | 服务器端口 | | `ALLOWED_SEARCH_ENGINES` | 空(全部可用) | 逗号分隔的引擎名称 | 限制可使用的搜索引擎,如默认搜索引擎不在范围,则默认第一个为默认搜索引擎 | +| `MAX_DESCRIPTION_LENGTH` | 无限制 | 正整数 | 搜索结果描述的全局最大长度,超出部分将被截断 | | `MCP_TOOL_SEARCH_NAME` | `search` | 有效的MCP工具名称 | 搜索工具的自定义名称 | | `MCP_TOOL_FETCH_LINUXDO_NAME` | `fetchLinuxDoArticle` | 有效的MCP工具名称 | Linux.do文章获取工具的自定义名称 | | `MCP_TOOL_FETCH_CSDN_NAME` | `fetchCsdnArticle` | 有效的MCP工具名称 | CSDN文章获取工具的自定义名称 | @@ -305,10 +308,13 @@ docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN= |--------|-------------------------|--------|------| | `ENABLE_CORS` | `false` | `true`, `false` | 启用CORS | | `CORS_ORIGIN` | `*` | 任何有效来源 | CORS来源配置 | -| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave` | 默认搜索引擎 | +| `DEFAULT_SEARCH_ENGINE` | `bing` | `bing`, `duckduckgo`, `exa`, `brave`, `baidu`, `csdn`, `juejin`, `linuxdo` | 默认搜索引擎 | | `USE_PROXY` | `false` | `true`, `false` | 启用HTTP代理 | -| `PROXY_URL` | `http://127.0.0.1:7890` | 任何有效URL | 代理服务器URL | +| `PROXY_URL` | `http://127.0.0.1:10809` | 任何有效URL | 代理服务器URL | +| `MODE` | `both` | `both`, `http`, `stdio` | 服务器模式 | | `PORT` | `3000` | 1-65535 | 服务器端口 | +| `ALLOWED_SEARCH_ENGINES` | 空(全部可用) | 逗号分隔的引擎名称 | 限制可使用的搜索引擎 | +| `MAX_DESCRIPTION_LENGTH` | 无限制 | 正整数 | 搜索结果描述最大长度 | 然后在MCP客户端中配置: ```json @@ -336,15 +342,16 @@ docker run -d --name web-search -p 3000:3000 -e ENABLE_CORS=true -e CORS_ORIGIN= ## 使用说明 -服务器提供四个工具:`search`、`fetchLinuxDoArticle`、`fetchCsdnArticle` 和 `fetchGithubReadme`。 +服务器提供五个工具:`search`、`fetchCsdnArticle`、`fetchGithubReadme`、`fetchJuejinArticle` 和 `fetchLinuxDoArticle`。 ### search工具使用说明 ```typescript { "query": string, // 搜索查询词 - "limit": number, // 可选:返回结果数量(默认:10) - "engines": string[] // 可选:使用的引擎 (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) 默认bing + "limit": number, // 可选:返回结果数量(默认:10,范围:1-50) + "engines": string[], // 可选:使用的引擎 (bing,baidu,linuxdo,csdn,duckduckgo,exa,brave,juejin) 默认bing,大小写不敏感 + "maxDescriptionLength": number // 可选:单次调用的描述最大长度,优先级高于全局 MAX_DESCRIPTION_LENGTH } ``` From 1cff398aed84f614adb9ec92eb1a987eaa78540f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 17:06:44 +0800 Subject: [PATCH 07/15] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=20linux.do=20?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=BC=95=E6=93=8E=E7=8A=B6=E6=80=81=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=EF=BC=88=E5=B7=B2=E6=94=AF=E6=8C=81=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=88=A0=E9=99=A4=E7=BA=BF=E6=A0=87=E8=AE=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README-en.md b/README-en.md index 2f8f354..7315ee3 100644 --- a/README-en.md +++ b/README-en.md @@ -111,7 +111,7 @@ Would you like me to look up other articles by author Aasee? I can continue sear - Web search using multi-engine results - bing - baidu - - ~~linux.do~~ temporarily unsupported + - linux.do - csdn - duckduckgo - exa diff --git a/README.md b/README.md index 4209380..995bf3f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ MCP工具支持: - 使用多引擎搜索结果进行网络检索 - bing - baidu - - ~~linux.do~~ 暂不支持 + - linux.do - csdn - duckduckgo - exa From c1878f0f721cae3480efc0a4f9a91843a111f3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 19:56:52 +0800 Subject: [PATCH 08/15] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Bing=20?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E8=B4=A8=E9=87=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重写 bing.ts,通过搜索框 (#sb_form_q) 提交查询,而非直接 URL 导航(Bing 对直接 URL 返回降级结果) - 将浏览器从 headless 模式切换为 GUI 模式(Bing 在服务端检测 headless) - 在 Windows 上通过 WMI Win32_ProcessStartup ShowWindow=0 (SW_HIDE) 隐藏浏览器窗口 - 新增 test-bing-quality.ts,使用随机后缀方法验证搜索结果相关性 --- src/engines/bing/bing.ts | 118 +++++++++++++++++----------------- src/engines/shared/browser.ts | 12 +++- src/test/test-bing-quality.ts | 67 +++++++++++++++++++ 3 files changed, 134 insertions(+), 63 deletions(-) create mode 100644 src/test/test-bing-quality.ts diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index 67cb5fb..8dab979 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -1,6 +1,7 @@ import * as cheerio from 'cheerio'; import { SearchResult } from '../../types.js'; import { getSharedBrowser, destroySharedBrowser } from '../shared/browser.js'; +import type { Page } from 'puppeteer-core'; /** * 解码 Bing 重定向 URL,提取实际目标地址。 @@ -25,81 +26,78 @@ function decodeBingUrl(bingUrl: string): string { } } -let bingWarmedUp = false; +function parsePageResults(html: string): SearchResult[] { + const $ = cheerio.load(html); + const results: SearchResult[] = []; + $('#b_results h2').each((i, element) => { + const linkElement = $(element).find('a').first(); + if (linkElement.length) { + const rawUrl = linkElement.attr('href'); + if (rawUrl && rawUrl.startsWith('http')) { + const url = decodeBingUrl(rawUrl); + const parentLi = $(element).closest('li'); + const snippetElement = parentLi.find('p').first(); + const sourceElement = parentLi.find('.b_tpcn'); + results.push({ + title: linkElement.text().trim(), + url: url, + description: snippetElement.text().trim() || '', + source: sourceElement.text().trim() || '', + engine: 'bing' + }); + } + } + }); + return results; +} /** - * 预热请求:先访问 cn.bing.com 建立有效的搜索会话。 - * cn.bing.com 对多词中文查询需要有效的会话 cookie, - * 否则会返回随机的无关内容。 + * 通过 Bing 搜索框提交查询。 + * Bing 会对直接 URL 导航返回降级的搜索结果(尤其对 "MCP" 等缩写词), + * 但通过搜索框表单提交(带 form=QBLH 参数)则能得到正确结果。 */ -async function warmUpBingSession(): Promise { - if (bingWarmedUp) return; - const browser = await getSharedBrowser(); - const page = await browser.newPage(); - try { - await page.goto('https://cn.bing.com/search?q=test', { waitUntil: 'networkidle2', timeout: 15000 }); - await new Promise(r => setTimeout(r, 500)); - bingWarmedUp = true; - } finally { - await page.close(); +async function submitSearchViaSearchBox(page: Page, query: string): Promise { + await page.goto('https://www.bing.com', { waitUntil: 'networkidle2', timeout: 15000 }); + await new Promise(r => setTimeout(r, 500)); + // 清空搜索框(可能有残留内容)并输入查询 + const searchBox = await page.$('#sb_form_q'); + if (searchBox) { + await searchBox.click({ clickCount: 3 }); // 选中全部 + await page.keyboard.press('Backspace'); // 删除 } + await page.type('#sb_form_q', query, { delay: 5 }); + await page.keyboard.press('Enter'); + await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }); + await new Promise(r => setTimeout(r, 500)); } export async function searchBing(query: string, limit: number): Promise { try { - await warmUpBingSession(); const browser = await getSharedBrowser(); + const page = await browser.newPage(); - let allResults: SearchResult[] = []; - let pn = 0; - - while (allResults.length < limit) { - const page = await browser.newPage(); - - const searchUrl = `https://cn.bing.com/search?q=${encodeURIComponent(query)}&first=${1 + pn * 10}`; - await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 15000 }); - await new Promise(r => setTimeout(r, 1000)); - - const html = await page.content(); - await page.close(); - - const $ = cheerio.load(html); - const results: SearchResult[] = []; - - $('#b_results h2').each((i, element) => { - const linkElement = $(element).find('a').first(); - if (linkElement.length) { - const rawUrl = linkElement.attr('href'); - if (rawUrl && rawUrl.startsWith('http')) { - const url = decodeBingUrl(rawUrl); - const parentLi = $(element).closest('li'); - const snippetElement = parentLi.find('p').first(); - const sourceElement = parentLi.find('.b_tpcn'); - - results.push({ - title: linkElement.text().trim(), - url: url, - description: snippetElement.text().trim() || '', - source: sourceElement.text().trim() || '', - engine: 'bing' - }); - } - } - }); + try { + // 第一页:通过搜索框提交 + await submitSearchViaSearchBox(page, query); + let allResults = parsePageResults(await page.content()); - allResults = allResults.concat(results); - - if (results.length === 0) { - console.error('⚠️ No more results, ending early....'); - break; + // 后续页:在已有 session 中直接翻页 + while (allResults.length < limit) { + const nextLink = await page.$('.sb_pagN'); + if (!nextLink) break; + await nextLink.click(); + await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }); + await new Promise(r => setTimeout(r, 500)); + const pageResults = parsePageResults(await page.content()); + if (pageResults.length === 0) break; + allResults = allResults.concat(pageResults); } - pn += 1; + return allResults.slice(0, limit); + } finally { + await page.close(); } - - return allResults.slice(0, limit); } catch (err) { - bingWarmedUp = false; destroySharedBrowser(); throw err; } diff --git a/src/engines/shared/browser.ts b/src/engines/shared/browser.ts index f97f631..7903077 100644 --- a/src/engines/shared/browser.ts +++ b/src/engines/shared/browser.ts @@ -86,7 +86,6 @@ async function launchBrowser(): Promise { const port = await findFreePort(); const args = [ - '--headless=new', `--remote-debugging-port=${port}`, `--user-data-dir=${tempDir}`, '--no-sandbox', @@ -97,6 +96,10 @@ async function launchBrowser(): Promise { '--no-first-run', '--no-default-browser-check', '--disable-blink-features=AutomationControlled', + // 使用 GUI 模式(非 headless)以避免 Bing 等搜索引擎的反自动化检测, + // 通过将窗口移到屏幕外来隐藏 + '--window-position=-32000,-32000', + '--window-size=1,1', ]; console.error(`[browser] Spawning browser on port ${port}, profile: ${tempDir}`); @@ -104,10 +107,13 @@ async function launchBrowser(): Promise { let browserPid: number | undefined; if (process.platform === 'win32') { + // 通过 WMI 启动浏览器,使用 Win32_ProcessStartup 的 ShowWindow=0 (SW_HIDE) + // 使窗口完全不可见,用户不会看到弹出的浏览器窗口 const cmdLine = `"${browserPath}" ${args.join(' ')}`; const psScript = [ - `$r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, - `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'}`, + `$si = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly -Property @{ShowWindow=[uint16]0}`, + `; $r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, + `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'; ProcessStartupInformation=$si}`, `; if($r.ReturnValue -eq 0){$r.ProcessId}else{throw "WMI error: $($r.ReturnValue)"}`, ].join(' '); try { diff --git a/src/test/test-bing-quality.ts b/src/test/test-bing-quality.ts new file mode 100644 index 0000000..a2c49cc --- /dev/null +++ b/src/test/test-bing-quality.ts @@ -0,0 +1,67 @@ +/** + * Bing 搜索结果质量测试。 + * + * 验证 Bing 搜索能正确理解查询语义,而不是将 "MCP" 等缩写 + * 错误地解读为 "Microsoft Certified Professional" 等无关含义。 + * + * 背景:cn.bing.com 对直接 URL 导航返回降级的搜索结果, + * 必须通过搜索框表单提交(带 form=QBLH 参数)才能获得正确结果。 + * 此外,headless 模式下 Bing 搜索框提交返回 0 结果, + * 必须使用 GUI 模式(窗口隐藏)的浏览器。 + */ +import { searchBing } from '../engines/bing/index.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; + +const rand = () => Math.random().toString(36).slice(2, 6); + +async function testBingSearchQuality() { + let passed = 0; + let failed = 0; + + async function assertRelevant(query: string, relevancePattern: RegExp, minRelevant: number) { + // 追加随机后缀确保每次搜索都是全新查询,避免缓存 + const suffix = rand(); + const fullQuery = `${query} ${suffix}`; + console.log(`\n🔍 Query: "${fullQuery}"`); + + const results = await searchBing(fullQuery, 10); + const relevant = results.filter(r => relevancePattern.test(r.title)); + + console.log(` Results: ${results.length}, Relevant: ${relevant.length}/${results.length}`); + results.slice(0, 5).forEach((r, i) => { + const mark = relevancePattern.test(r.title) ? '✓' : '✗'; + console.log(` ${mark} ${i + 1}. ${r.title}`); + }); + + if (relevant.length >= minRelevant) { + console.log(` ✅ PASS (>= ${minRelevant} relevant)`); + passed++; + } else { + console.log(` ❌ FAIL (expected >= ${minRelevant} relevant, got ${relevant.length})`); + failed++; + } + } + + console.log('=== Bing 搜索结果质量测试 ===\n'); + + // 测试1: "MCP" 应被理解为 Model Context Protocol,而非 Microsoft Certified Professional + await assertRelevant( + 'MCP tool rename duplicate tool names proxy wrapper', + /tool|FastMCP|collision|wrapper|proxy|duplicate|mcp.*server|rename|spring.*ai/i, + 3 + ); + + // 测试2: 常规英文技术搜索 + await assertRelevant( + 'TypeScript generic type inference guide', + /typescript|generic|type|inference|guide/i, + 3 + ); + + console.log(`\n=== 结果: ${passed} passed, ${failed} failed ===`); + if (failed > 0) process.exitCode = 1; +} + +testBingSearchQuality() + .catch(err => { console.error('❌ Test error:', err); process.exitCode = 1; }) + .finally(() => { destroySharedBrowser(); process.exit(); }); From fe88f433c4e1a5397aa96fc9f03c2427615feefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 21:02:17 +0800 Subject: [PATCH 09/15] =?UTF-8?q?fix:=20=E9=9A=90=E8=97=8F=E6=A1=8C?= =?UTF-8?q?=E9=9D=A2=E5=90=AF=E5=8A=A8=E6=B5=8F=E8=A7=88=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=99=BE=E5=BA=A6CDP=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Windows: 用 CreateDesktop + CreateProcessW 在不可见桌面上启动浏览器, 子进程自动继承桌面,用户桌面零弹窗 - DuplicateHandle 将桌面句柄复制到浏览器进程,防止启动脚本退出后桌面被销毁 - 移除百度 CDP webdriver 属性覆盖(GUI模式+AutomationControlled已足够) --- src/engines/baidu/baidu.ts | 9 --- src/engines/shared/browser.ts | 105 ++++++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 20 deletions(-) diff --git a/src/engines/baidu/baidu.ts b/src/engines/baidu/baidu.ts index 646e925..c3058ec 100644 --- a/src/engines/baidu/baidu.ts +++ b/src/engines/baidu/baidu.ts @@ -11,15 +11,6 @@ export async function searchBaidu(query: string, limit: number): Promise undefined }); - delete navigator.__proto__.webdriver; - ` - }); - const searchUrl = `https://www.baidu.com/s?wd=${encodeURIComponent(query)}&pn=${pn}&ie=utf-8`; await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 15000 }); diff --git a/src/engines/shared/browser.ts b/src/engines/shared/browser.ts index 7903077..bbfffed 100644 --- a/src/engines/shared/browser.ts +++ b/src/engines/shared/browser.ts @@ -107,24 +107,107 @@ async function launchBrowser(): Promise { let browserPid: number | undefined; if (process.platform === 'win32') { - // 通过 WMI 启动浏览器,使用 Win32_ProcessStartup 的 ShowWindow=0 (SW_HIDE) - // 使窗口完全不可见,用户不会看到弹出的浏览器窗口 + // 在一个独立的不可见 Win32 Desktop 上启动浏览器。 + // 通过 CreateProcessW + STARTUPINFO.lpDesktop 直接指定桌面, + // 子进程自动继承父进程的桌面,因此 Edge 的所有子进程(GPU、渲染器等) + // 都在隐藏桌面上运行,用户桌面上不会出现任何窗口。 + const desktopName = `mcp-search-${Date.now()}`; const cmdLine = `"${browserPath}" ${args.join(' ')}`; - const psScript = [ - `$si = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly -Property @{ShowWindow=[uint16]0}`, - `; $r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, - `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'; ProcessStartupInformation=$si}`, - `; if($r.ReturnValue -eq 0){$r.ProcessId}else{throw "WMI error: $($r.ReturnValue)"}`, - ].join(' '); + const psScript = ` +Add-Type @" +using System; +using System.Runtime.InteropServices; + +public class HiddenLauncher { + [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + static extern IntPtr CreateDesktopW(string lpszDesktop, IntPtr lpszDevice, + IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + + [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + static extern bool CreateProcessW(string lpApp, string lpCmd, + IntPtr lpProcAttr, IntPtr lpThreadAttr, bool bInherit, uint dwFlags, + IntPtr lpEnv, string lpDir, ref STARTUPINFOW si, out PROCESS_INFORMATION pi); + + [DllImport("kernel32.dll", SetLastError=true)] + static extern bool DuplicateHandle(IntPtr hSourceProcess, IntPtr hSourceHandle, + IntPtr hTargetProcess, out IntPtr lpTargetHandle, + uint dwDesiredAccess, bool bInheritHandle, uint dwOptions); + + [DllImport("kernel32.dll")] + static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError=true)] + static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInherit, int dwProcId); + + [DllImport("kernel32.dll")] + static extern bool CloseHandle(IntPtr hObject); + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + struct STARTUPINFOW { + public int cb; public string lpReserved; public string lpDesktop; + public string lpTitle; public int dwX; public int dwY; + public int dwXSize; public int dwYSize; public int dwXCountChars; + public int dwYCountChars; public int dwFillAttribute; public int dwFlags; + public short wShowWindow; public short cbReserved2; + public IntPtr lpReserved2; public IntPtr hStdInput; + public IntPtr hStdOutput; public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + struct PROCESS_INFORMATION { + public IntPtr hProcess; public IntPtr hThread; + public int dwProcessId; public int dwThreadId; + } + + const uint GENERIC_ALL = 0x10000000; + const uint PROCESS_DUP_HANDLE = 0x0040; + const uint DUPLICATE_SAME_ACCESS = 0x0002; + + public static int Launch(string cmdLine, string desktopName) { + IntPtr hDesk = CreateDesktopW(desktopName, IntPtr.Zero, IntPtr.Zero, + 0, GENERIC_ALL, IntPtr.Zero); + if (hDesk == IntPtr.Zero) + throw new Exception("CreateDesktop failed: " + + Marshal.GetLastWin32Error()); + + var si = new STARTUPINFOW(); + si.cb = Marshal.SizeOf(si); + si.lpDesktop = desktopName; + + PROCESS_INFORMATION pi; + if (!CreateProcessW(null, cmdLine, IntPtr.Zero, IntPtr.Zero, + false, 0, IntPtr.Zero, null, ref si, out pi)) + throw new Exception("CreateProcess failed: " + + Marshal.GetLastWin32Error()); + + // 将桌面句柄复制到浏览器进程中,使其持有对桌面的引用。 + // 这样即使当前进程(PowerShell)退出,桌面也不会被销毁。 + IntPtr hBrowserProc = OpenProcess(PROCESS_DUP_HANDLE, false, pi.dwProcessId); + if (hBrowserProc != IntPtr.Zero) { + IntPtr dupHandle; + DuplicateHandle(GetCurrentProcess(), hDesk, + hBrowserProc, out dupHandle, + 0, false, DUPLICATE_SAME_ACCESS); + CloseHandle(hBrowserProc); + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return pi.dwProcessId; + } +} +"@ +[HiddenLauncher]::Launch('${cmdLine.replace(/'/g, "''")}', '${desktopName}')`; try { const output = execFileSync('powershell.exe', [ '-NoProfile', '-NonInteractive', '-Command', psScript, - ], { encoding: 'utf8', windowsHide: true, timeout: 10000 }); + ], { encoding: 'utf8', windowsHide: true, timeout: 15000 }); browserPid = parseInt(output.trim()); - console.error(`[browser] Browser started via WMI, PID: ${browserPid}`); + console.error(`[browser] Browser started on hidden desktop "${desktopName}", PID: ${browserPid}`); } catch (err: any) { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} - throw new Error(`通过 WMI 启动浏览器失败: ${err.message}`); + throw new Error(`启动浏览器失败: ${err.message}`); } } else { const child = spawn(browserPath, args, { From 778b3631c024e97c51d17ebf85ae73e857900ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Wed, 25 Feb 2026 21:02:17 +0800 Subject: [PATCH 10/15] =?UTF-8?q?fix:=20=E9=9A=90=E8=97=8F=E6=A1=8C?= =?UTF-8?q?=E9=9D=A2=E5=90=AF=E5=8A=A8=E6=B5=8F=E8=A7=88=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=99=BE=E5=BA=A6CDP=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Windows: 用 CreateDesktop + CreateProcessW 在不可见桌面上启动浏览器, 子进程自动继承桌面,用户桌面零弹窗 - DuplicateHandle 将桌面句柄复制到浏览器进程,防止启动脚本退出后桌面被销毁 - 移除百度 CDP webdriver 属性覆盖(GUI模式+AutomationControlled已足够) --- src/engines/baidu/baidu.ts | 9 --- src/engines/shared/browser.ts | 108 ++++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/src/engines/baidu/baidu.ts b/src/engines/baidu/baidu.ts index 646e925..c3058ec 100644 --- a/src/engines/baidu/baidu.ts +++ b/src/engines/baidu/baidu.ts @@ -11,15 +11,6 @@ export async function searchBaidu(query: string, limit: number): Promise undefined }); - delete navigator.__proto__.webdriver; - ` - }); - const searchUrl = `https://www.baidu.com/s?wd=${encodeURIComponent(query)}&pn=${pn}&ie=utf-8`; await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 15000 }); diff --git a/src/engines/shared/browser.ts b/src/engines/shared/browser.ts index 7903077..66afd94 100644 --- a/src/engines/shared/browser.ts +++ b/src/engines/shared/browser.ts @@ -107,24 +107,110 @@ async function launchBrowser(): Promise { let browserPid: number | undefined; if (process.platform === 'win32') { - // 通过 WMI 启动浏览器,使用 Win32_ProcessStartup 的 ShowWindow=0 (SW_HIDE) - // 使窗口完全不可见,用户不会看到弹出的浏览器窗口 + // 在一个独立的不可见 Win32 Desktop 上启动浏览器。 + // 通过 CreateProcessW + STARTUPINFO.lpDesktop 直接指定桌面, + // 子进程自动继承父进程的桌面,因此 Edge 的所有子进程(GPU、渲染器等) + // 都在隐藏桌面上运行,用户桌面上不会出现任何窗口。 + const desktopName = `mcp-search-${Date.now()}`; + // 安全说明:cmdLine 中的 browserPath 来自 getBrowserPath() 的硬编码路径列表, + // args 全部是内部生成的常量,desktopName 是时间戳——均无外部输入,不存在注入风险。 + // 下方 replace(/'/g, "''") 是 PowerShell 单引号字符串的标准转义,作为防御性编码。 const cmdLine = `"${browserPath}" ${args.join(' ')}`; - const psScript = [ - `$si = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly -Property @{ShowWindow=[uint16]0}`, - `; $r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create`, - `-Arguments @{CommandLine='${cmdLine.replace(/'/g, "''")}'; ProcessStartupInformation=$si}`, - `; if($r.ReturnValue -eq 0){$r.ProcessId}else{throw "WMI error: $($r.ReturnValue)"}`, - ].join(' '); + const psScript = ` +Add-Type @" +using System; +using System.Runtime.InteropServices; + +public class HiddenLauncher { + [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + static extern IntPtr CreateDesktopW(string lpszDesktop, IntPtr lpszDevice, + IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + + [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + static extern bool CreateProcessW(string lpApp, string lpCmd, + IntPtr lpProcAttr, IntPtr lpThreadAttr, bool bInherit, uint dwFlags, + IntPtr lpEnv, string lpDir, ref STARTUPINFOW si, out PROCESS_INFORMATION pi); + + [DllImport("kernel32.dll", SetLastError=true)] + static extern bool DuplicateHandle(IntPtr hSourceProcess, IntPtr hSourceHandle, + IntPtr hTargetProcess, out IntPtr lpTargetHandle, + uint dwDesiredAccess, bool bInheritHandle, uint dwOptions); + + [DllImport("kernel32.dll")] + static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError=true)] + static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInherit, int dwProcId); + + [DllImport("kernel32.dll")] + static extern bool CloseHandle(IntPtr hObject); + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + struct STARTUPINFOW { + public int cb; public string lpReserved; public string lpDesktop; + public string lpTitle; public int dwX; public int dwY; + public int dwXSize; public int dwYSize; public int dwXCountChars; + public int dwYCountChars; public int dwFillAttribute; public int dwFlags; + public short wShowWindow; public short cbReserved2; + public IntPtr lpReserved2; public IntPtr hStdInput; + public IntPtr hStdOutput; public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + struct PROCESS_INFORMATION { + public IntPtr hProcess; public IntPtr hThread; + public int dwProcessId; public int dwThreadId; + } + + const uint GENERIC_ALL = 0x10000000; + const uint PROCESS_DUP_HANDLE = 0x0040; + const uint DUPLICATE_SAME_ACCESS = 0x0002; + + public static int Launch(string cmdLine, string desktopName) { + IntPtr hDesk = CreateDesktopW(desktopName, IntPtr.Zero, IntPtr.Zero, + 0, GENERIC_ALL, IntPtr.Zero); + if (hDesk == IntPtr.Zero) + throw new Exception("CreateDesktop failed: " + + Marshal.GetLastWin32Error()); + + var si = new STARTUPINFOW(); + si.cb = Marshal.SizeOf(si); + si.lpDesktop = desktopName; + + PROCESS_INFORMATION pi; + if (!CreateProcessW(null, cmdLine, IntPtr.Zero, IntPtr.Zero, + false, 0, IntPtr.Zero, null, ref si, out pi)) + throw new Exception("CreateProcess failed: " + + Marshal.GetLastWin32Error()); + + // 将桌面句柄复制到浏览器进程中,使其持有对桌面的引用。 + // 这样即使当前进程(PowerShell)退出,桌面也不会被销毁。 + IntPtr hBrowserProc = OpenProcess(PROCESS_DUP_HANDLE, false, pi.dwProcessId); + if (hBrowserProc != IntPtr.Zero) { + IntPtr dupHandle; + DuplicateHandle(GetCurrentProcess(), hDesk, + hBrowserProc, out dupHandle, + 0, false, DUPLICATE_SAME_ACCESS); + CloseHandle(hBrowserProc); + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return pi.dwProcessId; + } +} +"@ +[HiddenLauncher]::Launch('${cmdLine.replace(/'/g, "''")}', '${desktopName}')`; try { const output = execFileSync('powershell.exe', [ '-NoProfile', '-NonInteractive', '-Command', psScript, - ], { encoding: 'utf8', windowsHide: true, timeout: 10000 }); + ], { encoding: 'utf8', windowsHide: true, timeout: 15000 }); browserPid = parseInt(output.trim()); - console.error(`[browser] Browser started via WMI, PID: ${browserPid}`); + console.error(`[browser] Browser started on hidden desktop "${desktopName}", PID: ${browserPid}`); } catch (err: any) { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} - throw new Error(`通过 WMI 启动浏览器失败: ${err.message}`); + throw new Error(`启动浏览器失败: ${err.message}`); } } else { const child = spawn(browserPath, args, { From 45574c368173f6b89190fa9fa48caec67ed6a037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Thu, 26 Feb 2026 21:21:19 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E7=9F=A5=E4=B9=8E/?= =?UTF-8?q?LinuxDo/=E7=99=BE=E5=BA=A6/Bing/CSDN=20=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E9=97=AE=E9=A2=98=E5=8F=8A=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 知乎/LinuxDo: 将 axios HTTP 请求替换为 puppeteer 浏览器搜索, 修复 site: 运算符失效导致返回无关结果的问题 - 百度: 修复搜索天气等特殊词时 iframe 被销毁导致导航失败的问题, 回退到 domcontentloaded - Bing: 修复翻页时 waitForNavigation 竞态条件, 兼容 AJAX 翻页 - CSDN: 修复文库类内容 digest 字段为 undefined 导致崩溃的问题 - 安全: 移除 npx 依赖, 消除 65 个安全漏洞 --- package-lock.json | 5196 +++----------------------------- package.json | 1 - src/engines/baidu/baidu.ts | 12 +- src/engines/bing/bing.ts | 6 +- src/engines/csdn/csdn.ts | 2 +- src/engines/linuxdo/linuxdo.ts | 101 +- src/engines/zhihu/zhihu.ts | 100 +- 7 files changed, 489 insertions(+), 4929 deletions(-) diff --git a/package-lock.json b/package-lock.json index 820883b..c45c416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "express": "^4.18.2", "https-proxy-agent": "^7.0.6", "jsdom": "^26.1.0", - "npx": "^10.2.2", "puppeteer-core": "^24.37.5", "zod": "^3.23.0" }, @@ -157,26 +156,56 @@ "node": ">=18" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.2.tgz", - "integrity": "sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { @@ -193,35 +222,40 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { @@ -234,18 +268,19 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -276,9 +311,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -289,7 +324,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { @@ -301,6 +340,42 @@ "node": ">= 0.8" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -332,15 +407,19 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { @@ -353,9 +432,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -368,31 +447,35 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -402,6 +485,19 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { @@ -418,6 +514,25 @@ "node": ">= 0.6" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, "node_modules/@puppeteer/browsers": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", @@ -630,21 +745,38 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -690,15 +822,17 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -818,23 +952,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -850,6 +984,26 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -869,20 +1023,29 @@ "license": "MIT" }, "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1023,6 +1186,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1194,6 +1358,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -1371,6 +1536,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1478,40 +1658,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1525,10 +1705,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -1539,6 +1722,15 @@ "express": ">= 4.11" } }, + "node_modules/express-rate-limit/node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1586,11 +1778,21 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "license": "MIT" }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fd-slicer": { "version": "1.1.0", @@ -1635,15 +1837,16 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1654,12 +1857,15 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -1792,6 +1998,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1804,6 +2025,16 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz", + "integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -1936,6 +2167,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -1976,11 +2216,17 @@ } }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2084,4601 +2330,51 @@ "node": ">= 0.4.0" } }, - "node_modules/npx": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/npx/-/npx-10.2.2.tgz", - "integrity": "sha512-eImmySusyeWphzs5iNh791XbZnZG0FSNvM4KSah34pdQQIDsdTDhIwg1sjN3AIVcjGLpbQ/YcfqHPshKZQK1fA==", - "bundleDependencies": [ - "npm", - "libnpx" - ], - "deprecated": "This package is now part of the npm CLI.", - "license": "ISC", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dependencies": { - "libnpx": "10.2.2", - "npm": "5.1.0" + "boolbase": "^1.0.0" }, - "bin": { - "npx": "index.js" + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/npx/node_modules/ansi-align": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^2.0.0" - } + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "license": "MIT" }, - "node_modules/npx/node_modules/ansi-regex": { - "version": "3.0.0", - "inBundle": true, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/npx/node_modules/ansi-styles": { - "version": "3.2.1", - "inBundle": true, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npx/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/boxen": { - "version": "1.3.0", - "inBundle": true, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npx/node_modules/builtins": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/camelcase": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/capture-stack-trace": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/chalk": { - "version": "2.4.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/ci-info": { - "version": "1.6.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/cli-boxes": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/cliui": { - "version": "4.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/npx/node_modules/code-point-at": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/color-convert": { - "version": "1.9.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/npx/node_modules/color-name": { - "version": "1.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/configstore": { - "version": "3.1.2", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/create-error-class": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "capture-stack-trace": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/cross-spawn": { - "version": "5.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/npx/node_modules/crypto-random-string": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/decamelize": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/deep-extend": { - "version": "0.6.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/npx/node_modules/dot-prop": { - "version": "4.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-obj": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/dotenv": { - "version": "5.0.1", - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.6.0" - } - }, - "node_modules/npx/node_modules/duplexer3": { - "version": "0.1.4", - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/npx/node_modules/end-of-stream": { - "version": "1.4.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/npx/node_modules/escape-string-regexp": { - "version": "1.0.5", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npx/node_modules/execa": { - "version": "0.7.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/find-up": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/fs.realpath": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/get-caller-file": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/get-stream": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/glob": { - "version": "7.1.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npx/node_modules/global-dirs": { - "version": "0.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/got": { - "version": "6.7.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/graceful-fs": { - "version": "4.2.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/has-flag": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/hosted-git-info": { - "version": "2.8.5", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/import-lazy": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npx/node_modules/inflight": { - "version": "1.0.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npx/node_modules/inherits": { - "version": "2.0.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/ini": { - "version": "1.3.5", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/invert-kv": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/is-ci": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ci-info": "^1.5.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/npx/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/is-installed-globally": { - "version": "0.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/is-npm": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/is-obj": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/is-path-inside": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-is-inside": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/is-redirect": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/is-retry-allowed": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/is-stream": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/latest-version": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "package-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/lcid": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "invert-kv": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/libnpx": { - "version": "10.2.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^11.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/locate-path": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/lowercase-keys": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/lru-cache": { - "version": "4.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/npx/node_modules/make-dir": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/map-age-cleaner": { - "version": "0.1.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-defer": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/mem": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/mimic-fn": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/minimist": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/nice-try": { - "version": "1.0.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm": { - "version": "5.1.0", - "bundleDependencies": [ - "abbrev", - "ansi-regex", - "ansicolors", - "ansistyles", - "aproba", - "archy", - "cacache", - "call-limit", - "bluebird", - "chownr", - "cmd-shim", - "columnify", - "config-chain", - "debuglog", - "detect-indent", - "dezalgo", - "editor", - "fs-vacuum", - "fs-write-stream-atomic", - "fstream", - "fstream-npm", - "glob", - "graceful-fs", - "has-unicode", - "hosted-git-info", - "iferr", - "imurmurhash", - "inflight", - "inherits", - "ini", - "init-package-json", - "JSONStream", - "lazy-property", - "lockfile", - "lodash._baseindexof", - "lodash._baseuniq", - "lodash._bindcallback", - "lodash._cacheindexof", - "lodash._createcache", - "lodash._getnative", - "lodash.clonedeep", - "lodash.restparam", - "lodash.union", - "lodash.uniq", - "lodash.without", - "lru-cache", - "mkdirp", - "mississippi", - "move-concurrently", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-cache-filename", - "npm-install-checks", - "npm-package-arg", - "npm-registry-client", - "npm-user-validate", - "npmlog", - "once", - "opener", - "osenv", - "pacote", - "path-is-inside", - "promise-inflight", - "read", - "read-cmd-shim", - "read-installed", - "read-package-json", - "read-package-tree", - "readable-stream", - "readdir-scoped-modules", - "request", - "retry", - "rimraf", - "semver", - "sha", - "slide", - "sorted-object", - "sorted-union-stream", - "ssri", - "strip-ansi", - "tar", - "text-table", - "uid-number", - "umask", - "unique-filename", - "unpipe", - "update-notifier", - "uuid", - "validate-npm-package-license", - "validate-npm-package-name", - "which", - "wrappy", - "write-file-atomic", - "safe-buffer", - "worker-farm" - ], - "inBundle": true, - "license": "Artistic-2.0", - "dependencies": { - "abbrev": "~1.1.0", - "ansi-regex": "~3.0.0", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "aproba": "~1.1.2", - "archy": "~1.0.0", - "bluebird": "~3.5.0", - "cacache": "~9.2.9", - "call-limit": "~1.1.0", - "chownr": "~1.0.1", - "cmd-shim": "~2.0.2", - "columnify": "~1.5.4", - "config-chain": "~1.1.11", - "debuglog": "*", - "detect-indent": "~5.0.0", - "dezalgo": "~1.0.3", - "editor": "~1.0.0", - "fs-vacuum": "~1.2.10", - "fs-write-stream-atomic": "~1.0.10", - "fstream": "~1.0.11", - "fstream-npm": "~1.2.1", - "glob": "~7.1.2", - "graceful-fs": "~4.1.11", - "has-unicode": "~2.0.1", - "hosted-git-info": "~2.5.0", - "iferr": "~0.1.5", - "imurmurhash": "*", - "inflight": "~1.0.6", - "inherits": "~2.0.3", - "ini": "~1.3.4", - "init-package-json": "~1.10.1", - "JSONStream": "~1.3.1", - "lazy-property": "~1.0.0", - "lockfile": "~1.0.3", - "lodash._baseindexof": "*", - "lodash._baseuniq": "~4.6.0", - "lodash._bindcallback": "*", - "lodash._cacheindexof": "*", - "lodash._createcache": "*", - "lodash._getnative": "*", - "lodash.clonedeep": "~4.5.0", - "lodash.restparam": "*", - "lodash.union": "~4.6.0", - "lodash.uniq": "~4.5.0", - "lodash.without": "~4.4.0", - "lru-cache": "~4.1.1", - "mississippi": "~1.3.0", - "mkdirp": "~0.5.1", - "move-concurrently": "~1.0.1", - "node-gyp": "~3.6.2", - "nopt": "~4.0.1", - "normalize-package-data": "~2.4.0", - "npm-cache-filename": "~1.0.2", - "npm-install-checks": "~3.0.0", - "npm-package-arg": "~5.1.2", - "npm-registry-client": "~8.4.0", - "npm-user-validate": "~1.0.0", - "npmlog": "~4.1.2", - "once": "~1.4.0", - "opener": "~1.4.3", - "osenv": "~0.1.4", - "pacote": "~2.7.38", - "path-is-inside": "~1.0.2", - "promise-inflight": "~1.0.1", - "read": "~1.0.7", - "read-cmd-shim": "~1.0.1", - "read-installed": "~4.0.3", - "read-package-json": "~2.0.9", - "read-package-tree": "~5.1.6", - "readable-stream": "~2.3.2", - "readdir-scoped-modules": "*", - "request": "~2.81.0", - "retry": "~0.10.1", - "rimraf": "~2.6.1", - "safe-buffer": "~5.1.1", - "semver": "~5.3.0", - "sha": "~2.0.1", - "slide": "~1.1.6", - "sorted-object": "~2.0.1", - "sorted-union-stream": "~2.1.3", - "ssri": "~4.1.6", - "strip-ansi": "~4.0.0", - "tar": "~2.2.1", - "text-table": "~0.2.0", - "uid-number": "0.0.6", - "umask": "~1.1.0", - "unique-filename": "~1.1.0", - "unpipe": "~1.0.0", - "update-notifier": "~2.2.0", - "uuid": "~3.1.0", - "validate-npm-package-license": "*", - "validate-npm-package-name": "~3.0.0", - "which": "~1.2.14", - "worker-farm": "~1.3.1", - "wrappy": "~1.0.2", - "write-file-atomic": "~2.1.0" - }, - "bin": { - "npm": "bin/npm-cli.js" - } - }, - "node_modules/npx/node_modules/npm-package-arg": { - "version": "6.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "node_modules/npx/node_modules/npm-run-path": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/abbrev": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/ansi-regex": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/ansicolors": { - "version": "0.3.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/ansistyles": { - "version": "0.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/aproba": { - "version": "1.1.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/bluebird": { - "version": "3.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/cacache": { - "version": "9.2.9", - "inBundle": true, - "license": "CC0-1.0", - "dependencies": { - "bluebird": "^3.5.0", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "mississippi": "^1.3.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.1", - "ssri": "^4.1.6", - "unique-filename": "^1.1.0", - "y18n": "^3.2.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/cacache/node_modules/lru-cache": { - "version": "4.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/cacache/node_modules/lru-cache/node_modules/pseudomap": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/cacache/node_modules/lru-cache/node_modules/yallist": { - "version": "2.1.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/cacache/node_modules/y18n": { - "version": "3.2.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/call-limit": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/chownr": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/cmd-shim": { - "version": "2.0.2", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/columnify": { - "version": "1.5.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/columnify/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/columnify/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/columnify/node_modules/wcwidth": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/columnify/node_modules/wcwidth/node_modules/defaults": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/columnify/node_modules/wcwidth/node_modules/defaults/node_modules/clone": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/config-chain": { - "version": "1.1.11", - "inBundle": true, - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/config-chain/node_modules/proto-list": { - "version": "1.2.4", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/debuglog": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/detect-indent": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/dezalgo": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/dezalgo/node_modules/asap": { - "version": "2.0.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/editor": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/fs-vacuum": { - "version": "1.2.10", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fstream": { - "version": "1.0.11", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fstream-npm": { - "version": "1.2.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fstream-ignore": "^1.0.0", - "inherits": "2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fstream-npm/node_modules/fstream-ignore": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fstream": "^1.0.0", - "inherits": "2", - "minimatch": "^3.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fstream-npm/node_modules/fstream-ignore/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fstream-npm/node_modules/fstream-ignore/node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/fstream-npm/node_modules/fstream-ignore/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/fstream-npm/node_modules/fstream-ignore/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/glob": { - "version": "7.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/glob/node_modules/fs.realpath": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/glob/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/glob/node_modules/path-is-absolute": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/graceful-fs": { - "version": "4.1.11", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/hosted-git-info": { - "version": "2.5.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/iferr": { - "version": "0.1.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npx/node_modules/npm/node_modules/inflight": { - "version": "1.0.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/inherits": { - "version": "2.0.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/ini": { - "version": "1.3.4", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/init-package-json": { - "version": "1.10.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/init-package-json/node_modules/promzard": { - "version": "0.3.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/JSONStream": { - "version": "1.3.1", - "inBundle": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "index.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/JSONStream/node_modules/jsonparse": { - "version": "1.3.1", - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/JSONStream/node_modules/through": { - "version": "2.3.8", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lazy-property": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lockfile": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._baseindexof": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._baseuniq": { - "version": "4.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._baseuniq/node_modules/lodash._createset": { - "version": "4.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._baseuniq/node_modules/lodash._root": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._bindcallback": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._cacheindexof": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._createcache": { - "version": "3.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lodash._getnative": "^3.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/lodash._getnative": { - "version": "3.9.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash.clonedeep": { - "version": "4.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash.restparam": { - "version": "3.6.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash.union": { - "version": "4.6.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash.uniq": { - "version": "4.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lodash.without": { - "version": "4.4.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/lru-cache": { - "version": "4.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/lru-cache/node_modules/pseudomap": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/lru-cache/node_modules/yallist": { - "version": "2.1.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi": { - "version": "1.3.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^1.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/concat-stream": { - "version": "1.6.0", - "engines": [ - "node >= 0.8" - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/concat-stream/node_modules/typedarray": { - "version": "0.0.6", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/duplexify": { - "version": "3.5.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/duplexify/node_modules/end-of-stream": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "~1.3.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/duplexify/node_modules/end-of-stream/node_modules/once": { - "version": "1.3.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/duplexify/node_modules/stream-shift": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/end-of-stream": { - "version": "1.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/flush-write-stream": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/from2": { - "version": "2.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/parallel-transform": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/parallel-transform/node_modules/cyclist": { - "version": "0.2.2", - "inBundle": true - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/pump": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/pumpify": { - "version": "1.3.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.1.2", - "inherits": "^2.0.1", - "pump": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/stream-each": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/stream-each/node_modules/stream-shift": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/through2": { - "version": "2.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mississippi/node_modules/through2/node_modules/xtend": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mkdirp": { - "version": "0.5.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/mkdirp/node_modules/minimist": { - "version": "0.0.8", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/move-concurrently": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/move-concurrently/node_modules/copy-concurrently": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/move-concurrently/node_modules/run-queue": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/node-gyp": { - "version": "3.6.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fstream": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "2", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^2.0.0", - "which": "1" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/node-gyp/node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/node-gyp/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/node-gyp/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/node-gyp/node_modules/nopt": { - "version": "3.0.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/nopt": { - "version": "4.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/normalize-package-data": { - "version": "2.4.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/normalize-package-data/node_modules/is-builtin-module": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/normalize-package-data/node_modules/is-builtin-module/node_modules/builtin-modules": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npm-cache-filename": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/npm-install-checks": { - "version": "3.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^2.3.0 || 3.x || 4 || 5" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npm-package-arg": { - "version": "5.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^2.4.2", - "osenv": "^0.1.4", - "semver": "^5.1.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npm-registry-client": { - "version": "8.4.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "concat-stream": "^1.5.2", - "graceful-fs": "^4.1.6", - "normalize-package-data": "~1.0.1 || ^2.0.0", - "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0", - "once": "^1.3.3", - "request": "^2.74.0", - "retry": "^0.10.0", - "semver": "2 >=2.2.1 || 3.x || 4 || 5", - "slide": "^1.1.3", - "ssri": "^4.1.2" - }, - "optionalDependencies": { - "npmlog": "2 || ^3.1.0 || ^4.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npm-registry-client/node_modules/concat-stream": { - "version": "1.6.0", - "engines": [ - "node >= 0.8" - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npm-registry-client/node_modules/concat-stream/node_modules/typedarray": { - "version": "0.0.6", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/npm-user-validate": { - "version": "1.0.0", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog": { - "version": "4.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/are-we-there-yet": { - "version": "1.1.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/are-we-there-yet/node_modules/delegates": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/console-control-strings": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge": { - "version": "2.7.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/object-assign": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/string-width": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/string-width/node_modules/code-point-at": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/string-width/node_modules/is-fullwidth-code-point/node_modules/number-is-nan": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/wide-align": { - "version": "1.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/npmlog/node_modules/set-blocking": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/once": { - "version": "1.4.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/opener": { - "version": "1.4.3", - "inBundle": true, - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "opener.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/osenv": { - "version": "0.1.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/osenv/node_modules/os-homedir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/osenv/node_modules/os-tmpdir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote": { - "version": "2.7.38", - "inBundle": true, - "license": "CC0-1.0", - "dependencies": { - "bluebird": "^3.5.0", - "cacache": "^9.2.9", - "glob": "^7.1.2", - "lru-cache": "^4.1.1", - "make-fetch-happen": "^2.4.13", - "minimatch": "^3.0.4", - "mississippi": "^1.2.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^5.1.2", - "npm-pick-manifest": "^1.0.4", - "osenv": "^0.1.4", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^4.0.0", - "safe-buffer": "^5.1.1", - "semver": "^5.3.0", - "ssri": "^4.1.6", - "tar-fs": "^1.15.3", - "tar-stream": "^1.5.4", - "unique-filename": "^1.1.0", - "which": "^1.2.12" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen": { - "version": "2.4.13", - "inBundle": true, - "license": "CC0-1.0", - "dependencies": { - "agentkeepalive": "^3.3.0", - "cacache": "^9.2.9", - "http-cache-semantics": "^3.7.3", - "http-proxy-agent": "^2.0.0", - "https-proxy-agent": "^2.0.0", - "lru-cache": "^4.1.1", - "mississippi": "^1.2.0", - "node-fetch-npm": "^2.0.1", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^3.0.0", - "ssri": "^4.1.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/agentkeepalive": { - "version": "3.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/agentkeepalive/node_modules/humanize-ms": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/agentkeepalive/node_modules/humanize-ms/node_modules/ms": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-cache-semantics": { - "version": "3.7.3", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-proxy-agent": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "4", - "debug": "2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-proxy-agent/node_modules/agent-base/node_modules/es6-promisify": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-proxy-agent/node_modules/agent-base/node_modules/es6-promisify/node_modules/es6-promise": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-proxy-agent/node_modules/debug": { - "version": "2.6.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/http-proxy-agent/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/https-proxy-agent": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^4.1.0", - "debug": "^2.4.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/https-proxy-agent/node_modules/agent-base": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/https-proxy-agent/node_modules/agent-base/node_modules/es6-promisify": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/https-proxy-agent/node_modules/agent-base/node_modules/es6-promisify/node_modules/es6-promise": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/https-proxy-agent/node_modules/debug": { - "version": "2.6.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/https-proxy-agent/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/node-fetch-npm": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "encoding": "^0.1.11", - "json-parse-helpfulerror": "^1.0.3", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/node-fetch-npm/node_modules/encoding": { - "version": "0.1.12", - "inBundle": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "~0.4.13" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/node-fetch-npm/node_modules/encoding/node_modules/iconv-lite": { - "version": "0.4.18", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/node-fetch-npm/node_modules/json-parse-helpfulerror": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "jju": "^1.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/node-fetch-npm/node_modules/json-parse-helpfulerror/node_modules/jju": { - "version": "1.3.0", - "inBundle": true, - "license": "WTFPL" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^4.0.1", - "socks": "^1.1.10" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent/node_modules/agent-base/node_modules/es6-promisify": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent/node_modules/agent-base/node_modules/es6-promisify/node_modules/es6-promise": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent/node_modules/socks": { - "version": "1.1.10", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" - }, - "engines": { - "node": ">= 0.10.0", - "npm": ">= 1.3.5" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent/node_modules/socks/node_modules/ip": { - "version": "1.1.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/make-fetch-happen/node_modules/socks-proxy-agent/node_modules/socks/node_modules/smart-buffer": { - "version": "1.1.15", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.10.15", - "npm": ">= 1.3.5" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/npm-pick-manifest": { - "version": "1.0.4", - "inBundle": true, - "license": "CC0-1.0", - "dependencies": { - "npm-package-arg": "^5.1.2", - "semver": "^5.3.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/promise-retry": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/promise-retry/node_modules/err-code": { - "version": "1.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/protoduck": { - "version": "4.0.0", - "inBundle": true, - "license": "CC0-1.0", - "dependencies": { - "genfun": "^4.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/protoduck/node_modules/genfun": { - "version": "4.0.1", - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-fs": { - "version": "1.15.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-fs/node_modules/pump": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-fs/node_modules/pump/node_modules/end-of-stream": { - "version": "1.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-stream": { - "version": "1.5.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bl": "^1.0.0", - "end-of-stream": "^1.0.0", - "readable-stream": "^2.0.0", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-stream/node_modules/bl": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-stream/node_modules/end-of-stream": { - "version": "1.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/pacote/node_modules/tar-stream/node_modules/xtend": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/path-is-inside": { - "version": "1.0.2", - "inBundle": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/npx/node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/read": { - "version": "1.0.7", - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/read-cmd-shim": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/read-installed": { - "version": "4.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/read-installed/node_modules/util-extend": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/read-package-json": { - "version": "2.0.9", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "json-parse-helpfulerror": "^1.0.2", - "normalize-package-data": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/read-package-json/node_modules/json-parse-helpfulerror": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "jju": "^1.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/read-package-json/node_modules/json-parse-helpfulerror/node_modules/jju": { - "version": "1.3.0", - "inBundle": true, - "license": "WTFPL" - }, - "node_modules/npx/node_modules/npm/node_modules/read-package-tree": { - "version": "5.1.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "once": "^1.3.0", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/read/node_modules/mute-stream": { - "version": "0.0.7", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/readable-stream": { - "version": "2.3.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.0", - "string_decoder": "~1.0.0", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/readable-stream/node_modules/core-util-is": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/readable-stream/node_modules/process-nextick-args": { - "version": "1.0.7", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/readable-stream/node_modules/util-deprecate": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/readdir-scoped-modules": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request": { - "version": "2.81.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/aws-sign2": { - "version": "0.6.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/aws4": { - "version": "1.6.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/caseless": { - "version": "0.12.0", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/combined-stream": { - "version": "1.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/combined-stream/node_modules/delayed-stream": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/extend": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/forever-agent": { - "version": "0.6.1", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/form-data": { - "version": "2.1.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/form-data/node_modules/asynckit": { - "version": "0.4.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/har-validator": { - "version": "4.2.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/har-validator/node_modules/ajv": { - "version": "4.11.8", - "inBundle": true, - "license": "MIT", - "dependencies": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/har-validator/node_modules/ajv/node_modules/co": { - "version": "4.6.0", - "inBundle": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/har-validator/node_modules/ajv/node_modules/json-stable-stringify": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonify": "~0.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/har-validator/node_modules/ajv/node_modules/json-stable-stringify/node_modules/jsonify": { - "version": "0.0.0", - "inBundle": true, - "license": "Public Domain", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/har-validator/node_modules/har-schema": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/hawk": { - "version": "3.1.3", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - }, - "engines": { - "node": ">=0.10.32" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/hawk/node_modules/boom": { - "version": "2.10.1", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "hoek": "2.x.x" - }, - "engines": { - "node": ">=0.10.40" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/hawk/node_modules/cryptiles": { - "version": "2.0.5", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "boom": "2.x.x" - }, - "engines": { - "node": ">=0.10.40" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/hawk/node_modules/hoek": { - "version": "2.16.3", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.40" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/hawk/node_modules/sntp": { - "version": "1.0.9", - "inBundle": true, - "dependencies": { - "hoek": "2.x.x" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/assert-plus": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/jsprim": { - "version": "1.4.0", - "engines": [ - "node >=0.6.0" - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/jsprim/node_modules/assert-plus": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/jsprim/node_modules/extsprintf": { - "version": "1.0.2", - "engines": [ - "node >=0.6.0" - ], - "inBundle": true - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/jsprim/node_modules/json-schema": { - "version": "0.2.3", - "inBundle": true - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/jsprim/node_modules/verror": { - "version": "1.3.6", - "engines": [ - "node >=0.6.0" - ], - "inBundle": true, - "dependencies": { - "extsprintf": "1.0.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk": { - "version": "1.13.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "dashdash": "^1.12.0", - "getpass": "^0.1.1" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - }, - "optionalDependencies": { - "bcrypt-pbkdf": "^1.0.0", - "ecc-jsbn": "~0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/asn1": { - "version": "0.2.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/assert-plus": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/bcrypt-pbkdf": { - "version": "1.0.1", - "inBundle": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/dashdash": { - "version": "1.14.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/ecc-jsbn": { - "version": "0.1.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "jsbn": "~0.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/getpass": { - "version": "0.1.7", - "inBundle": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/jsbn": { - "version": "0.1.1", - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/tweetnacl": { - "version": "0.14.5", - "inBundle": true, - "license": "Unlicense", - "optional": true - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/is-typedarray": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/isstream": { - "version": "0.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/json-stringify-safe": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/mime-types": { - "version": "2.1.15", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "~1.27.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/mime-types/node_modules/mime-db": { - "version": "1.27.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/oauth-sign": { - "version": "0.8.2", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/performance-now": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/qs": { - "version": "6.4.0", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/stringstream": { - "version": "0.0.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/tough-cookie": { - "version": "2.3.2", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "punycode": "^1.4.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/tough-cookie/node_modules/punycode": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/request/node_modules/tunnel-agent": { - "version": "0.6.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/retry": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/rimraf": { - "version": "2.6.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/safe-buffer": { - "version": "5.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/semver": { - "version": "5.3.0", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npx/node_modules/npm/node_modules/sha": { - "version": "2.0.1", - "inBundle": true, - "license": "(BSD-2-Clause OR MIT)", - "dependencies": { - "graceful-fs": "^4.1.2", - "readable-stream": "^2.0.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/slide": { - "version": "1.1.6", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-object": { - "version": "2.0.1", - "inBundle": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream": { - "version": "2.1.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/from2": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" - } - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/from2/node_modules/readable-stream": { - "version": "1.1.14", - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/from2/node_modules/readable-stream/node_modules/core-util-is": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/from2/node_modules/readable-stream/node_modules/isarray": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/from2/node_modules/readable-stream/node_modules/string_decoder": { - "version": "0.10.31", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/stream-iterate": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/sorted-union-stream/node_modules/stream-iterate/node_modules/stream-shift": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/ssri": { - "version": "4.1.6", - "inBundle": true, - "license": "CC0-1.0", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/strip-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/tar": { - "version": "2.2.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/tar/node_modules/block-stream": { - "version": "0.0.9", - "inBundle": true, - "license": "ISC", - "dependencies": { - "inherits": "~2.0.0" - }, - "engines": { - "node": "0.4 || >=0.5.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/uid-number": { - "version": "0.0.6", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/npx/node_modules/npm/node_modules/umask": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/unique-filename": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/unique-filename/node_modules/unique-slug": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/unpipe": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier": { - "version": "2.2.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^1.0.0", - "chalk": "^1.0.0", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^1.1.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^0.1.0", - "widest-line": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/ansi-align": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^2.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/camelcase": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/cli-boxes": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/string-width": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size": { - "version": "0.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "execa": "^0.4.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa": { - "version": "0.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cross-spawn-async": "^2.1.1", - "is-stream": "^1.1.0", - "npm-run-path": "^1.0.0", - "object-assign": "^4.0.1", - "path-key": "^1.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa/node_modules/cross-spawn-async": { - "version": "2.2.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.0", - "which": "^1.2.8" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa/node_modules/is-stream": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa/node_modules/npm-run-path": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa/node_modules/object-assign": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa/node_modules/path-key": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/term-size/node_modules/execa/node_modules/strip-eof": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line/node_modules/string-width": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line/node_modules/string-width/node_modules/code-point-at": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line/node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line/node_modules/string-width/node_modules/is-fullwidth-code-point/node_modules/number-is-nan": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line/node_modules/string-width/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/boxen/node_modules/widest-line/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk": { - "version": "1.1.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/ansi-styles": { - "version": "2.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/has-ansi": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/chalk/node_modules/supports-color": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore": { - "version": "3.1.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore/node_modules/dot-prop": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-obj": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore/node_modules/dot-prop/node_modules/is-obj": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore/node_modules/make-dir": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore/node_modules/make-dir/node_modules/pify": { - "version": "2.3.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore/node_modules/unique-string": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/configstore/node_modules/unique-string/node_modules/crypto-random-string": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/import-lazy": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/is-npm": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "package-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got": { - "version": "6.7.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/create-error-class": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "capture-stack-trace": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/create-error-class/node_modules/capture-stack-trace": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/duplexer3": { - "version": "0.1.4", - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/get-stream": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/is-redirect": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/is-retry-allowed": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/is-stream": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/lowercase-keys": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/timed-out": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/unzip-response": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/url-parse-lax": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/got/node_modules/url-parse-lax/node_modules/prepend-http": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-auth-token": { - "version": "3.3.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-auth-token/node_modules/rc": { - "version": "1.2.1", - "inBundle": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "index.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-auth-token/node_modules/rc/node_modules/deep-extend": { - "version": "0.4.2", - "inBundle": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.12.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-auth-token/node_modules/rc/node_modules/minimist": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-auth-token/node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-url": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-url/node_modules/rc": { - "version": "1.2.1", - "inBundle": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "index.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-url/node_modules/rc/node_modules/deep-extend": { - "version": "0.4.2", - "inBundle": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.12.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-url/node_modules/rc/node_modules/minimist": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/latest-version/node_modules/package-json/node_modules/registry-url/node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/semver-diff": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^5.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/update-notifier/node_modules/xdg-basedir": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/uuid": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/npx/node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.1", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "~1.0.0", - "spdx-expression-parse": "~1.0.0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-correct": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-license-ids": "^1.0.2" - } - }, - "node_modules/npx/node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-correct/node_modules/spdx-license-ids": { - "version": "1.2.2", - "inBundle": true, - "license": "Unlicense" - }, - "node_modules/npx/node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "1.0.4", - "inBundle": true, - "license": "(MIT AND CC-BY-3.0)" - }, - "node_modules/npx/node_modules/npm/node_modules/validate-npm-package-name": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "builtins": "^1.0.3" - } - }, - "node_modules/npx/node_modules/npm/node_modules/validate-npm-package-name/node_modules/builtins": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/which": { - "version": "1.2.14", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/npx/node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/worker-farm": { - "version": "1.3.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "errno": ">=0.1.1 <0.2.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, - "node_modules/npx/node_modules/npm/node_modules/worker-farm/node_modules/errno": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "prr": "~0.0.0" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/npx/node_modules/npm/node_modules/worker-farm/node_modules/errno/node_modules/prr": { - "version": "0.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/npm/node_modules/worker-farm/node_modules/xtend": { - "version": "4.0.1", - "inBundle": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/npx/node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/npm/node_modules/write-file-atomic": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - }, - "node_modules/npx/node_modules/number-is-nan": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/once": { - "version": "1.4.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/npx/node_modules/os-homedir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/os-locale": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/os-locale/node_modules/cross-spawn": { - "version": "6.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npx/node_modules/os-locale/node_modules/execa": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/os-locale/node_modules/get-stream": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/os-tmpdir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/osenv": { - "version": "0.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/npx/node_modules/p-defer": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/p-finally": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/p-is-promise": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npx/node_modules/p-limit": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/p-locate": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/p-try": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/package-json": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/path-exists": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/path-is-absolute": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/path-is-inside": { - "version": "1.0.2", - "inBundle": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/npx/node_modules/path-key": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/pify": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/prepend-http": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/pseudomap": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/pump": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/npx/node_modules/rc": { - "version": "1.2.8", - "inBundle": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/npx/node_modules/registry-auth-token": { - "version": "3.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/npx/node_modules/registry-url": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/require-directory": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/require-main-filename": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/rimraf": { - "version": "2.7.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/npx/node_modules/safe-buffer": { - "version": "5.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npx/node_modules/semver": { - "version": "5.7.1", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npx/node_modules/semver-diff": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^5.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/set-blocking": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/shebang-command": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/shebang-regex": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/signal-exit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/string-width": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/strip-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/strip-eof": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/strip-json-comments": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/supports-color": { - "version": "5.5.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/term-size": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "execa": "^0.7.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/timed-out": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/unique-string": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/unzip-response": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/update-notifier": { - "version": "2.5.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/url-parse-lax": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/validate-npm-package-name": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "builtins": "^1.0.3" - } - }, - "node_modules/npx/node_modules/which": { - "version": "1.3.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/npx/node_modules/which-module": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/widest-line": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/wrap-ansi": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npx/node_modules/wrappy": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/write-file-atomic": { - "version": "2.4.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/npx/node_modules/xdg-basedir": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npx/node_modules/y18n": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/yallist": { - "version": "2.1.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npx/node_modules/yargs": { - "version": "11.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "node_modules/npx/node_modules/yargs-parser": { - "version": "9.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "camelcase": "^4.1.0" - } - }, - "node_modules/npx/node_modules/yargs/node_modules/y18n": { - "version": "3.2.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" + "ee-first": "1.1.1" }, "engines": { "node": ">= 0.8" @@ -6891,12 +2587,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -6915,15 +2611,61 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6937,6 +2679,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -6954,12 +2705,13 @@ } }, "node_modules/router/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/rrweb-cssom": { @@ -7410,9 +3162,10 @@ } }, "node_modules/undici": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", - "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", "engines": { "node": ">=18.17" } @@ -7430,15 +3183,6 @@ "node": ">= 0.8" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -7640,19 +3384,9 @@ "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } } } } diff --git a/package.json b/package.json index d27ba85..a2d2414 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "express": "^4.18.2", "https-proxy-agent": "^7.0.6", "jsdom": "^26.1.0", - "npx": "^10.2.2", "puppeteer-core": "^24.37.5", "zod": "^3.23.0" }, diff --git a/src/engines/baidu/baidu.ts b/src/engines/baidu/baidu.ts index c3058ec..4ab04b8 100644 --- a/src/engines/baidu/baidu.ts +++ b/src/engines/baidu/baidu.ts @@ -13,7 +13,17 @@ export async function searchBaidu(query: string, limit: number): Promise setTimeout(r, 1000)); const html = await page.content(); diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index fcc3652..45c8187 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -64,9 +64,11 @@ export async function searchBing(query: string, limit: number): Promise {}); await nextLink.click(); - await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }); - await new Promise(r => setTimeout(r, 500)); + await navPromise; + await new Promise(r => setTimeout(r, 1000)); const pageResults = parsePageResults(await page.content()); if (pageResults.length === 0) break; allResults = allResults.concat(pageResults); diff --git a/src/engines/csdn/csdn.ts b/src/engines/csdn/csdn.ts index d2cfd64..5f4ed51 100644 --- a/src/engines/csdn/csdn.ts +++ b/src/engines/csdn/csdn.ts @@ -38,7 +38,7 @@ export async function searchCsdn(query: string, limit: number): Promise { - let allResults: SearchResult[] = []; - let pn = 0; - // Format query for URL - const encodedQuery = encodeURIComponent(query); - - try { - while (allResults.length < limit) { - const response = await axios.get('https://cn.bing.com/search', { - params: { - "q": query, - "qs": "n", - "sp": "-1", - "lq": "0", - "pq": query, - "sc": "1-26", - "sk": "", - "cvid": "83A1CA7981304D6D9DC6924C92C248C9", - "FPIG": "5A297328992C4FB8BEED24CB159DD092", - "first": 1 + pn * 10, - "FORM": "PERE", - "rdr": "1", - "rdrig": "5BE6670756A24EA5A4274EB2A47D978D" - }, - headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", - "Connection": "keep-alive", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "Accept-Encoding": "gzip, deflate, br", - "ect": "4g", - "sec-ch-ua": "\"Chromium\";v=\"112\", \"Google Chrome\";v=\"112\", \"Not:A-Brand\";v=\"99\"", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-full-version": "\"112.0.5615.50\"", - "sec-ch-ua-arch": "\"x86\"", - "sec-ch-ua-platform": "\"Windows\"", - "sec-ch-ua-platform-version": "\"15.0.0\"", - "sec-ch-ua-model": "\"\"", - "sec-ch-ua-bitness": "\"64\"", - "sec-ch-ua-full-version-list": "\"Chromium\";v=\"112.0.5615.50\", \"Google Chrome\";v=\"112.0.5615.50\", \"Not:A-Brand\";v=\"99.0.0.0\"", - "upgrade-insecure-requests": "1", - "sec-fetch-site": "same-origin", - "sec-fetch-mode": "navigate", - "sec-fetch-dest": "document", - "referer": `https://cn.bing.com/search?q=${encodedQuery}&qs=n&sp=-1&lq=0&pq=${encodedQuery}&sc=1-26&sk=&cvid=83A1CA7981304D6D9DC6924C92C248C9&FPIG=5A297328992C4FB8BEED24CB159DD092&first=11&FORM=PERE`, - "accept-language": "zh-CN,zh;q=0.9", - "Cookie": "MUID=296FAEFA1CB065372DBFB8DB1D6264B0; MUIDB=296FAEFA1CB065372DBFB8DB1D6264B0; _EDGE_S=F=1&SID=063A15C99EE5624608BF03E89F3763A1; _EDGE_V=1; SRCHD=AF=NOFORM; SRCHUID=V=2&GUID=5C95B3592BB9427D94A15ACCB93B2998&dmnchg=1; _Rwho=u=d&ts=2025-07-06; ipv6=hit=1751775967739&t=4; USRLOC=HS=1&ELOC=LAT=23.0211181640625|LON=113.73304748535156|N=%E4%B8%9C%E8%8E%9E%E5%B8%82%EF%BC%8C%E5%B9%BF%E4%B8%9C%E7%9C%81|ELT=4|; BFPRResults=FirstPageUrls=B0D24EBFA8A0B66D6B071D854B69DC44%2C20426A336B6E4CF57DE0D0DD3484549D%2C9BF59C5A66BB498707BDEE26787183B3%2C4BD56F463CDE1E98954C975164097F6F%2C0F9C9A380F681662D573A082BEE6F471%2C8ED94F5FBF7A57786DE0FECB10C3D613%2C23FB8C1D9A41145741724F52CB242134%2CBD2D8F53469372B7D64F7AEF3D7F642D%2C6D9A9E581F375A9E57BF5A9599101CD5%2C8FC7081B6D04C85C644E9E41D523F4A0&FPIG=5A297328992C4FB8BEED24CB159DD092; SRCHUSR=DOB=20250706&DS=1; _RwBf=r=0&ilt=10&ihpd=0&ispd=10&rc=30&rb=0&rg=200&pc=27&mtu=0&rbb=0&clo=0&v=10&l=2025-07-05T07:00:00.0000000Z&lft=0001-01-01T00:00:00.0000000&aof=0&ard=0001-01-01T00:00:00.0000000&rwdbt=0&rwflt=0&rwaul2=0&g=&o=2&p=&c=&t=0&s=0001-01-01T00:00:00.0000000+00:00&ts=2025-07-06T04:16:43.3231859+00:00&rwred=0&wls=&wlb=&wle=&ccp=&cpt=&lka=0&lkt=0&aad=0&TH=&cid=0&gb=; _SS=SID=063A15C99EE5624608BF03E89F3763A1&R=30&RB=0&GB=0&RG=200&RP=27; SRCHHPGUSR=SRCHLANG=zh-Hans&PV=15.0.0&BZA=0&BRW=XW&BRH=M&CW=1707&CH=809&SCW=1691&SCH=1685&DPR=1.5&UTC=480&PRVCW=1707&PRVCH=809&HV=1751775699&HVE=CfDJ8Inh5QCoSQBNls38F2rbEpRMaCs0odiEKdnprhQV_w0a8_NuQurksG4JGF0rBdUFR2yGWPBo6wf3uOSO2p1l-Y8XcVCroAmqnrOsA1QCo3OSPJl7M38jS2gXSuJk8XX-qNBkwJmrKmfmGnVmG3kTuHL1a5gIpkLuYYB9-DlPnVHAE8tfBgro5p7vESusk8fsWA&EXLTT=11&DM=0" - } - }); - - const $ = cheerio.load(response.data); - const results: SearchResult[] = []; - - $('#b_content').children() - .find('#b_results').children() - .each((i, element) => { - const titleElement = $(element).find('h2'); - const linkElement = $(element).find('a'); - const snippetElement = $(element).find('p').first(); - - if (titleElement.length && linkElement.length) { - const url = linkElement.attr('href'); - if (url && url.startsWith('http') && url.includes('linux.do')) { - const sourceElement = $(element).find('.b_tpcn'); - results.push({ - title: titleElement.text(), - url: url, - description: snippetElement.text().trim() || '', - source: 'linux.do', - engine: 'bing' - }); - } - } - }); - - allResults = allResults.concat(results); - - if (results.length === 0) { - console.error('⚠️ No more results from Bing, ending early....'); - break; - } - - pn += 1; - } - - return allResults.slice(0, limit); - } catch (error: any) { - console.error('❌ Bing search failed:', error.message || error); - return []; - } -} +import {searchBing} from "../bing/index.js"; export async function searchLinuxDo(query: string, limit: number): Promise { @@ -108,10 +16,10 @@ export async function searchLinuxDo(query: string, limit: number): Promise { result.source = 'linux.do'; - // Keep the original engine info }); return filteredResults.slice(0, limit); diff --git a/src/engines/zhihu/zhihu.ts b/src/engines/zhihu/zhihu.ts index d971f41..16f6ad1 100644 --- a/src/engines/zhihu/zhihu.ts +++ b/src/engines/zhihu/zhihu.ts @@ -1,98 +1,7 @@ -import axios from 'axios'; import {SearchResult} from '../../types.js'; import { config } from '../../config.js'; import {searchDuckDuckGo} from "../duckduckgo/index.js"; -import * as cheerio from 'cheerio'; - -async function searchBingForZhiHu(query: string, limit: number): Promise { - let allResults: SearchResult[] = []; - let pn = 0; - // Format query for URL - const encodedQuery = encodeURIComponent(query); - - try { - while (allResults.length < limit) { - const response = await axios.get('https://cn.bing.com/search', { - params: { - "q": query, - "qs": "n", - "form": "QBRE", - "sp": "-1", - "lq": "0", - "pq": query, - "sc": "5-36", - "sk": "", - "cvid": "EC2944BAA67C4476B26D801B068E79FD", - "rdr": "1", - "rdrig": "03977EC990574FBF94EC11E34B85CD62", - "first": 2 + pn * 10, - }, - headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", - "Connection": "keep-alive", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "Accept-Encoding": "gzip, deflate, br", - "cache-control": "max-age=0", - "ect": "4g", - "sec-ch-ua": "\"Chromium\";v=\"112\", \"Google Chrome\";v=\"112\", \"Not:A-Brand\";v=\"99\"", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-full-version": "\"112.0.5615.50\"", - "sec-ch-ua-arch": "\"x86\"", - "sec-ch-ua-platform": "\"Windows\"", - "sec-ch-ua-platform-version": "\"15.0.0\"", - "sec-ch-ua-model": "\"\"", - "sec-ch-ua-bitness": "\"64\"", - "sec-ch-ua-full-version-list": "\"Chromium\";v=\"112.0.5615.50\", \"Google Chrome\";v=\"112.0.5615.50\", \"Not:A-Brand\";v=\"99.0.0.0\"", - "upgrade-insecure-requests": "1", - "sec-fetch-site": "none", - "sec-fetch-mode": "navigate", - "sec-fetch-user": "?1", - "sec-fetch-dest": "document", - "accept-language": "zh-CN,zh;q=0.9" - } - }); - - const $ = cheerio.load(response.data); - const results: SearchResult[] = []; - - $('#b_content').children() - .find('#b_results').children() - .each((i, element) => { - const titleElement = $(element).find('h2'); - const linkElement = $(element).find('a'); - const snippetElement = $(element).find('p').first(); - - if (titleElement.length && linkElement.length) { - const url = linkElement.attr('href'); - if (url && url.startsWith('http') && url.includes('zhuanlan.zhihu.com')) { - const sourceElement = $(element).find('.b_tpcn'); - results.push({ - title: titleElement.text(), - url: url, - description: snippetElement.text().trim() || '', - source: 'zhuanlan.zhihu.com', - engine: 'bing' - }); - } - } - }); - - allResults = allResults.concat(results); - - if (results.length === 0) { - console.error('⚠️ No more results from Bing, ending early....'); - break; - } - - pn += 1; - } - - return allResults.slice(0, limit); - } catch (error: any) { - console.error('❌ Bing search failed:', error.message || error); - return []; - } -} +import {searchBing} from "../bing/index.js"; export async function searchZhiHu(query: string, limit: number): Promise { @@ -107,10 +16,10 @@ export async function searchZhiHu(query: string, limit: number): Promise { result.source = 'zhuanlan.zhihu.com'; - // Keep the original engine info }); return filteredResults.slice(0, limit); From 39d377bc44bf1f22e1b90650f94e82d7e237efe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Thu, 26 Feb 2026 23:30:45 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=8F=E8=A7=88?= =?UTF-8?q?=E5=99=A8=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=80=81=E9=94=99=E8=AF=AF=E4=B8=8A=E6=8A=A5=E4=B8=8E=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构浏览器关闭逻辑:browser.close()优雅退出+强杀回退,setTimeout加unref防止阻塞退出 - 监听MCP server onclose事件,stdin关闭时主动清理浏览器并退出,解决停止服务器慢的问题 - 移除ws/@types/ws依赖(不再使用UnrefTransport) - executeSearch返回{results, errors}结构,引擎全部失败时isError:true报告给Agent - 部分引擎失败时在warnings中附带错误详情 - DuckDuckGo子方法错误不再吞掉,正确向上抛出 - 新增getErrorMessage()辅助函数,处理AxiosError/AggregateError的空message问题 - DuckDuckGo新增errMsg()辅助函数处理空错误消息 - 所有console.error改为简洁单行日志,不再dump整个错误对象 - 新增全局requestTimeout配置(环境变量REQUEST_TIMEOUT,默认30000ms) - 所有引擎硬编码超时替换为config.requestTimeout - 修复掘金搜索null result_model导致的崩溃 - 测试脚本添加destroySharedBrowser()清理调用 --- package-lock.json | 1516 +++++++++----------- package.json | 24 +- src/config.ts | 6 +- src/engines/baidu/baidu.ts | 7 +- src/engines/bing/bing.ts | 7 +- src/engines/brave/brave.ts | 4 +- src/engines/csdn/csdn.ts | 2 + src/engines/csdn/fetchCsdnArticle.ts | 4 +- src/engines/duckduckgo/searchDuckDuckGo.ts | 29 +- src/engines/exa/exa.ts | 13 +- src/engines/github/github.ts | 3 +- src/engines/juejin/fetchJuejinArticle.ts | 8 +- src/engines/juejin/juejin.ts | 24 +- src/engines/linuxdo/fetchLinuxDoArticle.ts | 4 +- src/engines/shared/browser.ts | 60 +- src/index.ts | 8 + src/test/test-baidu-availability.ts | 2 +- src/test/test-baidu.ts | 2 +- src/test/test-bing-quality.ts | 2 +- src/test/test-bing.ts | 2 +- src/test/test-description-length.ts | 4 +- src/test/test-linuxdo.ts | 3 +- src/test/test-search-relevance.ts | 2 +- src/test/test-zhihu.ts | 3 +- src/tools/setupTools.ts | 93 +- 25 files changed, 875 insertions(+), 957 deletions(-) diff --git a/package-lock.json b/package-lock.json index c45c416..57d8a9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,46 +8,86 @@ "name": "open-websearch", "version": "1.2.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.11.2", + "@modelcontextprotocol/sdk": "^1.27.1", "@types/axios": "^0.14.4", - "@types/cheerio": "^0.22.35", - "axios": "^1.7.9", - "cheerio": "^1.0.0", - "cors": "^2.8.5", - "express": "^4.18.2", + "@types/cheerio": "^1.0.0", + "axios": "^1.13.5", + "cheerio": "^1.2.0", + "cors": "^2.8.6", + "express": "^5.2.1", "https-proxy-agent": "^7.0.6", - "jsdom": "^26.1.0", + "jsdom": "^28.1.0", "puppeteer-core": "^24.37.5", - "zod": "^3.23.0" + "zod": "^4.3.6" }, "bin": { "open-websearch": "build/index.js" }, "devDependencies": { "@types/cors": "^2.8.19", - "@types/express": "^4.17.21", - "@types/jsdom": "^21.1.7", - "@types/node": "^20.17.10", - "typescript": "^5.3.3" + "@types/express": "^5.0.6", + "@types/jsdom": "^28.0.0", + "@types/node": "^25.3.1", + "typescript": "^5.9.3" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "license": "MIT" + }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" } }, "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "funding": [ { "type": "github", @@ -60,13 +100,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "funding": [ { "type": "github", @@ -79,17 +119,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "funding": [ { "type": "github", @@ -102,21 +142,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "funding": [ { "type": "github", @@ -130,16 +170,32 @@ "license": "MIT", "peer": true, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", + "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "funding": [ { "type": "github", @@ -153,7 +209,24 @@ "license": "MIT", "peer": true, "engines": { - "node": ">=18" + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", + "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/@hono/node-server": { @@ -208,322 +281,6 @@ } } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { "version": "3.25.1", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", @@ -581,11 +338,13 @@ } }, "node_modules/@types/cheerio": { - "version": "0.22.35", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", - "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-zAaImHWoh5RY2CLgU2mvg3bl2k3F65B0N5yphuII3ythFLPmJhL7sj1RDu6gSxcgqHlETbr/lhA2OBY+WF1fXQ==", + "deprecated": "This is a stub types definition. cheerio provides its own type definitions, so you do not need this installed.", + "license": "MIT", "dependencies": { - "@types/node": "*" + "cheerio": "*" } }, "node_modules/@types/connect": { @@ -609,22 +368,21 @@ } }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "dev": true, "license": "MIT", "dependencies": { @@ -642,32 +400,35 @@ "license": "MIT" }, "node_modules/@types/jsdom": { - "version": "21.1.7", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", - "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.0.tgz", + "integrity": "sha512-A8TBQQC/xAOojy9kM8E46cqT00sF0h7dWjV8t8BJhUi2rG6JRh7XXQo/oLoENuZIQEpXsxLccLCnknyQd7qssQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" + "parse5": "^7.0.0", + "undici-types": "^7.21.0" + } }, "node_modules/@types/node": { - "version": "20.17.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", - "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "version": "25.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.1.tgz", + "integrity": "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~7.18.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -683,26 +444,24 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" + "@types/node": "*" } }, "node_modules/@types/tough-cookie": { @@ -723,18 +482,43 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -801,12 +585,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -951,53 +729,33 @@ "node": ">=10.0.0" } }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "require-from-string": "^2.0.2" } }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">=18" }, "funding": { "type": "opencollective", @@ -1005,45 +763,19 @@ } }, "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/boolbase": { @@ -1098,24 +830,25 @@ } }, "node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", + "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=18.17" + "node": ">=20.18.1" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" @@ -1137,6 +870,30 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cheerio/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/chromium-bidi": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", @@ -1150,6 +907,15 @@ "devtools-protocol": "*" } }, + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1195,15 +961,16 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -1224,15 +991,18 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -1240,6 +1010,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cross-spawn": { @@ -1271,6 +1045,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -1283,16 +1070,18 @@ } }, "node_modules/cssstyle": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz", - "integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz", + "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "@asamuzakjp/css-color": "^5.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/data-uri-to-buffer": { @@ -1305,16 +1094,25 @@ } }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" } }, "node_modules/debug": { @@ -1335,9 +1133,9 @@ } }, "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, "node_modules/degenerator": { @@ -1371,16 +1169,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/devtools-protocol": { "version": "0.0.1566079", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", @@ -1427,9 +1215,10 @@ } }, "node_modules/domutils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", - "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -1475,9 +1264,10 @@ } }, "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" @@ -1658,46 +1448,43 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "peer": true, "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -1731,20 +1518,30 @@ "node": ">= 12" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/extract-zip": { "version": "2.0.1", @@ -1804,38 +1601,26 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1882,12 +1667,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/function-bind": { @@ -2036,21 +1821,21 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -2058,26 +1843,44 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-proxy-agent": { @@ -2110,6 +1913,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -2177,34 +1981,35 @@ } }, "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "license": "MIT", - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", + "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -2215,6 +2020,39 @@ } } }, + "node_modules/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2228,10 +2066,13 @@ "license": "BSD-2-Clause" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -2242,43 +2083,31 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mime-db": { @@ -2313,9 +2142,9 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2341,12 +2170,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2473,12 +2296,6 @@ "node": ">=8" } }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -2587,9 +2404,9 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -2625,26 +2442,6 @@ "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -2661,15 +2458,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2714,32 +2502,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2770,66 +2532,73 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -2978,10 +2747,20 @@ "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3074,21 +2853,21 @@ } }, "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", + "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", + "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", "license": "MIT" }, "node_modules/toidentifier": { @@ -3100,27 +2879,27 @@ } }, "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "license": "BSD-3-Clause", "dependencies": { - "tldts": "^6.1.32" + "tldts": "^7.0.5" }, "engines": { "node": ">=16" } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/tslib": { @@ -3130,18 +2909,44 @@ "license": "0BSD" }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-query-selector": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", @@ -3149,10 +2954,11 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3162,18 +2968,20 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=20.18.1" } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.22.0.tgz", + "integrity": "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw==", + "dev": true, + "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", @@ -3183,15 +2991,6 @@ "node": ">= 0.8" } }, - "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/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3220,18 +3019,20 @@ "license": "Apache-2.0" }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" }, @@ -3248,16 +3049,17 @@ } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -3381,9 +3183,11 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index a2d2414..066cf08 100644 --- a/package.json +++ b/package.json @@ -20,23 +20,23 @@ "test:both": "set MODE=both && node build/index.js" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.11.2", + "@modelcontextprotocol/sdk": "^1.27.1", "@types/axios": "^0.14.4", - "@types/cheerio": "^0.22.35", - "axios": "^1.7.9", - "cheerio": "^1.0.0", - "cors": "^2.8.5", - "express": "^4.18.2", + "@types/cheerio": "^1.0.0", + "axios": "^1.13.5", + "cheerio": "^1.2.0", + "cors": "^2.8.6", + "express": "^5.2.1", "https-proxy-agent": "^7.0.6", - "jsdom": "^26.1.0", + "jsdom": "^28.1.0", "puppeteer-core": "^24.37.5", - "zod": "^3.23.0" + "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.19", - "@types/express": "^4.17.21", - "@types/jsdom": "^21.1.7", - "@types/node": "^20.17.10", - "typescript": "^5.3.3" + "@types/express": "^5.0.6", + "@types/jsdom": "^28.0.0", + "@types/node": "^25.3.1", + "typescript": "^5.9.3" } } diff --git a/src/config.ts b/src/config.ts index ccc95b3..9ab95dc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,6 +14,8 @@ export interface AppConfig { enableHttpServer: boolean; // 搜索结果描述最大长度(undefined = 不限制) maxDescriptionLength?: number; + // 网络请求超时时间(毫秒) + requestTimeout: number; } // Read from environment variables or use defaults @@ -34,7 +36,9 @@ export const config: AppConfig = { // Modes: 'both' (default), 'http', 'stdio' enableHttpServer: process.env.MODE ? ['both', 'http'].includes(process.env.MODE) : true, // 搜索结果描述最大长度 - maxDescriptionLength: process.env.MAX_DESCRIPTION_LENGTH ? parseInt(process.env.MAX_DESCRIPTION_LENGTH, 10) : undefined + maxDescriptionLength: process.env.MAX_DESCRIPTION_LENGTH ? parseInt(process.env.MAX_DESCRIPTION_LENGTH, 10) : undefined, + // 网络请求超时时间(毫秒),默认 30 秒 + requestTimeout: process.env.REQUEST_TIMEOUT ? parseInt(process.env.REQUEST_TIMEOUT, 10) : 30000 }; // Valid search engines list diff --git a/src/engines/baidu/baidu.ts b/src/engines/baidu/baidu.ts index 4ab04b8..ea1c938 100644 --- a/src/engines/baidu/baidu.ts +++ b/src/engines/baidu/baidu.ts @@ -1,6 +1,7 @@ import * as cheerio from 'cheerio'; import { SearchResult } from '../../types.js'; import { getSharedBrowser, destroySharedBrowser } from '../shared/browser.js'; +import { config } from '../../config.js'; export async function searchBaidu(query: string, limit: number): Promise { try { @@ -14,12 +15,12 @@ export async function searchBaidu(query: string, limit: number): Promise setTimeout(r, 500)); let allResults = parsePageResults(await page.content()); @@ -65,7 +66,7 @@ export async function searchBing(query: string, limit: number): Promise {}); + const navPromise = page.waitForNavigation({ waitUntil: 'networkidle2', timeout: config.requestTimeout }).catch(() => {}); await nextLink.click(); await navPromise; await new Promise(r => setTimeout(r, 1000)); @@ -79,7 +80,7 @@ export async function searchBing(query: string, limit: number): Promise { @@ -39,7 +39,7 @@ export async function searchBrave(query: string, limit: number): Promise { let allResults: SearchResult[] = []; @@ -8,6 +9,7 @@ export async function searchCsdn(query: string, limit: number): Promise { @@ -10,7 +11,8 @@ export async function fetchCsdnArticle(url: string): Promise<{ content: string } 'Connection': 'keep-alive', 'Cookie': 'https_waf_cookie=771a8075-77ae-4b2cdf3bda08cd28ad372861867be773d8c1; uuid_tt_dd=10_20283045860-1751096847125-425142; dc_session_id=10_1751096847125.891975; waf_captcha_marker=318c5c7f316f665febdb746a58e039a681a94708df7a26376ed47720663cd99d', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', - } + }, + timeout: config.requestTimeout }); const $ = cheerio.load(response.data); diff --git a/src/engines/duckduckgo/searchDuckDuckGo.ts b/src/engines/duckduckgo/searchDuckDuckGo.ts index 46780a2..c462403 100644 --- a/src/engines/duckduckgo/searchDuckDuckGo.ts +++ b/src/engines/duckduckgo/searchDuckDuckGo.ts @@ -2,7 +2,17 @@ import axios from 'axios'; import * as cheerio from 'cheerio'; import {HttpsProxyAgent} from 'https-proxy-agent'; import {SearchResult} from "../../types.js"; -import {getProxyUrl} from "../../config.js"; +import {getProxyUrl, config} from "../../config.js"; + + +/** 从错误对象提取简短消息(处理 AxiosError/AggregateError message 为空的情况) */ +function errMsg(error: unknown): string { + if (axios.isAxiosError(error)) { + return [error.code, error.message, error.response?.status && `HTTP ${error.response.status}`].filter(Boolean).join(' - ') || 'Unknown AxiosError'; + } + if (error instanceof Error && error.message) return error.message; + return String(error) || 'Unknown error'; +} /** @@ -24,7 +34,7 @@ export async function searchDuckDuckGo(query: string, limit: number): Promise( `https://exa.ai/search/api/search-fast`, data, - requestOptions + { ...requestOptions, timeout: config.requestTimeout } ); const apiResults = response.data.results; @@ -82,11 +82,10 @@ export async function searchExa(query: string, limit: number): Promise 'Accept': 'application/vnd.github.v3.raw', 'User-Agent': 'GitHub-README-Fetcher/1.0' }, - timeout: 10000, + timeout: config.requestTimeout, validateStatus: (status) => status === 200 }); diff --git a/src/engines/juejin/fetchJuejinArticle.ts b/src/engines/juejin/fetchJuejinArticle.ts index 082a26e..bdeae32 100644 --- a/src/engines/juejin/fetchJuejinArticle.ts +++ b/src/engines/juejin/fetchJuejinArticle.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import * as cheerio from 'cheerio'; +import { config } from '../../config.js'; export async function fetchJuejinArticle(url: string): Promise<{ content: string }> { try { @@ -21,7 +22,7 @@ export async function fetchJuejinArticle(url: string): Promise<{ content: string 'accept-language': 'zh-CN,zh;q=0.9', 'priority': 'u=0, i' }, - timeout: 30000, + timeout: config.requestTimeout, decompress: true }); @@ -68,7 +69,8 @@ export async function fetchJuejinArticle(url: string): Promise<{ content: string return { content }; } catch (error) { - console.error('❌ 获取掘金文章失败:', error); - throw new Error(`获取掘金文章失败: ${error instanceof Error ? error.message : '未知错误'}`); + const msg = error instanceof Error ? error.message : String(error); + console.error(`❌ 获取掘金文章失败: ${msg}`); + throw new Error(`获取掘金文章失败: ${msg}`); } } diff --git a/src/engines/juejin/juejin.ts b/src/engines/juejin/juejin.ts index 7063102..761c320 100644 --- a/src/engines/juejin/juejin.ts +++ b/src/engines/juejin/juejin.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import { SearchResult } from '../../types.js'; +import { config } from '../../config.js'; interface JuejinSearchResponse { err_no: number; @@ -44,6 +45,7 @@ export async function searchJuejin(query: string, limit: number): Promise('https://api.juejin.cn/search_api/v1/search', { + timeout: config.requestTimeout, params: { aid: '2608', uuid: '7259393293459605051', @@ -79,17 +81,18 @@ export async function searchJuejin(query: string, limit: number): Promise { + const results: SearchResult[] = responseData.data + .filter((item) => item.result_model?.article_info && item.result_model?.author_user_info) + .map((item) => { const { result_model, title_highlight, content_highlight } = item; const { article_info, author_user_info, category, tags } = result_model; // 移除HTML标签的高亮标记 - const cleanTitle = title_highlight.replace(/<\/?em>/g, ''); - const cleanContent = content_highlight.replace(/<\/?em>/g, ''); + const cleanTitle = (title_highlight || article_info.title || '').replace(/<\/?em>/g, ''); + const cleanContent = (content_highlight || article_info.brief_content || '').replace(/<\/?em>/g, ''); - // 构建描述信息 - const tagNames = tags.map(tag => tag.tag_name).join(', '); - const description = `${cleanContent} | 分类: ${category.category_name} | 标签: ${tagNames} | 👍 ${article_info.digg_count} | 👀 ${article_info.view_count}`; + const tagNames = (tags || []).map(tag => tag.tag_name).join(', '); + const description = `${cleanContent} | 分类: ${category?.category_name ?? ''} | 标签: ${tagNames} | 👍 ${article_info.digg_count} | 👀 ${article_info.view_count}`; return { title: cleanTitle, @@ -115,11 +118,10 @@ export async function searchJuejin(query: string, limit: number): Promise { // 同时支持 /topic/123 和 /t/slug/123 两种 URL 格式 @@ -15,7 +16,8 @@ export async function fetchLinuxDoArticle(url: string): Promise<{ content: strin headers: { 'accept': 'application/json', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - } + }, + timeout: config.requestTimeout }); const cookedHtml = response.data?.post_stream?.posts?.[0]?.cooked || ''; diff --git a/src/engines/shared/browser.ts b/src/engines/shared/browser.ts index 66afd94..abadae2 100644 --- a/src/engines/shared/browser.ts +++ b/src/engines/shared/browser.ts @@ -259,9 +259,41 @@ public class HiddenLauncher { } } -function cleanupSession(session: BrowserSession) { +/** + * 优雅关闭浏览器:通过 CDP 命令让浏览器自行退出,所有子进程自动清理。 + * 如果 close() 超时,则回退到强杀进程树。 + */ +async function closeSession(session: BrowserSession) { + try { + // browser.close() 发送 CDP Browser.close 命令,浏览器会优雅退出并清理所有子进程 + await Promise.race([ + session.browser.close(), + new Promise(r => { const t = setTimeout(r, 5000); if (typeof t === 'object') t.unref(); }), // 5 秒超时,不阻塞事件循环 + ]); + } catch (err) { + // close 失败(例如浏览器已崩溃),回退到强杀 + console.error('[browser] browser.close() failed, falling back to force kill:', (err as Error).message ?? err); + killBrowserProcess(session); + } + try { rmSync(session.tempDir, { recursive: true, force: true }); } catch {} +} + +/** + * 同步强杀浏览器进程树(仅在 process.on('exit') 等无法使用 async 的场景中使用)。 + */ +function killBrowserProcess(session: BrowserSession) { + console.error(`[browser] Force killing browser process tree (PID: ${session.browserPid ?? 'unknown'})`); try { session.browser.disconnect(); } catch {} - if (session.browserPid) try { process.kill(session.browserPid); } catch {} + if (session.browserPid) { + if (process.platform === 'win32') { + // Windows 上 process.kill 只杀主进程,Edge 的子进程(GPU、渲染器等)会变成孤儿。 + // 使用 taskkill /T 级联终止整个进程树。 + try { execFileSync('taskkill', ['/F', '/T', '/PID', String(session.browserPid)], { windowsHide: true, timeout: 5000 }); } catch {} + } else { + try { process.kill(-session.browserPid); } catch {} // Unix: 杀进程组 + try { process.kill(session.browserPid); } catch {} + } + } try { rmSync(session.tempDir, { recursive: true, force: true }); } catch {} } @@ -273,7 +305,7 @@ export async function getSharedBrowser(): Promise { return cachedSession.browser; } catch { console.error('[browser] Cached browser session is dead, relaunching...'); - cleanupSession(cachedSession); + killBrowserProcess(cachedSession); cachedSession = null; } } @@ -283,24 +315,32 @@ export async function getSharedBrowser(): Promise { if (!cleanupRegistered) { cleanupRegistered = true; - const cleanup = () => { + // exit 事件只能执行同步代码,使用强杀作为最后防线 + process.once('exit', () => { + if (cachedSession) { + killBrowserProcess(cachedSession); + cachedSession = null; + } + }); + // SIGINT/SIGTERM 可以执行异步代码,优先使用优雅关闭 + const signalCleanup = async () => { if (cachedSession) { - cleanupSession(cachedSession); + await closeSession(cachedSession); cachedSession = null; } + process.exit(); }; - process.once('exit', cleanup); - process.once('SIGINT', cleanup); - process.once('SIGTERM', cleanup); + process.once('SIGINT', signalCleanup); + process.once('SIGTERM', signalCleanup); } return cachedSession.browser; } /** 销毁全局浏览器会话(搜索出错时调用,下次会重新启动) */ -export function destroySharedBrowser(): void { +export async function destroySharedBrowser(): Promise { if (cachedSession) { - cleanupSession(cachedSession); + await closeSession(cachedSession); cachedSession = null; } } diff --git a/src/index.ts b/src/index.ts index c07b0e1..89f5332 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js" import { randomUUID } from "node:crypto"; import cors from 'cors'; import {config} from "./config.js"; +import { destroySharedBrowser } from "./engines/shared/browser.js"; async function main() { // Create MCP server @@ -29,6 +30,13 @@ async function main() { }).catch(error => { console.error('❌ Failed to initialize STDIO transport:', error); }); + + // stdin 关闭时(MCP 宿主停止服务器),主动清理浏览器并退出 + server.server.onclose = async () => { + console.error('[server] MCP connection closed, cleaning up...'); + await destroySharedBrowser(); + process.exit(0); + }; } // Only set up HTTP server if enabled diff --git a/src/test/test-baidu-availability.ts b/src/test/test-baidu-availability.ts index ec3f2ba..4772ae5 100644 --- a/src/test/test-baidu-availability.ts +++ b/src/test/test-baidu-availability.ts @@ -87,4 +87,4 @@ async function testBaiduAvailability() { process.exit(passed === total ? 0 : 1); } -testBaiduAvailability().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); +testBaiduAvailability().catch(console.error).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-baidu.ts b/src/test/test-baidu.ts index c0032f6..130a8d2 100644 --- a/src/test/test-baidu.ts +++ b/src/test/test-baidu.ts @@ -29,4 +29,4 @@ async function testBaiduSearch() { } // Run the test -testBaiduSearch().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); +testBaiduSearch().catch(console.error).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-bing-quality.ts b/src/test/test-bing-quality.ts index a2c49cc..b7d6570 100644 --- a/src/test/test-bing-quality.ts +++ b/src/test/test-bing-quality.ts @@ -64,4 +64,4 @@ async function testBingSearchQuality() { testBingSearchQuality() .catch(err => { console.error('❌ Test error:', err); process.exitCode = 1; }) - .finally(() => { destroySharedBrowser(); process.exit(); }); + .finally(async () => { await destroySharedBrowser(); process.exit(); }); diff --git a/src/test/test-bing.ts b/src/test/test-bing.ts index 3813875..ca25dd0 100644 --- a/src/test/test-bing.ts +++ b/src/test/test-bing.ts @@ -29,4 +29,4 @@ async function testBingSearch() { } // Run the test -testBingSearch().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); +testBingSearch().catch(console.error).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-description-length.ts b/src/test/test-description-length.ts index 5f319cd..77ac109 100644 --- a/src/test/test-description-length.ts +++ b/src/test/test-description-length.ts @@ -1,6 +1,7 @@ import { searchBing } from '../engines/bing/index.js'; import { SearchResult } from '../types.js'; import { config } from '../config.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; // 与 setupTools.ts 中相同的截断逻辑 function truncateDescriptions(results: SearchResult[], descLimit?: number): SearchResult[] { @@ -85,5 +86,4 @@ async function testDescriptionLength() { testDescriptionLength().catch(e => { console.error('❌ 测试异常:', e); - process.exit(1); -}); +}).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-linuxdo.ts b/src/test/test-linuxdo.ts index ab86f67..315c4df 100644 --- a/src/test/test-linuxdo.ts +++ b/src/test/test-linuxdo.ts @@ -1,4 +1,5 @@ import { searchLinuxDo } from '../engines/linuxdo/index.js'; +import { destroySharedBrowser } from '../engines/shared/browser.js'; async function testLinuxDoSearch() { console.log('🔍 Starting LinuxDo search test...'); @@ -28,4 +29,4 @@ async function testLinuxDoSearch() { } // Run the test -testLinuxDoSearch().catch(console.error); +testLinuxDoSearch().catch(console.error).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-search-relevance.ts b/src/test/test-search-relevance.ts index 8258860..bec9170 100644 --- a/src/test/test-search-relevance.ts +++ b/src/test/test-search-relevance.ts @@ -92,4 +92,4 @@ async function testSearchRelevance() { } } -testSearchRelevance().catch(console.error).finally(() => { destroySharedBrowser(); process.exit(0); }); +testSearchRelevance().catch(console.error).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/test/test-zhihu.ts b/src/test/test-zhihu.ts index d1194cf..aac0383 100644 --- a/src/test/test-zhihu.ts +++ b/src/test/test-zhihu.ts @@ -1,4 +1,5 @@ import {searchZhiHu} from "../engines/zhihu/zhihu.js"; +import { destroySharedBrowser } from '../engines/shared/browser.js'; async function testZhiHuSearch() { console.log('🔍 Starting Zhihu search test...'); @@ -28,4 +29,4 @@ async function testZhiHuSearch() { } // Run the test -testZhiHuSearch().catch(console.error); +testZhiHuSearch().catch(console.error).finally(async () => { await destroySharedBrowser(); process.exit(0); }); diff --git a/src/tools/setupTools.ts b/src/tools/setupTools.ts index 3ff8074..8c7b116 100644 --- a/src/tools/setupTools.ts +++ b/src/tools/setupTools.ts @@ -15,6 +15,20 @@ import {searchBrave} from "../engines/brave/index.js"; import {fetchGithubReadme} from "../engines/github/index.js"; import { fetchJuejinArticle } from "../engines/juejin/fetchJuejinArticle.js"; import { searchJuejin } from "../engines/juejin/index.js"; +import axios from 'axios'; + +/** 从错误对象中提取可读的错误消息(处理 AxiosError/AggregateError 等 message 为空的情况) */ +function getErrorMessage(error: unknown): string { + if (axios.isAxiosError(error)) { + const parts: string[] = []; + if (error.code) parts.push(error.code); + if (error.message) parts.push(error.message); + if (error.response) parts.push(`HTTP ${error.response.status}`); + if (parts.length > 0) return parts.join(' - '); + } + if (error instanceof Error && error.message) return error.message; + return String(error) || 'Unknown error'; +} // 支持的搜索引擎 const SUPPORTED_ENGINES = ['baidu', 'bing', 'linuxdo', 'csdn', 'duckduckgo','exa','brave','juejin'] as const; @@ -42,8 +56,13 @@ const distributeLimit = (totalLimit: number, engineCount: number): number[] => { ); }; +interface SearchExecutionResult { + results: SearchResult[]; + errors: { engine: string; message: string }[]; +} + // 执行搜索 -const executeSearch = async (query: string, engines: string[], limit: number): Promise => { +const executeSearch = async (query: string, engines: string[], limit: number): Promise => { // Clean up the query string to ensure it won't cause issues due to spaces or special characters const cleanQuery = query.trim(); console.error(`[DEBUG] Executing search, query: "${cleanQuery}", engines: ${engines.join(', ')}, limit: ${limit}`); @@ -51,33 +70,32 @@ const executeSearch = async (query: string, engines: string[], limit: number): P if (!cleanQuery) { console.error('Query string is empty'); throw new Error('Query string cannot be empty'); - } const limits = distributeLimit(limit, engines.length); + const errors: { engine: string; message: string }[] = []; const searchTasks = engines.map((engine, index) => { const engineLimit = limits[index]; const searchFn = engineMap[engine as SupportedEngine]; if (!searchFn) { - console.warn(`Unsupported search engine: ${engine}`); + const msg = `Unsupported search engine: ${engine}`; + console.warn(msg); + errors.push({ engine, message: msg }); return Promise.resolve([]); } return searchFn(query, engineLimit).catch(error => { - console.error(`Search failed for engine ${engine}:`, error); + const msg = getErrorMessage(error); + console.error(`Search failed for engine ${engine}: ${msg}`); + errors.push({ engine, message: msg }); return []; }); }); - try { - const results = await Promise.all(searchTasks); - return results.flat().slice(0, limit); - } catch (error) { - console.error('Search execution failed:', error); - throw error; - } + const results = await Promise.all(searchTasks); + return { results: results.flat().slice(0, limit), errors }; }; // 验证文章 URL @@ -206,7 +224,19 @@ export const setupTools = (server: McpServer): void => { try { console.error(`Searching for "${query}" using engines: ${engines.join(', ')}`); - const results = await executeSearch(query.trim(), engines, limit); + const { results, errors } = await executeSearch(query.trim(), engines, limit); + + // 所有引擎全部失败 → 返回 isError + if (results.length === 0 && errors.length > 0) { + const errorDetail = errors.map(e => `${e.engine}: ${e.message}`).join('\n'); + return { + content: [{ + type: 'text', + text: `All search engines failed:\n${errorDetail}` + }], + isError: true + }; + } // 应用描述长度限制:调用参数 > 全局配置 > 不限制 const descLimit = maxDescriptionLength ?? config.maxDescriptionLength; @@ -219,23 +249,30 @@ export const setupTools = (server: McpServer): void => { })) : results; + const response: Record = { + query: query.trim(), + engines: engines, + totalResults: truncatedResults.length, + results: truncatedResults + }; + + // 部分引擎失败 → 在结果中附带警告 + if (errors.length > 0) { + response.warnings = errors.map(e => `${e.engine}: ${e.message}`); + } + return { content: [{ type: 'text', - text: JSON.stringify({ - query: query.trim(), - engines: engines, - totalResults: truncatedResults.length, - results: truncatedResults - }, null, 2) + text: JSON.stringify(response, null, 2) }] }; } catch (error) { - console.error('Search tool execution failed:', error); + console.error(`Search tool execution failed: ${getErrorMessage(error)}`); return { content: [{ type: 'text', - text: `Search failed: ${error instanceof Error ? error.message : 'Unknown error'}` + text: `Search failed: ${getErrorMessage(error)}` }], isError: true }; @@ -265,11 +302,11 @@ export const setupTools = (server: McpServer): void => { }] }; } catch (error) { - console.error('Failed to fetch Linux.do article:', error); + console.error(`Failed to fetch Linux.do article: ${getErrorMessage(error)}`); return { content: [{ type: 'text', - text: `Failed to fetch article: ${error instanceof Error ? error.message : 'Unknown error'}` + text: `Failed to fetch article: ${getErrorMessage(error)}` }], isError: true }; @@ -299,11 +336,11 @@ export const setupTools = (server: McpServer): void => { }] }; } catch (error) { - console.error('Failed to fetch CSDN article:', error); + console.error(`Failed to fetch CSDN article: ${getErrorMessage(error)}`); return { content: [{ type: 'text', - text: `Failed to fetch article: ${error instanceof Error ? error.message : 'Unknown error'}` + text: `Failed to fetch article: ${getErrorMessage(error)}` }], isError: true }; @@ -343,11 +380,11 @@ export const setupTools = (server: McpServer): void => { }; } } catch (error) { - console.error('Failed to fetch GitHub README:', error); + console.error(`Failed to fetch GitHub README: ${getErrorMessage(error)}`); return { content: [{ type: 'text', - text: `Failed to fetch README: ${error instanceof Error ? error.message : 'Unknown error'}` + text: `Failed to fetch README: ${getErrorMessage(error)}` }], isError: true }; @@ -377,11 +414,11 @@ export const setupTools = (server: McpServer): void => { }] }; } catch (error) { - console.error('Failed to fetch Juejin article:', error); + console.error(`Failed to fetch Juejin article: ${getErrorMessage(error)}`); return { content: [{ type: 'text', - text: `Failed to fetch article: ${error instanceof Error ? error.message : 'Unknown error'}` + text: `Failed to fetch article: ${getErrorMessage(error)}` }], isError: true }; From 9b51a4fa29703b179cea02aba65ea23553803750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Fri, 27 Feb 2026 14:37:16 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E5=BC=95?= =?UTF-8?q?=E6=93=8E=E6=90=9C=E7=B4=A2=E6=97=B6=E9=94=99=E8=AF=AF=E5=8F=8D?= =?UTF-8?q?=E9=A6=88=E4=B8=8D=E5=AE=8C=E6=95=B4=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 区分全部失败与部分失败:errors.length === engines.length 才判定为全部失败 - 0结果但部分引擎失败时,同时报告失败引擎的错误和成功引擎的0结果状态 - 避免DuckDuckGo超时掩盖Bing返回0结果的信息 --- src/tools/setupTools.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/tools/setupTools.ts b/src/tools/setupTools.ts index 8c7b116..77a2752 100644 --- a/src/tools/setupTools.ts +++ b/src/tools/setupTools.ts @@ -226,8 +226,8 @@ export const setupTools = (server: McpServer): void => { const { results, errors } = await executeSearch(query.trim(), engines, limit); - // 所有引擎全部失败 → 返回 isError - if (results.length === 0 && errors.length > 0) { + // 所有引擎全部失败(错误数 == 引擎数)→ 返回 isError + if (errors.length === engines.length) { const errorDetail = errors.map(e => `${e.engine}: ${e.message}`).join('\n'); return { content: [{ @@ -238,6 +238,23 @@ export const setupTools = (server: McpServer): void => { }; } + // 0 结果但只有部分引擎失败 → 也标记为错误,列出各引擎状态 + if (results.length === 0 && errors.length > 0) { + const failedSet = new Set(errors.map(e => e.engine)); + const succeededEngines = engines.filter(e => !failedSet.has(e)); + const lines = errors.map(e => `${e.engine}: ${e.message}`); + if (succeededEngines.length > 0) { + lines.push(`${succeededEngines.join(', ')}: returned 0 results`); + } + return { + content: [{ + type: 'text', + text: `No results found:\n${lines.join('\n')}` + }], + isError: true + }; + } + // 应用描述长度限制:调用参数 > 全局配置 > 不限制 const descLimit = maxDescriptionLength ?? config.maxDescriptionLength; const truncatedResults = descLimit From e1393fad92736de3b4c615e4e0471fa2365f569a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Mon, 2 Mar 2026 13:21:08 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Bing=20=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=9B=A0=E5=BC=82=E6=AD=A5=E6=B8=B2=E6=9F=93=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=200=20=E7=BB=93=E6=9E=9C=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=97=A0=E7=BB=93=E6=9E=9C=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E4=B8=AD=E6=96=87=E5=8F=8D=E9=A6=88=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bing: 用 waitForSelector 等待搜索结果 DOM 就绪,替代固定 500ms 延时 - Bing: 首次解析为空时自动重试,兼容 cn.bing.com 异步渲染延迟 - Bing: 翻页后也用 waitForSelector 替代固定等待 - DuckDuckGo: 增加预加载和 HTML 方法返回空结果时的中文警告日志 - 搜索工具: 新增所有引擎成功但 0 结果的分支,返回中文信息性提示 - 所有新增日志和提示文本均使用中文 --- src/engines/bing/bing.ts | 32 +++- src/engines/duckduckgo/searchDuckDuckGo.ts | 7 +- src/test/diag-bing.ts | 164 +++++++++++++++++++++ src/tools/setupTools.ts | 16 ++ 4 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 src/test/diag-bing.ts diff --git a/src/engines/bing/bing.ts b/src/engines/bing/bing.ts index 199750e..d0b1983 100644 --- a/src/engines/bing/bing.ts +++ b/src/engines/bing/bing.ts @@ -59,9 +59,26 @@ export async function searchBing(query: string, limit: number): Promise setTimeout(r, 500)); + + // cn.bing.com 等本地化版本可能异步渲染搜索结果, + // networkidle2 无法保证 DOM 已就绪,需要显式等待结果选择器。 + try { + await page.waitForSelector('#b_results .b_algo', { timeout: 10000 }); + } catch { + // 可能确实没有结果,或者 Bing 使用了不同的页面结构,继续尝试解析 + console.warn('[bing] 等待搜索结果选择器 #b_results .b_algo 超时,页面 URL:', page.url()); + } + let allResults = parsePageResults(await page.content()); + // 如果首次解析为空,可能是异步渲染还没完成(cn.bing.com 特有的延迟), + // 或者出现了 cookie 同意弹窗等遮挡,尝试再等待一次。 + if (allResults.length === 0) { + console.warn('[bing] 首次解析返回 0 条结果,等待 3 秒后重试...'); + await new Promise(r => setTimeout(r, 3000)); + allResults = parsePageResults(await page.content()); + } + while (allResults.length < limit) { const nextLink = await page.$('.sb_pagN'); if (!nextLink) break; @@ -69,13 +86,22 @@ export async function searchBing(query: string, limit: number): Promise {}); await nextLink.click(); await navPromise; - await new Promise(r => setTimeout(r, 1000)); + try { + await page.waitForSelector('#b_results .b_algo', { timeout: 10000 }); + } catch { + // 翻页后如果超时,继续尝试解析 + } const pageResults = parsePageResults(await page.content()); if (pageResults.length === 0) break; allResults = allResults.concat(pageResults); } - return allResults.slice(0, limit); + const finalResults = allResults.slice(0, limit); + if (finalResults.length === 0) { + const finalUrl = page.url(); + console.warn(`[bing] 搜索返回 0 条结果。最终 URL: ${finalUrl}。页面可能出现了验证码、Cookie 同意弹窗,或 HTML 结构已变更。`); + } + return finalResults; } finally { await page.close(); } diff --git a/src/engines/duckduckgo/searchDuckDuckGo.ts b/src/engines/duckduckgo/searchDuckDuckGo.ts index c462403..d6c0ac3 100644 --- a/src/engines/duckduckgo/searchDuckDuckGo.ts +++ b/src/engines/duckduckgo/searchDuckDuckGo.ts @@ -33,11 +33,16 @@ export async function searchDuckDuckGo(query: string, limit: number): Promise 0) { return results; } + console.warn('[duckduckgo] 预加载 URL 方法返回 0 条结果,尝试 HTML 方法...'); } catch (error) { console.warn('预加载URL方法失败,尝试HTML方法:', errMsg(error)); } - return await searchDuckDuckGoHtml(query, limit, effectiveProxyUrl); + const htmlResults = await searchDuckDuckGoHtml(query, limit, effectiveProxyUrl); + if (htmlResults.length === 0) { + console.warn('[duckduckgo] 预加载和 HTML 两种方法均返回 0 条结果。查询可能过于具体,或 DuckDuckGo 阻止了请求。'); + } + return htmlResults; } /** diff --git a/src/test/diag-bing.ts b/src/test/diag-bing.ts new file mode 100644 index 0000000..a06bf98 --- /dev/null +++ b/src/test/diag-bing.ts @@ -0,0 +1,164 @@ +/** + * 诊断脚本:分析 Bing 搜索为何对指定查询返回 0 结果。 + * 运行: npx tsx src/test/diag-bing.ts + */ +import { getSharedBrowser, destroySharedBrowser } from '../engines/shared/browser.js'; +import * as cheerio from 'cheerio'; +import { writeFileSync } from 'fs'; +import { join } from 'path'; + +const QUERY = 'proot-xed signal 11 SIGSEGV bus error Android apk add bash post-install crash workaround'; + +async function diagnose() { + console.log('=== Bing 搜索诊断 ==='); + console.log(`查询: "${QUERY}"`); + console.log(`编码后: ${encodeURIComponent(QUERY)}`); + console.log(`URL 长度: ${'https://www.bing.com/search?q='.length + encodeURIComponent(QUERY).length}`); + console.log(); + + const browser = await getSharedBrowser(); + const page = await browser.newPage(); + + try { + const searchUrl = `https://www.bing.com/search?q=${encodeURIComponent(QUERY)}`; + console.log(`导航到: ${searchUrl}`); + + const response = await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 30000 }); + console.log(`HTTP 状态码: ${response?.status()}`); + console.log(`最终 URL: ${page.url()}`); + + await new Promise(r => setTimeout(r, 2000)); // 多等一会 + + const html = await page.content(); + const outPath = join(process.cwd(), 'diag-bing-output.html'); + writeFileSync(outPath, html, 'utf8'); + console.log(`\n页面 HTML 已保存到: ${outPath} (${html.length} 字符)`); + + // 截图 + const screenshotPath = join(process.cwd(), 'diag-bing-screenshot.png'); + await page.screenshot({ path: screenshotPath, fullPage: true }); + console.log(`截图已保存到: ${screenshotPath}`); + + const $ = cheerio.load(html); + + console.log('\n=== 选择器分析 ==='); + console.log(`#b_results 是否存在: ${$('#b_results').length > 0}`); + console.log(`#b_results 子元素数: ${$('#b_results').children().length}`); + console.log(`#b_results h2 数量: ${$('#b_results h2').length}`); + console.log(`#b_results li 数量: ${$('#b_results li').length}`); + console.log(`#b_results .b_algo 数量: ${$('#b_results .b_algo').length}`); + console.log(`所有 h2 数量: ${$('h2').length}`); + console.log(`所有 h2 文本:`); + $('h2').each((i, el) => { + console.log(` [${i}] ${$(el).text().trim().substring(0, 100)}`); + }); + + // 检查 Cookie 同意弹窗 + console.log('\n=== Cookie/弹窗检测 ==='); + const cookieSelectors = [ + '#bnp_container', // Bing cookie banner + '#bnp_btn_accept', // Bing accept button + '.bnp_btn_accept', + '#consent-banner', + '.consent-banner', + '#onetrust-banner', + '.cc-banner', + '#cookie-banner', + '#b_notificationContainer', + 'div[data-bm="1"]', // Bing modal overlay + ]; + for (const sel of cookieSelectors) { + const count = $(sel).length; + if (count > 0) { + console.log(` ✓ 发现弹窗元素: ${sel} (${count} 个)`); + console.log(` 内容: ${$(sel).text().trim().substring(0, 200)}`); + } + } + + // 检查验证码 + console.log('\n=== 验证码/反爬检测 ==='); + const captchaSelectors = [ + '#captcha', + '.captcha', + '#cf-wrapper', // Cloudflare challenge + '#challenge-running', + 'form[action*="captcha"]', + '#bnp_captcha', + ]; + for (const sel of captchaSelectors) { + const count = $(sel).length; + if (count > 0) { + console.log(` ✓ 发现验证码元素: ${sel} (${count} 个)`); + } + } + + // 检查 "没有结果" 提示 + console.log('\n=== 无结果/错误提示检测 ==='); + const noResultSelectors = [ + '.b_no', // Bing no results + '.b_msg', + '#b_results .b_ans', + ]; + for (const sel of noResultSelectors) { + const count = $(sel).length; + if (count > 0) { + console.log(` ✓ 发现提示: ${sel}: "${$(sel).text().trim().substring(0, 200)}"`); + } + } + + // 检查是否被重定向到 cn.bing.com 或其他本地化版本 + console.log('\n=== URL/重定向分析 ==='); + const finalUrl = page.url(); + console.log(`最终 URL: ${finalUrl}`); + if (!finalUrl.includes('www.bing.com')) { + console.log('⚠️ 被重定向到了非 www.bing.com 的域名!'); + } + + // 输出页面 title + console.log(`\n页面标题: ${$('title').text()}`); + + // 显示 body 的前 1000 个文本字符 + console.log('\n=== 页面正文前 500 字符 ==='); + const bodyText = $('body').text().replace(/\s+/g, ' ').trim(); + console.log(bodyText.substring(0, 500)); + + // 检查 #b_results 内部结构 + console.log('\n=== #b_results 内部结构 ==='); + $('#b_results').children().each((i, el) => { + const tag = (el as any).tagName; + const cls = $(el).attr('class') || ''; + const id = $(el).attr('id') || ''; + const h2Text = $(el).find('h2').first().text().trim().substring(0, 80); + console.log(` [${i}] <${tag}> class="${cls}" id="${id}" h2="${h2Text}"`); + if (i > 15) { + console.log(' ... (截断)'); + return false; + } + }); + + // 看链接 + console.log('\n=== #b_results 中的 a[href^=http] ==='); + let linkCount = 0; + $('#b_results a[href^="http"]').each((i, el) => { + if (linkCount >= 10) return false; + const href = $(el).attr('href'); + const text = $(el).text().trim().substring(0, 80); + if (text) { + console.log(` [${linkCount}] "${text}" -> ${href?.substring(0, 120)}`); + linkCount++; + } + }); + if (linkCount === 0) { + console.log(' (无 http 链接)'); + } + + } finally { + await page.close(); + await destroySharedBrowser(); + } +} + +diagnose().catch(err => { + console.error('诊断失败:', err); + process.exit(1); +}); diff --git a/src/tools/setupTools.ts b/src/tools/setupTools.ts index 77a2752..8635890 100644 --- a/src/tools/setupTools.ts +++ b/src/tools/setupTools.ts @@ -255,6 +255,22 @@ export const setupTools = (server: McpServer): void => { }; } + // 所有引擎均成功执行但返回 0 条结果 → 不是错误,给出信息性提示 + if (results.length === 0 && errors.length === 0) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ + query: query.trim(), + engines: engines, + totalResults: 0, + results: [], + message: `所有搜索引擎(${engines.join('、')})均未找到与此查询匹配的结果。可能是查询过于具体、过长,或搜索引擎未能找到匹配的页面。建议简化查询或使用不同的关键词。` + }, null, 2) + }] + }; + } + // 应用描述长度限制:调用参数 > 全局配置 > 不限制 const descLimit = maxDescriptionLength ?? config.maxDescriptionLength; const truncatedResults = descLimit From 78b6a23e144dd1e33f27a2a4c7d698432ba62433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Mon, 2 Mar 2026 14:24:34 +0800 Subject: [PATCH 15/15] =?UTF-8?q?MCP=20=E5=B7=A5=E5=85=B7=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=E5=A2=9E=E5=8A=A0=E4=B8=AD=E6=96=87=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E6=8C=87=E5=BC=95=EF=BC=8C=E5=B8=AE=E5=8A=A9?= =?UTF-8?q?=20Agent=20=E5=88=A4=E6=96=AD=E4=BD=95=E6=97=B6=E8=B0=83?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/setupTools.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tools/setupTools.ts b/src/tools/setupTools.ts index 8635890..90ecdfc 100644 --- a/src/tools/setupTools.ts +++ b/src/tools/setupTools.ts @@ -172,8 +172,9 @@ export const setupTools = (server: McpServer): void => { // 搜索工具 // 生成搜索工具的动态描述 const getSearchDescription = () => { + const scenarioHint = '。当你需要查找最新信息、验证事实、研究主题或查阅超出你训练数据的文档/教程时,请使用此工具。中文查询优先选择 Baidu 或 Bing;英文查询优先选择 Bing 或 DuckDuckGo;开发/编程主题可考虑 CSDN、Juejin(掘金) 或 Exa。'; if (config.allowedSearchEngines.length === 0) { - return "Search the web using multiple engines (e.g., Baidu, Bing, DuckDuckGo, CSDN, Exa, Brave, Juejin(掘金)) with no API key required"; + return "Search the web using multiple engines (e.g., Baidu, Bing, DuckDuckGo, CSDN, Exa, Brave, Juejin(掘金)) with no API key required" + scenarioHint; } else { const enginesText = config.allowedSearchEngines.map(e => { switch (e) { @@ -183,7 +184,7 @@ export const setupTools = (server: McpServer): void => { return e.charAt(0).toUpperCase() + e.slice(1); } }).join(', '); - return `Search the web using these engines: ${enginesText} (no API key required)`; + return `Search the web using these engines: ${enginesText} (no API key required)` + scenarioHint; } }; @@ -316,7 +317,7 @@ export const setupTools = (server: McpServer): void => { // 获取 Linux.do 文章工具 server.tool( fetchLinuxDoToolName, - "Fetch full article content from a linux.do post URL", + "获取 linux.do 帖子的完整内容。当搜索结果中包含 linux.do 链接,且你需要阅读完整讨论或帖子内容以回答用户问题时,请使用此工具。", { url: z.string().url().refine( (url) => validateArticleUrl(url, 'linuxdo'), @@ -350,7 +351,7 @@ export const setupTools = (server: McpServer): void => { // 获取 CSDN 文章工具 server.tool( fetchCsdnToolName, - "Fetch full article content from a csdn post URL", + "获取 CSDN 博客文章的完整内容。当搜索结果中包含 blog.csdn.net 链接,且你需要全文来回答用户问题时,请使用此工具。", { url: z.string().url().refine( (url) => validateArticleUrl(url, 'csdn'), @@ -384,7 +385,7 @@ export const setupTools = (server: McpServer): void => { // 获取 GitHub README 工具 server.tool( fetchGithubToolName, - "Fetch README content from a GitHub repository URL", + "获取 GitHub 仓库的 README 内容。当用户询问某个 GitHub 项目,或你需要从 README 了解项目用途、安装步骤或使用方式时,请使用此工具。", { url: z.string().min(1).refine( (url) => validateGithubUrl(url), @@ -428,7 +429,7 @@ export const setupTools = (server: McpServer): void => { // 获取掘金文章工具 server.tool( fetchJuejinToolName, - "Fetch full article content from a Juejin(掘金) post URL", + "获取掘金(Juejin)文章的完整内容。当搜索结果中包含 juejin.cn 链接,且你需要全文来提供详细回答时,请使用此工具。", { url: z.string().url().refine( (url) => validateArticleUrl(url, 'juejin'),