diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 848ead7..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2298 +0,0 @@ -{ - "name": "@leadbay/openclaw-leadclaw", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@leadbay/openclaw-leadclaw", - "version": "0.1.0", - "license": "MIT", - "devDependencies": { - "@types/node": "^25.6.0", - "@vitest/coverage-v8": "^2.1.0", - "typescript": "^5.5", - "vitest": "^2.1.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.19.0" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", - "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", - "magicast": "^0.3.5", - "std-env": "^3.8.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "2.1.9", - "vitest": "2.1.9" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "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==", - "dev": true, - "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/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^10.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "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==", - "dev": true, - "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/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/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==", - "dev": true, - "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/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - } - } -} diff --git a/package.json b/package.json index ffe330f..6ad10a3 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,18 @@ { - "name": "@leadbay/openclaw-leadclaw", - "version": "0.1.0", - "description": "OpenClaw plugin for Leadbay — AI lead discovery, qualification, and enrichment", + "name": "leadclaw-workspace", + "version": "0.0.0", + "private": true, + "description": "Leadbay packages monorepo — @leadbay/core shared tools, @leadbay/leadclaw OpenClaw adapter, @leadbay/mcp MCP server", "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist/", - "openclaw.plugin.json", - "README.md", - "LICENSE", - "logo.png" - ], - "openclaw": { - "extensions": [ - "./dist/index.js" - ], - "install": { - "npmSpec": "@leadbay/openclaw-leadclaw" - }, - "compat": { - "pluginApi": ">=2026.3.24-beta.2", - "minGatewayVersion": "2026.3.24-beta.2" - }, - "build": { - "openclawVersion": "2026.3.24-beta.2", - "pluginSdkVersion": "2026.3.24-beta.2" - } - }, "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "test:smoke": "vitest run --config vitest.smoke.config.ts", - "posttest": "echo 'Tip: run npm run test:smoke with LEADBAY_TEST_TOKEN to test the live API.'", - "prepublishOnly": "npm run build && npm test" + "build": "pnpm -r build", + "typecheck": "pnpm -r typecheck", + "test": "pnpm -r test", + "test:smoke": "pnpm --filter @leadbay/core --filter @leadbay/mcp test:smoke" }, "devDependencies": { "@types/node": "^25.6.0", - "@vitest/coverage-v8": "^2.1.0", + "tsup": "^8.3.5", "typescript": "^5.5", "vitest": "^2.1.0" }, diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 0000000..e896f69 --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Leadbay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..c62248f --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,16 @@ +{ + "name": "@leadbay/core", + "version": "0.1.0", + "private": true, + "description": "Leadbay shared core: HTTP client and protocol-agnostic tool definitions. Consumed by @leadbay/leadclaw and @leadbay/mcp.", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:smoke": "vitest run --config vitest.smoke.config.ts" + }, + "license": "MIT" +} diff --git a/src/client.ts b/packages/core/src/client.ts similarity index 84% rename from src/client.ts rename to packages/core/src/client.ts index a91c632..c0b733c 100644 --- a/src/client.ts +++ b/packages/core/src/client.ts @@ -12,6 +12,11 @@ const LENS_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes const TASTE_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes const MAX_CONCURRENT = 5; +const REGIONS: Record = { + us: "https://api-us.leadbay.app", + fr: "https://api-fr.leadbay.app", +}; + interface HttpResult { status: number; body: string; @@ -62,6 +67,23 @@ export interface TasteProfileResult { qualificationQuestions: AiAgentQuestionPayload[]; } +export interface CreateClientConfig { + token?: string; + region?: "us" | "fr"; + baseUrl?: string; +} + +export function createClient(config: CreateClientConfig = {}): LeadbayClient { + const region = config.region ?? "us"; + const baseUrl = config.baseUrl ?? REGIONS[region]; + if (!baseUrl) { + throw new Error( + `Leadbay: unknown region "${region}". Supported: ${Object.keys(REGIONS).join(", ")}. Or pass an explicit baseUrl.` + ); + } + return new LeadbayClient(baseUrl, config.token); +} + export class LeadbayClient { private token: string | null; readonly baseUrl: string; @@ -88,6 +110,11 @@ export class LeadbayClient { return this.token !== null; } + // Test-only getter for concurrency assertions + get _semaphoreState(): { active: number; queued: number } { + return { active: this.activeRequests, queued: this.waitQueue.length }; + } + private async acquireSemaphore(): Promise { if (this.activeRequests < MAX_CONCURRENT) { this.activeRequests++; @@ -112,7 +139,7 @@ export class LeadbayClient { throw this.makeError( "NOT_AUTHENTICATED", "Not logged in to Leadbay", - "Call leadbay_login with your Leadbay email and password first" + "Set LEADBAY_TOKEN env var (obtain token at https://app.leadbay.ai/settings/api-tokens), or use the OpenClaw leadbay_login tool" ); } await this.acquireSemaphore(); @@ -151,7 +178,7 @@ export class LeadbayClient { throw this.makeError( "NOT_AUTHENTICATED", "Not logged in to Leadbay", - "Call leadbay_login with your Leadbay email and password first" + "Set LEADBAY_TOKEN env var (obtain token at https://app.leadbay.ai/settings/api-tokens), or use the OpenClaw leadbay_login tool" ); } await this.acquireSemaphore(); @@ -191,14 +218,14 @@ export class LeadbayClient { return this.makeError( "AUTH_EXPIRED", "Authentication token expired or invalid", - "Call leadbay_login to re-authenticate" + "Your LEADBAY_TOKEN is no longer valid. Regenerate at https://app.leadbay.ai/settings/api-tokens and restart." ); } if (status === 402 || parsed?.error === "quota_exceeded") { return this.makeError( "QUOTA_EXCEEDED", "No enrichment credits remaining", - "Purchase more credits at app.leadbay.ai" + "Your Leadbay account is out of credits. Purchase more at https://app.leadbay.ai" ); } if (status === 403) { @@ -210,13 +237,13 @@ export class LeadbayClient { return this.makeError( "BILLING_SUSPENDED", "Account billing is suspended", - "Check billing at app.leadbay.ai" + "Your Leadbay account billing is suspended. Update at https://app.leadbay.ai" ); } return this.makeError( "FORBIDDEN", "Insufficient permissions", - "Check your account permissions" + "Your token does not have access to this resource. Check account permissions at https://app.leadbay.ai" ); } if (status === 404) { @@ -327,3 +354,5 @@ export class LeadbayClient { return { error: true, code, message, hint }; } } + +export { REGIONS }; diff --git a/packages/core/src/composite/find-prospects.ts b/packages/core/src/composite/find-prospects.ts new file mode 100644 index 0000000..58c2879 --- /dev/null +++ b/packages/core/src/composite/find-prospects.ts @@ -0,0 +1,68 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool, ToolContext } from "../types.js"; +import { listLenses } from "../tools/list-lenses.js"; +import { discoverLeads } from "../tools/discover-leads.js"; + +interface FindProspectsParams { + lensName?: string; + lensId?: number; + count?: number; +} + +export const findProspects: Tool = { + name: "leadbay_find_prospects", + description: + "Find B2B prospects matching the user's Ideal Buyer Profile. Uses the active Leadbay lens by default. Returns scored lead summaries with AI qualification, recommended contact titles, and next-step suggestions. Start here for most lead-gen tasks.", + inputSchema: { + type: "object", + properties: { + lensName: { + type: "string", + description: + "Lens name to search (optional). If omitted, uses the active lens. The lens defines the target market segment.", + }, + lensId: { + type: "number", + description: + "Lens ID (optional, takes precedence over lensName). Auto-resolves to the active lens if both omitted.", + }, + count: { + type: "number", + description: "Number of prospects to return, max 50 (default: 20)", + }, + }, + }, + execute: async ( + client: LeadbayClient, + params: FindProspectsParams, + ctx?: ToolContext + ) => { + let lensId = params.lensId; + + if (!lensId && params.lensName) { + // Resolve lensName → lensId + const lensesResult = await listLenses.execute(client, {}, ctx); + const match = (lensesResult.lenses as Array<{ id: number; name: string }>).find( + (l) => l.name.toLowerCase() === params.lensName!.toLowerCase() + ); + if (!match) { + throw client.makeError( + "LENS_NOT_FOUND", + `No lens named "${params.lensName}" found`, + "Call leadbay_list_lenses to see available lens names, or omit lensName to use the active lens." + ); + } + lensId = match.id; + } + + return discoverLeads.execute( + client, + { + lensId, + count: params.count, + page: 0, + }, + ctx + ); + }, +}; diff --git a/packages/core/src/composite/prepare-outreach.ts b/packages/core/src/composite/prepare-outreach.ts new file mode 100644 index 0000000..4a7bade --- /dev/null +++ b/packages/core/src/composite/prepare-outreach.ts @@ -0,0 +1,111 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool, ToolContext } from "../types.js"; +import { getLeadProfile } from "../tools/get-lead-profile.js"; +import { getContacts } from "../tools/get-contacts.js"; +import { enrichContacts } from "../tools/enrich-contacts.js"; + +interface PrepareOutreachParams { + leadId: string; + enrich?: boolean; +} + +export const prepareOutreach: Tool = { + name: "leadbay_prepare_outreach", + description: + "Prepare an outreach package for a lead: returns the recommended contact (best match by job title), their enriched email/phone (if available), and the lead's AI summary. If enrich=true and credits are available, will trigger enrichment on the recommended contact and return the ID to poll later. Write action — requires user-level permission.", + optional: true, + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + enrich: { + type: "boolean", + description: + "If true and credits available, trigger enrichment on the recommended contact (default: false). Enrichment is async — poll leadbay_get_contacts after ~60s.", + }, + }, + required: ["leadId"], + }, + execute: async ( + client: LeadbayClient, + params: PrepareOutreachParams, + ctx?: ToolContext + ) => { + const contactsResult = await getContacts.execute( + client, + { leadId: params.leadId }, + ctx + ); + const contacts = contactsResult.contacts as Array<{ + id: string; + first_name: string | null; + last_name: string | null; + email: string | null; + phone_number: string | null; + linkedin_page: string | null; + job_title: string | null; + recommended: boolean; + source: "org" | "paid"; + }>; + + const recommended = contacts.find((c) => c.recommended) ?? contacts[0]; + + let enrichmentTriggered = false; + let enrichmentError: string | null = null; + if (params.enrich && recommended) { + try { + await enrichContacts.execute( + client, + { leadId: params.leadId, contactId: recommended.id }, + ctx + ); + enrichmentTriggered = true; + } catch (e: any) { + enrichmentError = e?.message ?? String(e); + } + } + + // Also fetch a short profile for AI summary context + let leadSummary: { name: string; ai_summary: string | null; website: string | null } | null = + null; + try { + const profile = await getLeadProfile.execute( + client, + { leadId: params.leadId }, + ctx + ); + leadSummary = { + name: (profile.lead as any).name, + ai_summary: (profile.lead as any).ai_summary, + website: (profile.lead as any).website, + }; + } catch {} + + return { + lead: leadSummary, + recommended_contact: recommended + ? { + id: recommended.id, + name: [recommended.first_name, recommended.last_name] + .filter(Boolean) + .join(" "), + job_title: recommended.job_title, + email: recommended.email, + phone_number: recommended.phone_number, + linkedin_page: recommended.linkedin_page, + } + : null, + other_contacts_count: Math.max(0, contacts.length - 1), + enrichment: { + triggered: enrichmentTriggered, + error: enrichmentError, + hint: enrichmentTriggered + ? "Enrichment started. Poll leadbay_get_contacts with the same leadId in ~60 seconds." + : null, + }, + }; + }, +}; diff --git a/packages/core/src/composite/research-company.ts b/packages/core/src/composite/research-company.ts new file mode 100644 index 0000000..a2ee7f3 --- /dev/null +++ b/packages/core/src/composite/research-company.ts @@ -0,0 +1,82 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool, ToolContext } from "../types.js"; +import { discoverLeads } from "../tools/discover-leads.js"; +import { getLeadProfile } from "../tools/get-lead-profile.js"; +import { getLeadActivities } from "../tools/get-lead-activities.js"; + +interface ResearchCompanyParams { + companyName?: string; + leadId?: string; +} + +export const researchCompany: Tool = { + name: "leadbay_research_company", + description: + "Deep-dive research on a specific company: full profile with AI qualification scores, web insights, contacts, and recent prospecting activity. Pass leadId if you already have it (from leadbay_find_prospects), or companyName to search for a match first.", + inputSchema: { + type: "object", + properties: { + companyName: { + type: "string", + description: + "Company name to research (one of companyName or leadId required). Matches the top-scoring lead with this name.", + }, + leadId: { + type: "string", + description: + "Lead UUID if already known (one of companyName or leadId required). Takes precedence over companyName.", + }, + }, + }, + execute: async ( + client: LeadbayClient, + params: ResearchCompanyParams, + ctx?: ToolContext + ) => { + if (!params.leadId && !params.companyName) { + throw client.makeError( + "INVALID_PARAMS", + "Pass either leadId or companyName", + "Use leadbay_find_prospects first to get a lead's ID, then call this with leadId." + ); + } + + let leadId = params.leadId; + + if (!leadId && params.companyName) { + // Try to match by scanning the first page of the active lens + const results = await discoverLeads.execute( + client, + { count: 50, page: 0 }, + ctx + ); + const needle = params.companyName.toLowerCase(); + const match = (results.leads as Array<{ id: string; name: string }>).find( + (l) => l.name.toLowerCase().includes(needle) + ); + if (!match) { + throw client.makeError( + "LEAD_NOT_FOUND", + `No lead matching "${params.companyName}" in the current lens`, + "Try leadbay_find_prospects with a broader lens, or search by leadId instead." + ); + } + leadId = match.id; + } + + const [profile, activities] = await Promise.allSettled([ + getLeadProfile.execute(client, { leadId: leadId! }, ctx), + getLeadActivities.execute(client, { leadId: leadId!, count: 20 }, ctx), + ]); + + if (profile.status === "rejected") { + throw profile.reason; + } + + return { + ...profile.value, + recent_activities: + activities.status === "fulfilled" ? activities.value.activities : [], + }; + }, +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..c9361cc --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,67 @@ +export { LeadbayClient, createClient, REGIONS } from "./client.js"; +export type { CreateClientConfig, TasteProfileResult } from "./client.js"; +export * from "./types.js"; + +// Granular tools (11, 1:1 with Leadbay API endpoints) +import { login } from "./tools/login.js"; +import { listLenses } from "./tools/list-lenses.js"; +import { discoverLeads } from "./tools/discover-leads.js"; +import { getLeadProfile } from "./tools/get-lead-profile.js"; +import { getContacts } from "./tools/get-contacts.js"; +import { getQuota } from "./tools/get-quota.js"; +import { getTasteProfile } from "./tools/get-taste-profile.js"; +import { qualifyLead } from "./tools/qualify-lead.js"; +import { enrichContacts } from "./tools/enrich-contacts.js"; +import { addNote } from "./tools/add-note.js"; +import { getLeadActivities } from "./tools/get-lead-activities.js"; + +// Composite workflow tools (3, MCP primary surface) +import { findProspects } from "./composite/find-prospects.js"; +import { researchCompany } from "./composite/research-company.js"; +import { prepareOutreach } from "./composite/prepare-outreach.js"; + +import type { Tool } from "./types.js"; + +export { + login, + listLenses, + discoverLeads, + getLeadProfile, + getContacts, + getQuota, + getTasteProfile, + qualifyLead, + enrichContacts, + addNote, + getLeadActivities, + findProspects, + researchCompany, + prepareOutreach, +}; + +export const granularTools: Tool[] = [ + login, + listLenses, + discoverLeads, + getLeadProfile, + getLeadActivities, + getTasteProfile, + getContacts, + getQuota, + qualifyLead, + enrichContacts, + addNote, +]; + +// Mark the granular tools as advanced so MCP can opt them out of the default list. +granularTools.forEach((t) => { + t.advanced = true; +}); + +export const compositeTools: Tool[] = [ + findProspects, + researchCompany, + prepareOutreach, +]; + +export const tools: Tool[] = [...compositeTools, ...granularTools]; diff --git a/packages/core/src/tools/add-note.ts b/packages/core/src/tools/add-note.ts new file mode 100644 index 0000000..e25fe41 --- /dev/null +++ b/packages/core/src/tools/add-note.ts @@ -0,0 +1,52 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { NotePayload } from "../types.js"; + +interface AddNoteParams { + leadId: string; + note: string; +} + +export const addNote: Tool = { + name: "leadbay_add_note", + description: + "Add a note to a lead. Notes are visible to the whole organization in Leadbay.", + optional: true, + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + note: { + type: "string", + description: "Note text (max 4095 characters)", + }, + }, + required: ["leadId", "note"], + }, + execute: async (client: LeadbayClient, params: AddNoteParams) => { + if (!params.note || params.note.trim().length === 0) { + throw client.makeError( + "INVALID_PARAMS", + "Note cannot be empty", + "Provide a non-empty note" + ); + } + + const note = params.note.slice(0, 4095); + + const result = await client.request( + "POST", + `/leads/${params.leadId}/notes`, + { note } + ); + + return { + id: result.id, + note: result.note, + created_at: result.created_at, + }; + }, +}; diff --git a/packages/core/src/tools/discover-leads.ts b/packages/core/src/tools/discover-leads.ts new file mode 100644 index 0000000..18a1c36 --- /dev/null +++ b/packages/core/src/tools/discover-leads.ts @@ -0,0 +1,64 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { WishlistResponse } from "../types.js"; + +interface DiscoverLeadsParams { + lensId?: number; + page?: number; + count?: number; +} + +export const discoverLeads: Tool = { + name: "leadbay_discover_leads", + description: + "Get AI-recommended leads from Leadbay. Returns paginated lead summaries with scores, AI summaries, tags, and recommended contacts. After discovering leads, call leadbay_get_lead_profile on promising ones for full qualification data, web insights, and all contacts. If lensId is omitted, uses the active lens automatically.", + inputSchema: { + type: "object", + properties: { + lensId: { + type: "number", + description: "Lens ID (optional, auto-resolves to the active lens)", + }, + page: { + type: "number", + description: "Page number, 0-indexed (default: 0)", + }, + count: { + type: "number", + description: "Results per page, max 50 (default: 20)", + }, + }, + }, + execute: async (client: LeadbayClient, params: DiscoverLeadsParams) => { + const lensId = params.lensId ?? (await client.resolveDefaultLens()); + const page = params.page ?? 0; + const count = Math.min(params.count ?? 20, 50); + + const res = await client.request( + "GET", + `/lenses/${lensId}/leads/wishlist?count=${count}&page=${page}&contacts=true` + ); + + return { + leads: res.items.map((lead) => ({ + id: lead.id, + name: lead.name, + score: lead.score, + ai_agent_lead_score: lead.ai_agent_lead_score, + location: lead.location, + description: lead.description, + size: lead.size, + website: lead.website, + contacts_count: lead.contacts_count, + ai_summary: lead.ai_summary, + split_ai_summary: lead.split_ai_summary, + tags: lead.tags, + phone_numbers: lead.phone_numbers, + keywords: lead.keywords, + recommended_contact_title: lead.recommended_contact_title ?? null, + recommended_contact: lead.recommended_contact ?? null, + })), + pagination: res.pagination, + }; + }, +}; diff --git a/packages/core/src/tools/enrich-contacts.ts b/packages/core/src/tools/enrich-contacts.ts new file mode 100644 index 0000000..57f09a6 --- /dev/null +++ b/packages/core/src/tools/enrich-contacts.ts @@ -0,0 +1,91 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { UserMePayload } from "../types.js"; + +interface EnrichContactsParams { + leadId: string; + contactId: string; + email?: boolean; + phone?: boolean; +} + +export const enrichContacts: Tool = { + name: "leadbay_enrich_contacts", + description: + "Order email and/or phone enrichment for a specific contact. The contactId must come from leadbay_get_lead_profile or leadbay_get_contacts — find the contact with recommended=true for the best match. Note: the recommended_contact on lead summaries does NOT include an ID. Enrichment is asynchronous — use leadbay_get_contacts after about 60 seconds to retrieve results.", + optional: true, + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + contactId: { + type: "string", + description: "Contact UUID (required)", + }, + email: { + type: "boolean", + description: "Enrich email address (default: true)", + }, + phone: { + type: "boolean", + description: "Enrich phone number (default: true)", + }, + }, + required: ["leadId", "contactId"], + }, + execute: async (client: LeadbayClient, params: EnrichContactsParams) => { + const email = params.email ?? true; + const phone = params.phone ?? true; + + if (!email && !phone) { + throw client.makeError( + "INVALID_PARAMS", + "At least one of email or phone must be true", + "Set email=true or phone=true" + ); + } + + // Advisory quota check + let creditsRemaining: number | null = null; + try { + const me = await client.request("GET", "/users/me"); + creditsRemaining = me.organization.billing?.ai_credits ?? null; + if (creditsRemaining !== null && creditsRemaining <= 0) { + throw client.makeError( + "QUOTA_EXCEEDED", + "No enrichment credits remaining", + "Purchase more credits at app.leadbay.ai" + ); + } + } catch (e: any) { + if (e?.code === "QUOTA_EXCEEDED") throw e; + // Advisory check failed, proceed anyway — server will enforce + } + + // Try paid contact enrichment path first + const enrichPath = `/leads/${params.leadId}/enrich/contacts/${params.contactId}/enrich?email=${email}&phone=${phone}`; + try { + await client.requestVoid("POST", enrichPath); + } catch (e: any) { + if (e?.code === "NOT_FOUND") { + // Fall back to org contact enrichment path + const orgPath = `/leads/${params.leadId}/contacts/${params.contactId}/enrich?email=${email}&phone=${phone}`; + await client.requestVoid("POST", orgPath); + } else { + throw e; + } + } + + return { + triggered: true, + contact_id: params.contactId, + email_requested: email, + phone_requested: phone, + credits_remaining: creditsRemaining, + hint: "Enrichment started. Use leadbay_get_contacts after ~60 seconds to check results.", + }; + }, +}; diff --git a/packages/core/src/tools/get-contacts.ts b/packages/core/src/tools/get-contacts.ts new file mode 100644 index 0000000..ea57c64 --- /dev/null +++ b/packages/core/src/tools/get-contacts.ts @@ -0,0 +1,69 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { ContactPayload, PaidContactPayload } from "../types.js"; + +interface GetContactsParams { + leadId: string; +} + +export const getContacts: Tool = { + name: "leadbay_get_contacts", + description: + "Get contacts for a lead, including enriched email and phone data. Returns both organization contacts and enrichable contacts with IDs. The contact with recommended=true is the best match based on job title — prioritize enriching that one.", + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + }, + required: ["leadId"], + }, + execute: async (client: LeadbayClient, params: GetContactsParams) => { + const [orgResult, paidResult] = await Promise.allSettled([ + client.request( + "GET", + `/leads/${params.leadId}/contacts?IncludeEnriched=true` + ), + client.request( + "GET", + `/leads/${params.leadId}/enrich/contacts?IncludeEnriched=true` + ), + ]); + + const orgContacts = + orgResult.status === "fulfilled" ? orgResult.value : []; + const paidContacts = + paidResult.status === "fulfilled" ? paidResult.value : []; + + return { + contacts: [ + ...orgContacts.map((c) => ({ + id: c.id, + first_name: c.first_name, + last_name: c.last_name, + email: c.email, + phone_number: c.phone_number, + linkedin_page: c.linkedin_page, + job_title: c.job_title, + recommended: c.recommended, + enrichment: c.enrichment, + source: "org" as const, + })), + ...paidContacts.map((c) => ({ + id: c.id, + first_name: c.first_name, + last_name: c.last_name, + email: c.email, + phone_number: c.phone_number, + linkedin_page: c.linkedin_page, + job_title: c.job_title, + recommended: c.recommended, + enrichment: c.enrichment, + source: "paid" as const, + })), + ], + }; + }, +}; diff --git a/packages/core/src/tools/get-lead-activities.ts b/packages/core/src/tools/get-lead-activities.ts new file mode 100644 index 0000000..1ee0145 --- /dev/null +++ b/packages/core/src/tools/get-lead-activities.ts @@ -0,0 +1,44 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { PaginatedActivities } from "../types.js"; + +interface GetLeadActivitiesParams { + leadId: string; + count?: number; +} + +export const getLeadActivities: Tool = { + name: "leadbay_get_lead_activities", + description: + "Get prospecting activity history for a lead (emails sent, calls made, status changes, notes). Use this to avoid redundant outreach and understand where this lead is in the sales process.", + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + count: { + type: "number", + description: "Number of activities to return, max 100 (default: 50)", + }, + }, + required: ["leadId"], + }, + execute: async (client: LeadbayClient, params: GetLeadActivitiesParams) => { + const count = Math.min(params.count ?? 50, 100); + + const res = await client.request( + "GET", + `/leads/${params.leadId}/activities?count=${count}` + ); + + return { + activities: res.items.map((a) => ({ + type: a.type, + date: a.date, + })), + total: res.pagination.total, + }; + }, +}; diff --git a/packages/core/src/tools/get-lead-profile.ts b/packages/core/src/tools/get-lead-profile.ts new file mode 100644 index 0000000..a625d6f --- /dev/null +++ b/packages/core/src/tools/get-lead-profile.ts @@ -0,0 +1,144 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { + LeadPayload, + AiAgentResponse, + ContactPayload, + PaidContactPayload, + LeadWebFetchPayload, +} from "../types.js"; + +interface GetLeadProfileParams { + leadId: string; + lensId?: number; +} + +export const getLeadProfile: Tool = { + name: "leadbay_get_lead_profile", + description: + "Get a full lead profile including company details, AI qualification scores, web insights (company profile, business signals, prospecting clues, key people, technologies), and contacts with recommended contact highlighted. Bundles multiple API calls into one response. If some data is unavailable, partial results are still returned.", + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + lensId: { + type: "number", + description: "Lens ID (optional, auto-resolves to active lens)", + }, + }, + required: ["leadId"], + }, + execute: async (client: LeadbayClient, params: GetLeadProfileParams) => { + const lensId = params.lensId ?? (await client.resolveDefaultLens()); + + const [leadResult, qualResult, contactsResult, paidContactsResult, webFetchResult] = + await Promise.allSettled([ + client.request( + "GET", + `/lenses/${lensId}/leads/${params.leadId}` + ), + client.request( + "GET", + `/leads/${params.leadId}/ai_agent_responses` + ), + client.request( + "GET", + `/leads/${params.leadId}/contacts?IncludeEnriched=true` + ), + client.request( + "GET", + `/leads/${params.leadId}/enrich/contacts?IncludeEnriched=true` + ), + client.request( + "GET", + `/leads/${params.leadId}/web_fetch` + ), + ]); + + if (leadResult.status === "rejected") { + throw leadResult.reason; + } + + const lead = leadResult.value; + + const qualification = + qualResult.status === "fulfilled" ? qualResult.value : null; + + const orgContacts = + contactsResult.status === "fulfilled" ? contactsResult.value : []; + + const paidContacts = + paidContactsResult.status === "fulfilled" + ? paidContactsResult.value + : []; + + const webFetch = + webFetchResult.status === "fulfilled" ? webFetchResult.value : null; + + // Merge org contacts and paid contacts + const allContacts = [ + ...orgContacts.map((c) => ({ + id: c.id, + first_name: c.first_name, + last_name: c.last_name, + email: c.email, + phone_number: c.phone_number, + linkedin_page: c.linkedin_page, + job_title: c.job_title, + recommended: c.recommended, + enrichment: c.enrichment, + source: "org" as const, + })), + ...paidContacts.map((c) => ({ + id: c.id, + first_name: c.first_name, + last_name: c.last_name, + email: c.email, + phone_number: c.phone_number, + linkedin_page: c.linkedin_page, + job_title: c.job_title, + recommended: c.recommended, + enrichment: c.enrichment, + source: "paid" as const, + })), + ]; + + return { + lead: { + id: lead.id, + name: lead.name, + score: lead.score, + ai_agent_lead_score: lead.ai_agent_lead_score, + location: lead.location, + description: lead.description, + short_description: lead.short_description, + size: lead.size, + website: lead.website, + logo: lead.logo, + ai_summary: lead.ai_summary, + split_ai_summary: lead.split_ai_summary, + tags: lead.tags, + phone_numbers: lead.phone_numbers, + keywords: lead.keywords, + contacts_count: lead.contacts_count, + recommended_contact_title: lead.recommended_contact_title ?? null, + recommended_contact: lead.recommended_contact ?? null, + web_fetch_in_progress: lead.web_fetch_in_progress ?? false, + }, + qualification: + qualification?.map((q) => ({ + question: q.question, + score: q.score, + response: q.response, + computed_at: q.computed_at, + outdated_at: q.outdated_at, + })) ?? null, + contacts: allContacts, + web_insights: webFetch?.content ?? null, + web_insights_fetched_at: webFetch?.fetch_at ?? null, + }; + }, +}; diff --git a/packages/core/src/tools/get-quota.ts b/packages/core/src/tools/get-quota.ts new file mode 100644 index 0000000..9f767de --- /dev/null +++ b/packages/core/src/tools/get-quota.ts @@ -0,0 +1,23 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { UserMePayload } from "../types.js"; + +export const getQuota: Tool> = { + name: "leadbay_get_quota", + description: + "Check organization billing and AI credit quota. Useful before enrichment or qualification operations to verify available credits.", + inputSchema: { + type: "object", + properties: {}, + }, + execute: async (client: LeadbayClient) => { + const me = await client.request("GET", "/users/me"); + const org = me.organization; + return { + org_name: org.name, + ai_credits: org.billing?.ai_credits ?? null, + ai_credits_quota: org.billing?.ai_credits_quota ?? null, + billing_status: org.billing?.status ?? "unknown", + }; + }, +}; diff --git a/packages/core/src/tools/get-taste-profile.ts b/packages/core/src/tools/get-taste-profile.ts new file mode 100644 index 0000000..70839cc --- /dev/null +++ b/packages/core/src/tools/get-taste-profile.ts @@ -0,0 +1,45 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; + +export const getTasteProfile: Tool> = { + name: "leadbay_get_taste_profile", + description: + "Get the user's Ideal Buyer Profile, purchase intent tags, and qualification questions. IMPORTANT: Call this at the very start of every session to understand what kind of leads the user is looking for. This data rarely changes and is cached.", + inputSchema: { + type: "object", + properties: {}, + }, + execute: async (client: LeadbayClient) => { + const profile = await client.resolveTasteProfile(); + + const isEmpty = + !profile.idealBuyerProfile && + profile.purchaseIntentTags.length === 0 && + profile.qualificationQuestions.length === 0; + + return { + ideal_buyer_profile: profile.idealBuyerProfile + ? { + summary: profile.idealBuyerProfile.summary, + key_characteristics: + profile.idealBuyerProfile.key_characteristics, + anti_patterns: profile.idealBuyerProfile.anti_patterns, + } + : null, + purchase_intent_tags: profile.purchaseIntentTags.map((t) => ({ + display_name: t.display_name, + description: t.description, + score: t.score, + reasoning: t.reasoning, + })), + qualification_questions: profile.qualificationQuestions.map((q) => ({ + question: q.question, + })), + ...(isEmpty + ? { + hint: "No taste profile configured yet. Set it up at app.leadbay.ai for better lead matching.", + } + : {}), + }; + }, +}; diff --git a/packages/core/src/tools/list-lenses.ts b/packages/core/src/tools/list-lenses.ts new file mode 100644 index 0000000..5ae7dea --- /dev/null +++ b/packages/core/src/tools/list-lenses.ts @@ -0,0 +1,24 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; +import type { LensPayload } from "../types.js"; + +export const listLenses: Tool> = { + name: "leadbay_list_lenses", + description: + "List all available Leadbay lenses (saved lead search configurations). Each lens defines a different target market or buyer segment. The lens with is_last_active=true is used by default for lead discovery.", + inputSchema: { + type: "object", + properties: {}, + }, + execute: async (client: LeadbayClient) => { + const lenses = await client.request("GET", "/lenses"); + return { + lenses: lenses.map((l) => ({ + id: l.id, + name: l.name, + is_last_active: l.is_last_active, + description: l.description, + })), + }; + }, +}; diff --git a/packages/core/src/tools/login.ts b/packages/core/src/tools/login.ts new file mode 100644 index 0000000..c0abfe1 --- /dev/null +++ b/packages/core/src/tools/login.ts @@ -0,0 +1,110 @@ +import https from "node:https"; +import type { LeadbayClient } from "../client.js"; +import type { Tool, ToolContext } from "../types.js"; + +function httpsPost(url: string, body: string): Promise<{ status: number; body: string }> { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const req = https.request( + { + hostname: parsed.hostname, + port: 443, + path: parsed.pathname, + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body), + }, + }, + (res) => { + const chunks: Buffer[] = []; + res.on("data", (chunk) => chunks.push(chunk)); + res.on("end", () => { + resolve({ + status: res.statusCode ?? 0, + body: Buffer.concat(chunks).toString("utf8"), + }); + }); + } + ); + req.on("error", reject); + req.write(body); + req.end(); + }); +} + +interface LoginParams { + email: string; + password: string; +} + +export const login: Tool = { + name: "leadbay_login", + description: + "Log in to Leadbay with email and password. Must be called before using any other Leadbay tool. The user needs a Leadbay account — they can register at https://wow.leadbay.ai/?register=true", + inputSchema: { + type: "object", + properties: { + email: { + type: "string", + description: "Leadbay account email address", + }, + password: { + type: "string", + description: "Leadbay account password", + }, + }, + required: ["email", "password"], + }, + execute: async (client: LeadbayClient, params: LoginParams, ctx?: ToolContext) => { + // Some LLMs backslash-escape special characters in tool call JSON + // (e.g. "Password1\!" instead of "Password1!"). Strip spurious escapes. + const cleanPassword = params.password.replace(/\\(.)/g, "$1"); + const payload = JSON.stringify({ + email: params.email, + password: cleanPassword, + }); + ctx?.logger?.info?.(`LeadClaw login: email=${params.email} baseUrl=${client.baseUrl}`); + + let result: { status: number; body: string }; + try { + result = await httpsPost(`${client.baseUrl}/1.5/auth/login`, payload); + } catch (err: any) { + ctx?.logger?.error?.(`LeadClaw login: request error: ${err?.message}`); + return { + error: true, + code: "NETWORK_ERROR", + message: `Network error: ${err?.message}`, + hint: "Check your internet connection", + }; + } + + ctx?.logger?.info?.(`LeadClaw login: status=${result.status}`); + + if (result.status < 200 || result.status >= 300) { + ctx?.logger?.error?.(`LeadClaw login: error: ${result.body}`); + let msg = "Login failed"; + try { + const parsed = JSON.parse(result.body); + msg = parsed.message || parsed.error?.message || parsed.error || msg; + } catch {} + return { + error: true, + code: "LOGIN_FAILED", + message: msg, + hint: "Check your email and password. Need an account? Register at https://wow.leadbay.ai/?register=true", + }; + } + + const data = JSON.parse(result.body); + client.setToken(data.token); + + // Prefetch org data now that we're authenticated + client.prefetchOrgData().catch(() => {}); + + return { + success: true, + message: "Logged in to Leadbay successfully", + }; + }, +}; diff --git a/packages/core/src/tools/qualify-lead.ts b/packages/core/src/tools/qualify-lead.ts new file mode 100644 index 0000000..d8f4a69 --- /dev/null +++ b/packages/core/src/tools/qualify-lead.ts @@ -0,0 +1,40 @@ +import type { LeadbayClient } from "../client.js"; +import type { Tool } from "../types.js"; + +interface QualifyLeadParams { + leadId: string; + forceFetch?: boolean; +} + +export const qualifyLead: Tool = { + name: "leadbay_qualify_lead", + description: + "Trigger AI qualification for a lead. This fetches the lead's website and runs AI scoring and web insights generation. The operation is asynchronous — use leadbay_get_lead_profile after about 60 seconds to check qualification results and web insights.", + optional: true, + inputSchema: { + type: "object", + properties: { + leadId: { + type: "string", + description: "Lead UUID (required)", + }, + forceFetch: { + type: "boolean", + description: + "Force re-fetch even if recent data exists (default: false)", + }, + }, + required: ["leadId"], + }, + execute: async (client: LeadbayClient, params: QualifyLeadParams) => { + const force = params.forceFetch ?? false; + await client.requestVoid( + "POST", + `/leads/${params.leadId}/web_fetch?force_fetch=${force}` + ); + return { + triggered: true, + hint: "AI qualification started. Use leadbay_get_lead_profile after ~60 seconds to check qualification results and web insights.", + }; + }, +}; diff --git a/src/types.ts b/packages/core/src/types.ts similarity index 85% rename from src/types.ts rename to packages/core/src/types.ts index afd8a61..4e98e56 100644 --- a/src/types.ts +++ b/packages/core/src/types.ts @@ -1,4 +1,5 @@ // All interfaces use snake_case to match the Leadbay API (JsonNamingStrategy.SnakeCase) +import type { LeadbayClient } from "./client.js"; export interface LeadbayError { error: true; @@ -194,3 +195,26 @@ export interface PaginatedActivities { items: ActivityItem[]; pagination: PaginationPayload; } + +// ─── Protocol-agnostic Tool type ────────────────────────────────────────────── + +export interface ToolLogger { + info?: (msg: string) => void; + warn?: (msg: string) => void; + error?: (msg: string) => void; +} + +export interface ToolContext { + logger?: ToolLogger; +} + +export type JSONSchema = Record; + +export interface Tool

{ + name: string; + description: string; + inputSchema: JSONSchema; + optional?: boolean; + advanced?: boolean; + execute: (client: LeadbayClient, params: P, ctx?: ToolContext) => Promise; +} diff --git a/test/harness.ts b/packages/core/test/harness.ts similarity index 51% rename from test/harness.ts rename to packages/core/test/harness.ts index 72179d3..273c08e 100644 --- a/test/harness.ts +++ b/packages/core/test/harness.ts @@ -1,82 +1,20 @@ /** - * Test harness for the LeadClaw OpenClaw tool plugin. + * Test harness for packages/core. * - * Public API (stable): - * - createTestApi(config?) fake `api` object capturing registerTool calls - * - executeTool(testApi, ...) invoke a registered tool's execute() + * Protocol-agnostic — no OpenClaw API surface. Tests invoke + * `await someTool.execute(client, params, ctx?)` directly. + * + * Public API: * - mockHttp(scripts) predicate-match mock for node:https * - resetHttpMock() clear scripts between tests - * - * Do NOT import from this file's internals in test files. The public API above - * is all you need. When the OpenClaw SDK ships a tool-plugin test helper - * (openclaw/plugin-sdk/testing), this file is the one-file swap. - * - * mockHttp note: matches each script once by {method, path} against the - * outgoing https.request. Concurrent requests (e.g. Promise.allSettled in - * get-lead-profile) are handled because matching is by-request, not FIFO. + * - httpsMockFactory() returns the vi.mock factory object + * - expectAllScriptsConsumed() fail if any script was unused + * - createLogger() captures logs so tests can assert ctx.logger calls */ import { vi, expect } from "vitest"; import { EventEmitter } from "node:events"; - -export interface RegisteredTool { - name: string; - description: string; - parameters: unknown; - optional?: boolean; - execute: (id: string, params: unknown) => unknown | Promise; -} - -export interface TestApi { - api: { - pluginConfig: Record; - logger: { - info: (msg: string) => void; - warn: (msg: string) => void; - error: (msg: string) => void; - }; - registerTool: (tool: RegisteredTool) => void; - }; - tools: Map; - logs: { level: "info" | "warn" | "error"; msg: string }[]; -} - -export function createTestApi(pluginConfig: Record = {}): TestApi { - const tools = new Map(); - const logs: { level: "info" | "warn" | "error"; msg: string }[] = []; - const api = { - pluginConfig, - logger: { - info: (msg: string) => logs.push({ level: "info", msg }), - warn: (msg: string) => logs.push({ level: "warn", msg }), - error: (msg: string) => logs.push({ level: "error", msg }), - }, - registerTool: (tool: RegisteredTool) => { - tools.set(tool.name, tool); - }, - }; - return { api, tools, logs }; -} - -export async function executeTool( - testApi: TestApi, - name: string, - params: unknown = {} -): Promise { - const tool = testApi.tools.get(name); - if (!tool) { - throw new Error( - `executeTool: tool "${name}" not registered. Registered: [${Array.from( - testApi.tools.keys() - ).join(", ")}]` - ); - } - return tool.execute("test-id", params); -} - -// ---------------------------------------------------------------------------- -// mockHttp — predicate-match mock for node:https default import. -// ---------------------------------------------------------------------------- +import type { ToolLogger } from "../src/types.js"; export interface RequestScript { method: string; @@ -86,7 +24,7 @@ export interface RequestScript { error?: Error; } -interface CapturedRequest { +export interface CapturedRequest { method: string; url: string; path: string; @@ -94,7 +32,7 @@ interface CapturedRequest { headers: Record; } -// Hoisted so vi.mock's factory can see it. Not exported — access via helpers. +// Hoisted so vi.mock's factory can see it. const mockHttpState = vi.hoisted(() => ({ scripts: [] as Array<{ script: any; consumed: boolean }>, requests: [] as any[], @@ -120,12 +58,7 @@ function pathMatches(pattern: string | RegExp, path: string): boolean { return pattern === path; } -// The actual https.request replacement. Must match node:https contract closely -// enough that src/client.ts and src/tools/login.ts don't know the difference. -function fakeHttpsRequest( - options: any, - callback?: (res: any) => void -): any { +function fakeHttpsRequest(options: any, callback?: (res: any) => void): any { const method = options.method ?? "GET"; const path = options.path ?? "/"; const hostname = options.hostname ?? "localhost"; @@ -156,9 +89,7 @@ function fakeHttpsRequest( (s: any) => `${s.script.method} ${s.script.path}${s.consumed ? " [used]" : ""}` ); const err = new Error( - `mockHttp: no script matched ${method} ${path}\n registered: [${registered.join( - ", " - )}]` + `mockHttp: no script matched ${method} ${path}\n registered: [${registered.join(", ")}]` ); setImmediate(() => req.emit("error", err)); return; @@ -188,16 +119,11 @@ function fakeHttpsRequest( return req; } -// vi.mock calls must be hoisted. We install the mock at the top of every test -// file that needs it via `vi.mock("node:https", () => ...)`. The shared factory -// below is what those mocks reference. export const httpsMockModule = { default: { request: fakeHttpsRequest }, request: fakeHttpsRequest, }; -// Convenience for test files. Use at module scope ABOVE any src imports: -// vi.mock("node:https", () => httpsMockFactory()); export function httpsMockFactory() { return { default: { request: fakeHttpsRequest }, @@ -205,7 +131,6 @@ export function httpsMockFactory() { }; } -// Assertion helper: check that all scripts were consumed (no leftover mocks). export function expectAllScriptsConsumed(): void { const leftover = mockHttpState.scripts .filter((s) => !s.consumed) @@ -217,5 +142,19 @@ export function expectAllScriptsConsumed(): void { } } -// Keep `expect` imported so vitest.config types resolve consistently. +export interface CapturedLog { + level: "info" | "warn" | "error"; + msg: string; +} + +export function createLogger(): { logger: ToolLogger; logs: CapturedLog[] } { + const logs: CapturedLog[] = []; + const logger: ToolLogger = { + info: (msg: string) => logs.push({ level: "info", msg }), + warn: (msg: string) => logs.push({ level: "warn", msg }), + error: (msg: string) => logs.push({ level: "error", msg }), + }; + return { logger, logs }; +} + void expect; diff --git a/test/smoke/leadbay.live.test.ts b/packages/core/test/smoke/leadbay.live.test.ts similarity index 76% rename from test/smoke/leadbay.live.test.ts rename to packages/core/test/smoke/leadbay.live.test.ts index 176f2e6..d3d5d15 100644 --- a/test/smoke/leadbay.live.test.ts +++ b/packages/core/test/smoke/leadbay.live.test.ts @@ -1,18 +1,12 @@ /** * LIVE smoke tests against the real Leadbay API. + * Opt-in: set LEADBAY_TEST_TOKEN (and optionally LEADBAY_TEST_BASE_URL). * - * Opt-in — set LEADBAY_TEST_TOKEN (and optionally LEADBAY_TEST_BASE_URL) to run. - * Excluded from the default `npm test` via vitest.config.ts; run explicitly: - * - * npm run test:smoke - * - * Governance (important before enabling in CI): - * - Use a DEDICATED test tenant — not a production Leadbay org + * Governance: + * - Use a DEDICATED test tenant * - Use a LEAST-PRIVILEGED, READ-ONLY token - * - Smoke only hits read endpoints (/lenses, /users/me, taste profile) - * - No live login (email/password) — use token preconfigure to avoid - * session churn and audit-trail noise - * - No enrich / qualify / add-note calls from smoke — those cost credits + * - Smoke hits only read endpoints (/lenses, /users/me, taste profile) + * - No live login / enrich / qualify / add-note */ import { describe, it, expect } from "vitest"; diff --git a/test/unit/client.test.ts b/packages/core/test/unit/client.test.ts similarity index 88% rename from test/unit/client.test.ts rename to packages/core/test/unit/client.test.ts index bfd6f0b..a8c48b9 100644 --- a/test/unit/client.test.ts +++ b/packages/core/test/unit/client.test.ts @@ -6,13 +6,11 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { - createTestApi, mockHttp, resetHttpMock, httpsMockFactory, } from "../harness.js"; -// Install the node:https mock at module scope BEFORE any src imports. vi.mock("node:https", () => httpsMockFactory()); import { LeadbayClient } from "../../src/client.js"; @@ -29,8 +27,8 @@ afterEach(() => { describe("LeadbayClient.request — HTTP status → error code mapping", () => { const cases: Array<[number, string, object | string | undefined, string, string?]> = [ - [401, "AUTH_EXPIRED", { message: "expired" }, "re-authenticate"], - [402, "QUOTA_EXCEEDED", { message: "no credits" }, "Purchase more credits"], + [401, "AUTH_EXPIRED", { message: "expired" }, "regenerate"], + [402, "QUOTA_EXCEEDED", { message: "no credits" }, "credits"], [403, "BILLING_SUSPENDED", { message: "account suspended" }, "billing"], [403, "BILLING_SUSPENDED", { error: "billing_locked" }, "billing"], [403, "FORBIDDEN", { message: "forbidden" }, "permissions"], @@ -43,7 +41,7 @@ describe("LeadbayClient.request — HTTP status → error code mapping", () => { it.each(cases)( "HTTP %i → error code %s", - async (status, expectedCode, body, _expectedHint, expectedMessage) => { + async (status, expectedCode, body) => { mockHttp([{ method: "GET", path: "/1.5/lenses", status, body }]); const client = new LeadbayClient(BASE, "u.test-token"); await expect(client.request("GET", "/lenses")).rejects.toMatchObject({ @@ -211,7 +209,7 @@ describe("LeadbayClient.resolveDefaultLens", () => { ]); const client = new LeadbayClient(BASE, "u.test-token"); await expect(client.resolveDefaultLens()).resolves.toBe(1); - vi.setSystemTime(new Date("2026-04-20T00:06:00Z")); // 6 min later + vi.setSystemTime(new Date("2026-04-20T00:06:00Z")); await expect(client.resolveDefaultLens()).resolves.toBe(2); expect(requests.length).toBe(2); }); @@ -298,35 +296,6 @@ describe("LeadbayClient.resolveTasteProfile — partial-result resilience", () = expect(tp.purchaseIntentTags).toHaveLength(1); expect(tp.qualificationQuestions).toHaveLength(0); }); - - it("returns empty arrays when tags/questions reject but IBP succeeds", async () => { - mockHttp([ - { method: "GET", path: "/1.5/users/me", status: 200, body: meBody }, - { - method: "GET", - path: "/1.5/organizations/org-1/ideal_buyer_profile", - status: 200, - body: { summary: "ok" }, - }, - { - method: "GET", - path: "/1.5/organizations/org-1/purchase_intent_tags", - status: 500, - body: {}, - }, - { - method: "GET", - path: "/1.5/organizations/org-1/ai_agent_questions", - status: 500, - body: {}, - }, - ]); - const client = new LeadbayClient(BASE, "u.test-token"); - const tp = await client.resolveTasteProfile(); - expect(tp.idealBuyerProfile).toEqual({ summary: "ok" }); - expect(tp.purchaseIntentTags).toEqual([]); - expect(tp.qualificationQuestions).toEqual([]); - }); }); describe("LeadbayClient — auth state", () => { @@ -337,3 +306,29 @@ describe("LeadbayClient — auth state", () => { expect(c.isAuthenticated).toBe(true); }); }); + +describe("createClient factory", () => { + it("resolves US region to the us baseUrl", async () => { + // No network here — just verify construction + const { createClient, REGIONS } = await import("../../src/client.js"); + const c = createClient({ token: "tok", region: "us" }); + expect(c.baseUrl).toBe(REGIONS.us); + }); + + it("resolves FR region to the fr baseUrl", async () => { + const { createClient, REGIONS } = await import("../../src/client.js"); + const c = createClient({ token: "tok", region: "fr" }); + expect(c.baseUrl).toBe(REGIONS.fr); + }); + + it("throws on unknown region (no baseUrl)", async () => { + const { createClient } = await import("../../src/client.js"); + expect(() => createClient({ region: "xx" as any })).toThrow(/unknown region/); + }); + + it("custom baseUrl overrides region", async () => { + const { createClient } = await import("../../src/client.js"); + const c = createClient({ baseUrl: "https://staging.example.com", region: "us" }); + expect(c.baseUrl).toBe("https://staging.example.com"); + }); +}); diff --git a/test/unit/tools/enrich-contacts.test.ts b/packages/core/test/unit/tools/enrich-contacts.test.ts similarity index 80% rename from test/unit/tools/enrich-contacts.test.ts rename to packages/core/test/unit/tools/enrich-contacts.test.ts index ea9d6fa..5f5f3dc 100644 --- a/test/unit/tools/enrich-contacts.test.ts +++ b/packages/core/test/unit/tools/enrich-contacts.test.ts @@ -1,13 +1,11 @@ /** - * Tests for leadbay_enrich_contacts. + * Tests for leadbay_enrich_contacts (protocol-agnostic Tool shape). * Critical invariants: paid-path fallback to org, paid-success never triggers * org (no double-charge), URL literal for query-param drift detection. */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { - createTestApi, - executeTool, mockHttp, resetHttpMock, httpsMockFactory, @@ -16,7 +14,7 @@ import { vi.mock("node:https", () => httpsMockFactory()); import { LeadbayClient } from "../../../src/client.js"; -import { registerEnrichContacts } from "../../../src/tools/enrich-contacts.js"; +import { enrichContacts } from "../../../src/tools/enrich-contacts.js"; const BASE = "https://api-us.leadbay.app"; @@ -35,19 +33,15 @@ beforeEach(() => { resetHttpMock(); }); -function setup() { - const t = createTestApi({}); - const client = new LeadbayClient(BASE, "u.test-token"); - registerEnrichContacts(t.api as any, client); - return t; +function client() { + return new LeadbayClient(BASE, "u.test-token"); } describe("leadbay_enrich_contacts — validation", () => { it("both email=false and phone=false throws INVALID_PARAMS", async () => { mockHttp([]); - const t = setup(); await expect( - executeTool(t, "leadbay_enrich_contacts", { + enrichContacts.execute(client(), { leadId: "L1", contactId: "C1", email: false, @@ -62,14 +56,9 @@ describe("leadbay_enrich_contacts — quota advisory", () => { const { requests } = mockHttp([ { method: "GET", path: "/1.5/users/me", status: 200, body: meBodyZeroCredits }, ]); - const t = setup(); await expect( - executeTool(t, "leadbay_enrich_contacts", { - leadId: "L1", - contactId: "C1", - }) + enrichContacts.execute(client(), { leadId: "L1", contactId: "C1" }) ).rejects.toMatchObject({ code: "QUOTA_EXCEEDED" }); - // Only the /users/me call happened; no enrich call was made expect(requests.filter((r) => r.path.includes("/enrich"))).toHaveLength(0); }); @@ -82,13 +71,11 @@ describe("leadbay_enrich_contacts — quota advisory", () => { status: 204, }, ]); - const t = setup(); - const result: any = await executeTool(t, "leadbay_enrich_contacts", { + const result: any = await enrichContacts.execute(client(), { leadId: "L1", contactId: "C1", }); expect(result.triggered).toBe(true); - // credits_remaining is null because the advisory check failed expect(result.credits_remaining).toBeNull(); }); }); @@ -109,8 +96,7 @@ describe("leadbay_enrich_contacts — paid → org fallback", () => { status: 204, }, ]); - const t = setup(); - const result: any = await executeTool(t, "leadbay_enrich_contacts", { + const result: any = await enrichContacts.execute(client(), { leadId: "L1", contactId: "C1", }); @@ -135,15 +121,10 @@ describe("leadbay_enrich_contacts — paid → org fallback", () => { body: { message: "boom" }, }, ]); - const t = setup(); await expect( - executeTool(t, "leadbay_enrich_contacts", { - leadId: "L1", - contactId: "C1", - }) + enrichContacts.execute(client(), { leadId: "L1", contactId: "C1" }) ).rejects.toMatchObject({ code: "API_ERROR" }); - // Ensure the org-contacts fallback was NOT tried const orgFallbackCalled = requests.some((r) => /\/leads\/L1\/contacts\//.test(r.path) && /\/enrich$/.test(r.path) ); @@ -159,8 +140,7 @@ describe("leadbay_enrich_contacts — paid → org fallback", () => { status: 204, }, ]); - const t = setup(); - const result: any = await executeTool(t, "leadbay_enrich_contacts", { + const result: any = await enrichContacts.execute(client(), { leadId: "L1", contactId: "C1", }); @@ -184,8 +164,7 @@ describe("leadbay_enrich_contacts — URL + response shape", () => { status: 204, }, ]); - const t = setup(); - await executeTool(t, "leadbay_enrich_contacts", { + await enrichContacts.execute(client(), { leadId: "L1", contactId: "C1", email: true, @@ -208,8 +187,7 @@ describe("leadbay_enrich_contacts — URL + response shape", () => { status: 204, }, ]); - const t = setup(); - const result: any = await executeTool(t, "leadbay_enrich_contacts", { + const result: any = await enrichContacts.execute(client(), { leadId: "L1", contactId: "C1", }); diff --git a/test/unit/tools/get-lead-profile.test.ts b/packages/core/test/unit/tools/get-lead-profile.test.ts similarity index 87% rename from test/unit/tools/get-lead-profile.test.ts rename to packages/core/test/unit/tools/get-lead-profile.test.ts index 9d11879..efa2ff7 100644 --- a/test/unit/tools/get-lead-profile.test.ts +++ b/packages/core/test/unit/tools/get-lead-profile.test.ts @@ -1,13 +1,11 @@ /** - * Tests for leadbay_get_lead_profile. + * Tests for leadbay_get_lead_profile (protocol-agnostic Tool shape). * Critical invariants: lead-fetch failure is fatal; the other four sub-requests * degrade to partial results; contact merge tags source correctly. */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { - createTestApi, - executeTool, mockHttp, resetHttpMock, httpsMockFactory, @@ -16,7 +14,7 @@ import { vi.mock("node:https", () => httpsMockFactory()); import { LeadbayClient } from "../../../src/client.js"; -import { registerGetLeadProfile } from "../../../src/tools/get-lead-profile.js"; +import { getLeadProfile } from "../../../src/tools/get-lead-profile.js"; const BASE = "https://api-us.leadbay.app"; @@ -49,11 +47,8 @@ beforeEach(() => { resetHttpMock(); }); -function setup() { - const t = createTestApi({}); - const client = new LeadbayClient(BASE, "u.test-token"); - registerGetLeadProfile(t.api as any, client); - return t; +function client() { + return new LeadbayClient(BASE, "u.test-token"); } describe("leadbay_get_lead_profile — success path", () => { @@ -116,8 +111,7 @@ describe("leadbay_get_lead_profile — success path", () => { }, }, ]); - const t = setup(); - const result: any = await executeTool(t, "leadbay_get_lead_profile", { + const result: any = await getLeadProfile.execute(client(), { leadId: LEAD, lensId: LENS, }); @@ -167,9 +161,8 @@ describe("leadbay_get_lead_profile — partial-result resilience", () => { it("lead fetch rejects (fatal) → throws", async () => { mockHttp(baseScripts(404, { message: "no lead" }) as any); - const t = setup(); await expect( - executeTool(t, "leadbay_get_lead_profile", { leadId: LEAD, lensId: LENS }) + getLeadProfile.execute(client(), { leadId: LEAD, lensId: LENS }) ).rejects.toMatchObject({ code: "NOT_FOUND" }); }); @@ -182,8 +175,7 @@ describe("leadbay_get_lead_profile — partial-result resilience", () => { body: {} as any, }; mockHttp(scripts as any); - const t = setup(); - const result: any = await executeTool(t, "leadbay_get_lead_profile", { + const result: any = await getLeadProfile.execute(client(), { leadId: LEAD, lensId: LENS, }); @@ -206,8 +198,7 @@ describe("leadbay_get_lead_profile — partial-result resilience", () => { body: {} as any, }; mockHttp(scripts as any); - const t = setup(); - const result: any = await executeTool(t, "leadbay_get_lead_profile", { + const result: any = await getLeadProfile.execute(client(), { leadId: LEAD, lensId: LENS, }); @@ -223,8 +214,7 @@ describe("leadbay_get_lead_profile — partial-result resilience", () => { body: {} as any, }; mockHttp(scripts as any); - const t = setup(); - const result: any = await executeTool(t, "leadbay_get_lead_profile", { + const result: any = await getLeadProfile.execute(client(), { leadId: LEAD, lensId: LENS, }); @@ -256,8 +246,7 @@ describe("leadbay_get_lead_profile — contact source tagging", () => { body: { content: null, fetch_at: null }, }, ]); - const t = setup(); - const result: any = await executeTool(t, "leadbay_get_lead_profile", { + const result: any = await getLeadProfile.execute(client(), { leadId: LEAD, lensId: LENS, }); diff --git a/test/unit/tools/login.test.ts b/packages/core/test/unit/tools/login.test.ts similarity index 67% rename from test/unit/tools/login.test.ts rename to packages/core/test/unit/tools/login.test.ts index c256e0c..ef766e1 100644 --- a/test/unit/tools/login.test.ts +++ b/packages/core/test/unit/tools/login.test.ts @@ -1,21 +1,19 @@ /** - * Tests for the login tool. Covers the password-unescape regex — an easy-to-miss - * subtlety, since some LLMs backslash-escape special chars in tool call JSON. + * Tests for the login tool (protocol-agnostic Tool shape). */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { - createTestApi, - executeTool, mockHttp, resetHttpMock, httpsMockFactory, + createLogger, } from "../../harness.js"; vi.mock("node:https", () => httpsMockFactory()); import { LeadbayClient } from "../../../src/client.js"; -import { registerLogin } from "../../../src/tools/login.js"; +import { login } from "../../../src/tools/login.js"; const BASE = "https://api-us.leadbay.app"; @@ -48,17 +46,16 @@ describe("leadbay_login — password unescape", () => { status: 200, body: { token: "u.new-token" }, }, - // allow the prefetchOrgData fire-and-forget to 404 quietly { method: "GET", path: /\/1\.5\/users\/me/, status: 404, body: {} }, ]); - const t = createTestApi({}); const client = new LeadbayClient(BASE); - registerLogin(t.api as any, client); + const { logger } = createLogger(); - await executeTool(t, "leadbay_login", { - email: "a@b.com", - password: input, - }); + await login.execute( + client, + { email: "a@b.com", password: input }, + { logger } + ); const loginReq = requests.find((r) => r.path === "/1.5/auth/login"); expect(loginReq).toBeDefined(); @@ -79,14 +76,14 @@ describe("leadbay_login — status path handling", () => { }, { method: "GET", path: /users\/me/, status: 404, body: {} }, ]); - const t = createTestApi({}); const client = new LeadbayClient(BASE); - registerLogin(t.api as any, client); + const { logger } = createLogger(); - const result: any = await executeTool(t, "leadbay_login", { - email: "a@b.com", - password: "secret", - }); + const result: any = await login.execute( + client, + { email: "a@b.com", password: "secret" }, + { logger } + ); expect(result).toEqual({ success: true, @@ -104,14 +101,14 @@ describe("leadbay_login — status path handling", () => { body: { message: "bad credentials" }, }, ]); - const t = createTestApi({}); const client = new LeadbayClient(BASE); - registerLogin(t.api as any, client); + const { logger } = createLogger(); - const result: any = await executeTool(t, "leadbay_login", { - email: "a@b.com", - password: "wrong", - }); + const result: any = await login.execute( + client, + { email: "a@b.com", password: "wrong" }, + { logger } + ); expect(result).toMatchObject({ error: true, @@ -130,14 +127,14 @@ describe("leadbay_login — status path handling", () => { error: new Error("ECONNREFUSED"), }, ]); - const t = createTestApi({}); const client = new LeadbayClient(BASE); - registerLogin(t.api as any, client); + const { logger } = createLogger(); - const result: any = await executeTool(t, "leadbay_login", { - email: "a@b.com", - password: "x", - }); + const result: any = await login.execute( + client, + { email: "a@b.com", password: "x" }, + { logger } + ); expect(result).toMatchObject({ error: true, @@ -154,20 +151,40 @@ describe("leadbay_login — status path handling", () => { status: 200, body: { token: "u.abc" }, }, - // both prefetch paths fail — login must still resolve cleanly { method: "GET", path: /users\/me/, status: 500, body: {} }, ]); - const t = createTestApi({}); const client = new LeadbayClient(BASE); - registerLogin(t.api as any, client); + const { logger } = createLogger(); - const result: any = await executeTool(t, "leadbay_login", { - email: "a@b.com", - password: "x", - }); + const result: any = await login.execute( + client, + { email: "a@b.com", password: "x" }, + { logger } + ); expect(result.success).toBe(true); - // And no unhandled rejection bubbles up — waiting a tick confirms await new Promise((r) => setImmediate(r)); }); + + it("logger.info and logger.error are invoked with descriptive messages", async () => { + mockHttp([ + { + method: "POST", + path: "/1.5/auth/login", + status: 401, + body: { message: "bad" }, + }, + ]); + const client = new LeadbayClient(BASE); + const { logger, logs } = createLogger(); + + await login.execute( + client, + { email: "a@b.com", password: "wrong" }, + { logger } + ); + + expect(logs.some((l) => l.level === "info" && /login: email=/.test(l.msg))).toBe(true); + expect(logs.some((l) => l.level === "error")).toBe(true); + }); }); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..a013e0c --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"] +} diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 0000000..60da776 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["test/**/*.test.ts"], + exclude: ["test/smoke/**", "node_modules", "dist"], + }, +}); diff --git a/vitest.smoke.config.ts b/packages/core/vitest.smoke.config.ts similarity index 55% rename from vitest.smoke.config.ts rename to packages/core/vitest.smoke.config.ts index 6144859..2a0e430 100644 --- a/vitest.smoke.config.ts +++ b/packages/core/vitest.smoke.config.ts @@ -1,7 +1,5 @@ import { defineConfig } from "vitest/config"; -// Smoke-only config — used by `npm run test:smoke`. These tests hit the live -// Leadbay API and are opt-in (gated by LEADBAY_TEST_TOKEN env var). export default defineConfig({ test: { environment: "node", diff --git a/packages/leadclaw/LICENSE b/packages/leadclaw/LICENSE new file mode 100644 index 0000000..e896f69 --- /dev/null +++ b/packages/leadclaw/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Leadbay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/leadclaw/logo.png b/packages/leadclaw/logo.png new file mode 100644 index 0000000..1cef97a Binary files /dev/null and b/packages/leadclaw/logo.png differ diff --git a/openclaw.plugin.json b/packages/leadclaw/openclaw.plugin.json similarity index 100% rename from openclaw.plugin.json rename to packages/leadclaw/openclaw.plugin.json diff --git a/packages/leadclaw/package.json b/packages/leadclaw/package.json new file mode 100644 index 0000000..c06a487 --- /dev/null +++ b/packages/leadclaw/package.json @@ -0,0 +1,43 @@ +{ + "name": "@leadbay/leadclaw", + "version": "0.1.0", + "description": "OpenClaw plugin for Leadbay — AI lead discovery, qualification, and enrichment", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/", + "openclaw.plugin.json", + "README.md", + "LICENSE", + "logo.png" + ], + "openclaw": { + "extensions": [ + "./dist/index.js" + ], + "install": { + "npmSpec": "@leadbay/leadclaw" + }, + "compat": { + "pluginApi": ">=2026.3.24-beta.2", + "minGatewayVersion": "2026.3.24-beta.2" + }, + "build": { + "openclawVersion": "2026.3.24-beta.2", + "pluginSdkVersion": "2026.3.24-beta.2" + } + }, + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit", + "test": "vitest run" + }, + "devDependencies": { + "@leadbay/core": "workspace:*" + }, + "engines": { + "node": ">=22" + }, + "license": "MIT" +} diff --git a/packages/leadclaw/src/index.ts b/packages/leadclaw/src/index.ts new file mode 100644 index 0000000..e201571 --- /dev/null +++ b/packages/leadclaw/src/index.ts @@ -0,0 +1,40 @@ +import { createClient, granularTools } from "@leadbay/core"; + +// OpenClaw plugin entry point. +// +// The OpenClaw adapter exposes the 11 granular tools (matching the published +// plugin manifest). Composite workflow tools live in @leadbay/mcp only. + +export function register(api: any) { + const cfg = api.pluginConfig ?? {}; + + const region = (cfg.region ?? "us") as "us" | "fr"; + let client; + try { + client = createClient({ + token: cfg.token, + region, + baseUrl: cfg.baseUrl, + }); + } catch (err: any) { + api.logger?.warn?.( + `LeadClaw: ${err?.message ?? "Missing region config"}. Set it via: openclaw config set plugins.entries.leadclaw.region "us"` + ); + return; + } + + if (cfg.token) { + api.logger?.info?.("LeadClaw: Using preconfigured auth token"); + } + + for (const tool of granularTools) { + api.registerTool({ + name: tool.name, + description: tool.description, + parameters: tool.inputSchema, + ...(tool.optional ? { optional: true } : {}), + execute: async (_id: string, params: unknown) => + tool.execute(client, params as any, { logger: api.logger }), + }); + } +} diff --git a/test/contract.test.ts b/packages/leadclaw/test/contract.test.ts similarity index 80% rename from test/contract.test.ts rename to packages/leadclaw/test/contract.test.ts index e08b572..06269b1 100644 --- a/test/contract.test.ts +++ b/packages/leadclaw/test/contract.test.ts @@ -1,9 +1,8 @@ /** - * Contract tests — the single most valuable tests in the suite. + * Contract tests for @leadbay/leadclaw — manifest ↔ code parity. * - * Enforces manifest ↔ code parity. When a tool is added in src/ or removed - * from openclaw.plugin.json, these fail with a named-diff error. No magic - * tool counts. + * When a tool is added to @leadbay/core or removed from openclaw.plugin.json, + * these fail with a named-diff error. No magic tool counts. */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; @@ -37,9 +36,7 @@ describe("contract: manifest ↔ code parity", () => { if (added.length || missing.length) { throw new Error( `Manifest drift.\n` + - ` registered in code but MISSING from openclaw.plugin.json: [${added.join( - ", " - )}]\n` + + ` registered in code but MISSING from openclaw.plugin.json: [${added.join(", ")}]\n` + ` declared in openclaw.plugin.json but NOT registered: [${missing.join(", ")}]\n` + `Fix: either update openclaw.plugin.json contracts.tools or delete the unregistered tool.` ); @@ -82,6 +79,13 @@ describe("contract: manifest ↔ code parity", () => { expect((tool.description as string).length, `${name}.description length`).toBeGreaterThan(10); } }); + + it("manifest has expected top-level shape", () => { + expect(manifest.id).toBe("leadclaw"); + expect(manifest.configSchema).toBeTypeOf("object"); + expect(Array.isArray(manifest.contracts.tools)).toBe(true); + expect(manifest.contracts.tools.length).toBe(11); + }); }); describe("contract: register() behaviour", () => { @@ -93,8 +97,8 @@ describe("contract: register() behaviour", () => { vi.restoreAllMocks(); }); - it("emits logger.warn and registers no tools when region+baseUrl missing", () => { - const t = createTestApi({ region: "xx" }); // invalid region → no baseUrl lookup + it("emits logger.warn and registers no tools when region is invalid", () => { + const t = createTestApi({ region: "xx" }); register(t.api as any); expect(t.tools.size).toBe(0); expect(t.logs.some((l) => l.level === "warn" && /region/i.test(l.msg))).toBe(true); @@ -106,23 +110,12 @@ describe("contract: register() behaviour", () => { expect(t.tools.size).toBe(11); }); - it("calls client.setToken and logs info when cfg.token is provided", async () => { - const clientModule = await import("../src/client.js"); - const spy = vi.spyOn(clientModule.LeadbayClient.prototype, "setToken"); - + it("logs info when cfg.token is provided (preconfigured)", async () => { const t = createTestApi({ region: "us", token: "u.preconfig-token" }); register(t.api as any); - expect(spy).toHaveBeenCalledWith("u.preconfig-token"); expect(t.logs.some((l) => l.level === "info" && /preconfigured/i.test(l.msg))).toBe( true ); }); - - it("manifest contracts.tools matches the openclaw.plugin.json schema", () => { - // Sanity: manifest is a valid JSON schema shape - expect(manifest.id).toBe("leadclaw"); - expect(manifest.configSchema).toBeTypeOf("object"); - expect(Array.isArray(manifest.contracts.tools)).toBe(true); - }); }); diff --git a/packages/leadclaw/test/harness.ts b/packages/leadclaw/test/harness.ts new file mode 100644 index 0000000..72f3455 --- /dev/null +++ b/packages/leadclaw/test/harness.ts @@ -0,0 +1,46 @@ +/** + * Test harness for the OpenClaw adapter. + * + * Unlike the protocol-agnostic core tests, adapter tests need to verify the + * OpenClaw-shaped API surface: how `register(api)` invokes `api.registerTool`, + * how logs/token-preconfigure behavior are observed, etc. + */ + +export interface RegisteredTool { + name: string; + description: string; + parameters: unknown; + optional?: boolean; + execute: (id: string, params: unknown) => unknown | Promise; +} + +export interface TestApi { + api: { + pluginConfig: Record; + logger: { + info: (msg: string) => void; + warn: (msg: string) => void; + error: (msg: string) => void; + }; + registerTool: (tool: RegisteredTool) => void; + }; + tools: Map; + logs: { level: "info" | "warn" | "error"; msg: string }[]; +} + +export function createTestApi(pluginConfig: Record = {}): TestApi { + const tools = new Map(); + const logs: { level: "info" | "warn" | "error"; msg: string }[] = []; + const api = { + pluginConfig, + logger: { + info: (msg: string) => logs.push({ level: "info", msg }), + warn: (msg: string) => logs.push({ level: "warn", msg }), + error: (msg: string) => logs.push({ level: "error", msg }), + }, + registerTool: (tool: RegisteredTool) => { + tools.set(tool.name, tool); + }, + }; + return { api, tools, logs }; +} diff --git a/packages/leadclaw/tsconfig.json b/packages/leadclaw/tsconfig.json new file mode 100644 index 0000000..a013e0c --- /dev/null +++ b/packages/leadclaw/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"] +} diff --git a/packages/leadclaw/tsup.config.ts b/packages/leadclaw/tsup.config.ts new file mode 100644 index 0000000..e7e9f38 --- /dev/null +++ b/packages/leadclaw/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup"; + +// @leadbay/core is a private workspace package; bundle it into dist/index.js +// so that the published tarball is self-contained (no workspace:* dep survives). +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm"], + dts: true, + outDir: "dist", + clean: true, + noExternal: ["@leadbay/core"], + target: "es2022", +}); diff --git a/packages/leadclaw/vitest.config.ts b/packages/leadclaw/vitest.config.ts new file mode 100644 index 0000000..fa6a932 --- /dev/null +++ b/packages/leadclaw/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["test/**/*.test.ts"], + exclude: ["node_modules", "dist"], + }, +}); diff --git a/packages/mcp/LICENSE b/packages/mcp/LICENSE new file mode 100644 index 0000000..e896f69 --- /dev/null +++ b/packages/mcp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Leadbay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/mcp/README.md b/packages/mcp/README.md new file mode 100644 index 0000000..b177953 --- /dev/null +++ b/packages/mcp/README.md @@ -0,0 +1,141 @@ +# @leadbay/mcp — Leadbay MCP server + +A Model Context Protocol server that lets Claude Desktop, Cursor, Claude Code, and any other MCP-compatible agent find, research, and prepare outreach on B2B prospects using your Leadbay account. + +## 1. Get a token + +Log in at [app.leadbay.ai](https://app.leadbay.ai). Go to **Settings → API Tokens** and create a new token. Copy it — you'll paste it into your MCP client config below. + +If you don't have a Leadbay account yet, [register here](https://wow.leadbay.ai/?register=true). + +## 2. Quickstart + +### Claude Desktop + +Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows): + +```json +{ + "mcpServers": { + "leadbay": { + "command": "npx", + "args": ["-y", "@leadbay/mcp@0.1"], + "env": { + "LEADBAY_TOKEN": "lb_...", + "LEADBAY_REGION": "us" + } + } + } +} +``` + +Restart Claude Desktop. + +### Cursor + +In Cursor settings, add the MCP server: + +```json +{ + "mcp.servers": { + "leadbay": { + "command": "npx", + "args": ["-y", "@leadbay/mcp@0.1"], + "env": { "LEADBAY_TOKEN": "lb_...", "LEADBAY_REGION": "us" } + } + } +} +``` + +### Claude Code + +```bash +claude mcp add leadbay \ + --env LEADBAY_TOKEN=lb_... \ + --env LEADBAY_REGION=us \ + -- npx -y @leadbay/mcp@0.1 +``` + +### Verify it works + +Before starting Claude, run: + +```bash +LEADBAY_TOKEN=lb_... npx -y @leadbay/mcp@0.1 doctor +``` + +Expected output: + +``` +Leadbay connection OK. + Organization: Your Org + Billing: active + AI credits: 420 / 1000 +``` + +## 3. Example prompts that work + +> *Find me 20 SaaS companies in Berlin that match my Ideal Buyer Profile.* + +> *Research the top prospect from that list — give me the AI summary, recent activity, and who I should reach out to.* + +> *Prepare an outreach package for Acme Corp — include the recommended contact with enriched email if we have credits.* + +## 4. Troubleshooting + +| Problem | Cause | Fix | +|---------|-------|-----| +| `LEADBAY_TOKEN environment variable is required` | Token missing from config env | Add `LEADBAY_TOKEN` to the `env` block, restart client | +| `Authentication token expired or invalid` | Token revoked or wrong region | Re-generate token at [app.leadbay.ai/settings/api-tokens](https://app.leadbay.ai/settings/api-tokens); verify `LEADBAY_REGION` | +| `Leadbay doctor: could not reach any Leadbay region` | Wrong region OR network blocked | Run `doctor` with `LEADBAY_REGION=fr` to auto-probe. Check `https://api-us.leadbay.app` reachable. | +| `No enrichment credits remaining` | Out of quota | Buy credits at [app.leadbay.ai](https://app.leadbay.ai) | +| Claude Desktop "loading forever" on first use | `npx` cold-start fetching the package | First run takes ~10s. Prefer `npm install -g @leadbay/mcp` for faster startup. | +| Claude Desktop doesn't show Leadbay tools | Server crashed at startup | Check `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\mcp*.log` (Windows). | + +## 5. Upgrade & rotation + +**Upgrade**: change the pinned minor in your config, e.g. `"@leadbay/mcp@0.1"` → `"@leadbay/mcp@0.2"`, then restart the client. See the [changelog](https://github.com/leadbay/leadclaw/releases). + +**Rotate token**: delete the old token at [app.leadbay.ai/settings/api-tokens](https://app.leadbay.ai/settings/api-tokens), create a new one, update `LEADBAY_TOKEN` in your MCP client config, restart. + +## 6. Advanced + +### Exposing the 10 granular tools + +By default the server exposes 3 **composite workflow tools** (`leadbay_find_prospects`, `leadbay_research_company`, `leadbay_prepare_outreach`). These compose the underlying Leadbay API and work well with most prompts. + +If you'd rather give the LLM direct access to the 10 endpoint-level tools (`leadbay_list_lenses`, `leadbay_discover_leads`, `leadbay_get_lead_profile`, `leadbay_get_contacts`, `leadbay_get_quota`, `leadbay_get_taste_profile`, `leadbay_qualify_lead`, `leadbay_enrich_contacts`, `leadbay_add_note`, `leadbay_get_lead_activities`), set `LEADBAY_MCP_ADVANCED=1`: + +```json +"env": { + "LEADBAY_TOKEN": "lb_...", + "LEADBAY_MCP_ADVANCED": "1" +} +``` + +**Note**: `leadbay_login` is intentionally not exposed over MCP — see [Security](#security) below. + +### Environment variables + +| Var | Required | Default | Purpose | +|-----|----------|---------|---------| +| `LEADBAY_TOKEN` | yes | — | Bearer token | +| `LEADBAY_REGION` | no | `us` | `us` or `fr` | +| `LEADBAY_BASE_URL` | no | derived from region | Override for staging/dev | +| `LEADBAY_MCP_ADVANCED` | no | unset | `"1"` exposes the 10 granular endpoint tools | +| `LEADBAY_LOG_LEVEL` | no | `error` | `debug` \| `info` \| `error`, logs to stderr | +| `LEADBAY_TIMEOUT_MS` | no | (client default) | Per-request timeout override | + +### Security + +- Tokens live only in your MCP client's config file — they never traverse the network except to `api-{region}.leadbay.app`. +- The `leadbay_login` tool from the OpenClaw adapter is **not** registered on MCP: exposing a credential-taking tool to an LLM is a prompt-injection risk. Use the token path above. +- The `leadbay_add_note` and `leadbay_enrich_contacts` tools are write actions flagged `optional: true`. If your client supports per-tool opt-in, leave them disabled until you need them. + +### Privacy + +Contact data fetched through this server stays local to your MCP client session. No analytics or telemetry is sent by `@leadbay/mcp`. Requests to Leadbay are subject to the [Leadbay privacy policy](https://leadbay.ai/privacy). + +## License + +MIT. See [LICENSE](./LICENSE). diff --git a/packages/mcp/package.json b/packages/mcp/package.json new file mode 100644 index 0000000..22b91db --- /dev/null +++ b/packages/mcp/package.json @@ -0,0 +1,40 @@ +{ + "name": "@leadbay/mcp", + "version": "0.1.0", + "description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.", + "type": "module", + "bin": { + "leadbay-mcp": "dist/bin.js" + }, + "files": [ + "dist/", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:smoke": "vitest run --config vitest.smoke.config.ts" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.29.0" + }, + "devDependencies": { + "@leadbay/core": "workspace:*" + }, + "engines": { + "node": ">=22" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "leadbay", + "lead-generation", + "b2b", + "sales", + "claude-desktop", + "cursor" + ], + "license": "MIT" +} diff --git a/packages/mcp/src/bin.ts b/packages/mcp/src/bin.ts new file mode 100644 index 0000000..1b4e68f --- /dev/null +++ b/packages/mcp/src/bin.ts @@ -0,0 +1,238 @@ +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createClient, LeadbayClient, type CreateClientConfig, type ToolLogger } from "@leadbay/core"; +import { buildServer } from "./server.js"; + +const VERSION = "0.1.0"; + +const HELP = ` +leadbay-mcp ${VERSION} — Leadbay Model Context Protocol server + +USAGE + leadbay-mcp Run the MCP stdio server (for Claude Desktop, Cursor, etc.) + leadbay-mcp doctor Validate your token, probe your region, print account + quota. + leadbay-mcp --version Print version + leadbay-mcp --help Print this help + +ENV VARS + LEADBAY_TOKEN (required) Bearer token from https://app.leadbay.ai/settings/api-tokens + LEADBAY_REGION (optional) "us" or "fr". Auto-detected from /users/me if unset. + LEADBAY_BASE_URL (optional) Override API base URL (for staging/dev). + LEADBAY_MCP_ADVANCED (optional) Set to "1" to expose 10 granular tools alongside + the 3 composite workflow tools. Most users don't need this. + LEADBAY_LOG_LEVEL (optional) "debug" | "info" | "error" (default "error"). Logs to stderr. + LEADBAY_TIMEOUT_MS (optional) Per-request timeout override (not yet plumbed). + +EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json) + { + "mcpServers": { + "leadbay": { + "command": "npx", + "args": ["-y", "@leadbay/mcp@0.1"], + "env": { + "LEADBAY_TOKEN": "lb_...", + "LEADBAY_REGION": "us" + } + } + } + } + +DOCS + https://github.com/leadbay/leadclaw#readme +`.trim(); + +type LogLevel = "debug" | "info" | "error"; +function makeStderrLogger(level: LogLevel): ToolLogger { + const rank: Record = { debug: 0, info: 1, error: 2 }; + const threshold = rank[level] ?? rank.error; + return { + info: (m: string) => { + if (rank.info >= threshold) process.stderr.write(`[leadbay-mcp info] ${m}\n`); + }, + warn: (m: string) => { + if (rank.info >= threshold) process.stderr.write(`[leadbay-mcp warn] ${m}\n`); + }, + error: (m: string) => { + process.stderr.write(`[leadbay-mcp error] ${m}\n`); + }, + }; +} + +function parseLogLevel(raw: string | undefined): LogLevel { + if (raw === "debug" || raw === "info") return raw; + return "error"; +} + +function exitWithTokenError(): never { + process.stderr.write( + "leadbay-mcp: LEADBAY_TOKEN environment variable is required.\n" + + " 1. Create a token at https://app.leadbay.ai/settings/api-tokens\n" + + " 2. Set it in your MCP client config (e.g. claude_desktop_config.json).\n" + + "\n" + + "Run `leadbay-mcp --help` for the full config template.\n" + ); + process.exit(1); +} + +export async function resolveClientFromEnv(logger: ToolLogger): Promise { + const token = process.env.LEADBAY_TOKEN; + if (!token) exitWithTokenError(); + + const regionEnv = process.env.LEADBAY_REGION; + const explicitRegion: "us" | "fr" | undefined = + regionEnv === "us" || regionEnv === "fr" ? regionEnv : undefined; + const baseUrl = process.env.LEADBAY_BASE_URL; + + // If the user pinned a baseUrl or region, honor it exactly. + if (baseUrl || explicitRegion) { + const config: CreateClientConfig = { token }; + if (baseUrl) config.baseUrl = baseUrl; + if (explicitRegion) config.region = explicitRegion; + return createClient(config); + } + + // Otherwise probe both regions in parallel and pick whichever /users/me + // resolves first. This keeps us-region users on fast path while letting + // fr users work without setting LEADBAY_REGION. + logger.info?.("Auto-detecting region via /users/me on us and fr..."); + const probe = async (region: "us" | "fr"): Promise => { + const c = createClient({ token, region }); + await c.request("GET", "/users/me"); + return c; + }; + + try { + return await Promise.any([probe("us"), probe("fr")]); + } catch (err: any) { + // Both failed. The AggregateError exposes each leaf error. + const errors: any[] = err?.errors ?? []; + const firstAuth = errors.find( + (e) => e?.code === "AUTH_EXPIRED" || e?.code === "NOT_AUTHENTICATED" + ); + if (firstAuth) { + process.stderr.write( + `leadbay-mcp: ${firstAuth.message}. ${firstAuth.hint}\n` + + "Tip: verify your LEADBAY_TOKEN is valid and, if you know your region, set LEADBAY_REGION=us or LEADBAY_REGION=fr.\n" + ); + process.exit(1); + } + // Non-auth failures (network, DNS, etc.) — fall back to us so the + // server can still start and surface the error on first tool call. + const firstMsg = errors[0]?.message ?? String(err); + process.stderr.write( + `leadbay-mcp: region auto-detection failed (${firstMsg}). Defaulting to us; set LEADBAY_REGION to skip probing.\n` + ); + return createClient({ token, region: "us" }); + } +} + +async function runDoctor(): Promise { + const token = process.env.LEADBAY_TOKEN; + if (!token) { + exitWithTokenError(); + } + + const logger = makeStderrLogger(parseLogLevel(process.env.LEADBAY_LOG_LEVEL)); + const regionEnv = process.env.LEADBAY_REGION; + const baseUrl = process.env.LEADBAY_BASE_URL; + + const regions: Array<"us" | "fr"> = regionEnv === "fr" + ? ["fr", "us"] + : regionEnv === "us" + ? ["us", "fr"] + : ["us", "fr"]; + + for (const region of regions) { + const config: CreateClientConfig = { token }; + if (baseUrl) config.baseUrl = baseUrl; + else config.region = region; + const client = createClient(config); + logger.info?.(`Trying region="${region}" baseUrl="${client.baseUrl}"`); + try { + const me = await client.request<{ + id: string; + organization: { + id: string; + name: string; + billing?: { + status: string; + ai_credits: number | null; + ai_credits_quota: number | null; + } | null; + }; + }>("GET", "/users/me"); + process.stdout.write( + `Leadbay connection OK.\n` + + ` Region: ${baseUrl ? "(custom baseUrl)" : region}\n` + + ` Base URL: ${client.baseUrl}\n` + + ` Organization: ${me.organization.name} (${me.organization.id})\n` + + ` Billing: ${me.organization.billing?.status ?? "unknown"}\n` + + ` AI credits: ${ + me.organization.billing?.ai_credits ?? "?" + } / ${me.organization.billing?.ai_credits_quota ?? "?"}\n` + ); + return 0; + } catch (err: any) { + logger.error?.(`${region}: ${err?.message ?? err}`); + if (err?.code === "AUTH_EXPIRED" || err?.code === "NOT_AUTHENTICATED") { + process.stderr.write( + `Leadbay: your LEADBAY_TOKEN is not valid for ${region}. ${err.hint}\n` + ); + return 1; + } + // fall through and try next region + } + if (baseUrl) break; // custom baseUrl — don't try other regions + } + process.stderr.write( + "Leadbay doctor: could not reach any Leadbay region with this token. Check the token and your network.\n" + ); + return 1; +} + +async function main(): Promise { + const arg = process.argv[2]; + + if (arg === "--version" || arg === "-v") { + process.stdout.write(`${VERSION}\n`); + return; + } + if (arg === "--help" || arg === "-h" || arg === "help") { + process.stdout.write(`${HELP}\n`); + return; + } + if (arg === "doctor") { + process.exit(await runDoctor()); + } + + // Stdio MCP mode (default). + const logger = makeStderrLogger(parseLogLevel(process.env.LEADBAY_LOG_LEVEL)); + const client = await resolveClientFromEnv(logger); + const includeAdvanced = process.env.LEADBAY_MCP_ADVANCED === "1"; + + const server = buildServer(client, { includeAdvanced, logger }); + const transport = new StdioServerTransport(); + logger.info?.( + `Starting MCP server (advanced=${includeAdvanced}, baseUrl=${client.baseUrl})` + ); + await server.connect(transport); +} + +// Only run main() when invoked as a CLI, not when imported by tests. +// import.meta.url === file:// ish — compare by resolved path. +const isEntrypoint = (() => { + try { + const entry = process.argv[1]; + if (!entry) return false; + const url = new URL(import.meta.url); + return url.pathname === entry || url.pathname.endsWith(entry); + } catch { + return false; + } +})(); + +if (isEntrypoint) { + main().catch((err) => { + process.stderr.write(`leadbay-mcp: ${err?.message ?? err}\n`); + process.exit(1); + }); +} diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts new file mode 100644 index 0000000..93957f9 --- /dev/null +++ b/packages/mcp/src/server.ts @@ -0,0 +1,123 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { + compositeTools, + granularTools, + type LeadbayClient, + type Tool, + type ToolLogger, +} from "@leadbay/core"; + +export const SERVER_INSTRUCTIONS = + "Leadbay is a B2B lead-gen platform. Use these tools to find prospects, " + + "research companies, and prepare outreach based on the user's Ideal Buyer Profile.\n\n" + + "Recommended flow:\n" + + "1. leadbay_find_prospects — discovery (returns scored leads)\n" + + "2. leadbay_research_company — deep-dive on a lead (profile + contacts + activity)\n" + + "3. leadbay_prepare_outreach — assemble a contact package for the recommended contact\n\n" + + "The 11 granular tools (leadbay_list_lenses, leadbay_discover_leads, " + + "leadbay_get_lead_profile, etc.) map 1:1 to Leadbay API endpoints and are " + + "available if LEADBAY_MCP_ADVANCED=1 is set. Most tasks do not need them."; + +interface BuildServerOptions { + includeAdvanced?: boolean; + logger?: ToolLogger; +} + +function formatErrorForLLM(err: any): string { + // LeadbayError shape: { error: true, code, message, hint } + if (err && typeof err === "object" && err.error === true) { + return `${err.message}. ${err.hint}`.trim(); + } + if (err instanceof Error) { + return err.message; + } + return String(err); +} + +function toolsListPayload(tools: Tool[]) { + return tools.map((t) => ({ + name: t.name, + description: t.description, + inputSchema: t.inputSchema, + })); +} + +export function buildServer( + client: LeadbayClient, + opts: BuildServerOptions = {} +): Server { + const server = new Server( + { name: "leadbay", version: "0.1.0" }, + { + capabilities: { tools: {} }, + instructions: SERVER_INSTRUCTIONS, + } + ); + + const exposedTools: Tool[] = opts.includeAdvanced + ? [...compositeTools, ...granularTools.filter((t) => t.name !== "leadbay_login")] + : [...compositeTools]; + + // UC-3: leadbay_login is NEVER registered on MCP (prompt-injection vector). + // It remains available only in the OpenClaw adapter. + + const toolByName = new Map(exposedTools.map((t) => [t.name, t])); + + server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: toolsListPayload(exposedTools), + })); + + server.setRequestHandler(CallToolRequestSchema, async (req) => { + const name = req.params.name; + const tool = toolByName.get(name); + if (!tool) { + return { + content: [ + { + type: "text", + text: `Unknown Leadbay tool: ${name}. Available: ${[...toolByName.keys()].join(", ")}.`, + }, + ], + isError: true, + }; + } + + const args = (req.params.arguments ?? {}) as any; + try { + const result = await tool.execute(client, args, { logger: opts.logger }); + // Leadbay tools may return error envelopes ({ error: true, code, ... }) + // rather than throwing. Surface those as MCP isError so the LLM doesn't + // treat them as success. + if ( + result && + typeof result === "object" && + (result as any).error === true + ) { + return { + content: [ + { type: "text", text: formatErrorForLLM(result) }, + ], + isError: true, + }; + } + return { + content: [ + { type: "text", text: JSON.stringify(result, null, 2) }, + ], + }; + } catch (err: any) { + return { + content: [ + { type: "text", text: formatErrorForLLM(err) }, + ], + isError: true, + }; + } + }); + + return server; +} diff --git a/packages/mcp/test/concurrency.test.ts b/packages/mcp/test/concurrency.test.ts new file mode 100644 index 0000000..c2bad74 --- /dev/null +++ b/packages/mcp/test/concurrency.test.ts @@ -0,0 +1,106 @@ +/** + * Concurrency test for the MCP server — catches the "semaphore leak across + * concurrent tool calls" class of bugs flagged in the Eng review. + * + * Dispatches 10 concurrent tools/call requests through an InMemoryTransport, + * asserts: + * - all resolve + * - the LeadbayClient semaphore returns to 0 (no leaked slots, no stuck queue) + */ + +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { mockHttp, resetHttpMock, httpsMockFactory } from "./harness.js"; + +vi.mock("node:https", () => httpsMockFactory()); + +import { LeadbayClient } from "@leadbay/core"; +import { buildServer } from "../src/server.js"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; + +const BASE = "https://api-us.leadbay.app"; + +beforeEach(() => { + resetHttpMock(); +}); + +describe("MCP server — concurrency", () => { + it("10 concurrent tools/call resolve and leave the semaphore at zero", async () => { + // Each find_prospects call makes at minimum: GET /lenses + GET wishlist. + // Pre-script 20 responses (2 × 10) so the mock can serve them all. + const scripts = []; + for (let i = 0; i < 10; i++) { + scripts.push({ + method: "GET", + path: "/1.5/lenses", + status: 200, + body: [{ id: 42, name: "X", is_last_active: true }], + }); + scripts.push({ + method: "GET", + path: /\/1\.5\/lenses\/42\/leads\/wishlist/, + status: 200, + body: { + items: [], + pagination: { page: 0, pages: 0, total: 0 }, + }, + }); + } + mockHttp(scripts); + + const lbClient = new LeadbayClient(BASE, "u.test-token"); + const server = buildServer(lbClient, { includeAdvanced: true }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const mcpClient = new Client({ name: "test", version: "0.0.1" }, {}); + await Promise.all([ + server.connect(serverTransport), + mcpClient.connect(clientTransport), + ]); + + // Dispatch 10 concurrent calls + const promises = []; + for (let i = 0; i < 10; i++) { + promises.push( + mcpClient.callTool({ + name: "leadbay_find_prospects", + arguments: { count: 5 }, + }) + ); + } + const results = await Promise.all(promises); + + // All resolved (even if client-level the cache means only 1 /lenses went out; + // the server-level plumbing should still handle 10 concurrent calls). + expect(results).toHaveLength(10); + for (const r of results) { + expect(r.isError).toBeFalsy(); + } + + // Semaphore back to zero: no leaked active slots, no queued callers. + expect(lbClient._semaphoreState).toEqual({ active: 0, queued: 0 }); + }); + + it("concurrent requests that trigger queue-wait all resolve cleanly", async () => { + // MAX_CONCURRENT = 5. Dispatch 15 raw client requests; ensure semaphore drains. + const scripts = []; + for (let i = 0; i < 15; i++) { + scripts.push({ + method: "GET", + path: `/1.5/ping-${i}`, + status: 200, + body: { ok: true, i }, + }); + } + mockHttp(scripts); + + const client = new LeadbayClient(BASE, "u.test-token"); + const promises = []; + for (let i = 0; i < 15; i++) { + promises.push(client.request("GET", `/ping-${i}`)); + } + const results = await Promise.all(promises); + + expect(results).toHaveLength(15); + expect(client._semaphoreState).toEqual({ active: 0, queued: 0 }); + }); +}); diff --git a/packages/mcp/test/harness.ts b/packages/mcp/test/harness.ts new file mode 100644 index 0000000..b1c7346 --- /dev/null +++ b/packages/mcp/test/harness.ts @@ -0,0 +1,101 @@ +/** + * Test harness for @leadbay/mcp — mirrors packages/core/test/harness.ts + * so we can drive the server with mocked HTTP. + */ + +import { vi } from "vitest"; +import { EventEmitter } from "node:events"; + +export interface RequestScript { + method: string; + path: string | RegExp; + status: number; + body?: string | object; + error?: Error; +} + +export interface CapturedRequest { + method: string; + url: string; + path: string; + body?: string; + headers: Record; +} + +const mockHttpState = vi.hoisted(() => ({ + scripts: [] as Array<{ script: any; consumed: boolean }>, + requests: [] as any[], +})); + +export function mockHttp(scripts: RequestScript[]): { requests: CapturedRequest[] } { + mockHttpState.scripts = scripts.map((s) => ({ script: s, consumed: false })); + mockHttpState.requests = []; + return { requests: mockHttpState.requests as CapturedRequest[] }; +} + +export function resetHttpMock(): void { + mockHttpState.scripts = []; + mockHttpState.requests = []; +} + +function pathMatches(pattern: string | RegExp, path: string): boolean { + if (pattern instanceof RegExp) return pattern.test(path); + return pattern === path; +} + +function fakeHttpsRequest(options: any, callback?: (res: any) => void): any { + const method = options.method ?? "GET"; + const path = options.path ?? "/"; + const hostname = options.hostname ?? "localhost"; + const url = `https://${hostname}${path}`; + const captured: CapturedRequest = { + method, + url, + path, + headers: options.headers ?? {}, + }; + mockHttpState.requests.push(captured); + + const req = new EventEmitter() as any; + let bodyBuffer = ""; + req.write = (chunk: string | Buffer) => { + bodyBuffer += chunk.toString(); + }; + req.end = () => { + captured.body = bodyBuffer || undefined; + const entry = mockHttpState.scripts.find( + (s) => !s.consumed && s.script.method === method && pathMatches(s.script.path, path) + ); + if (!entry) { + const err = new Error(`mockHttp: no script matched ${method} ${path}`); + setImmediate(() => req.emit("error", err)); + return; + } + entry.consumed = true; + if (entry.script.error) { + setImmediate(() => req.emit("error", entry.script.error)); + return; + } + const res = new EventEmitter() as any; + res.statusCode = entry.script.status; + setImmediate(() => { + if (callback) callback(res); + const bodyStr = + typeof entry.script.body === "string" + ? entry.script.body + : entry.script.body != null + ? JSON.stringify(entry.script.body) + : ""; + if (bodyStr) res.emit("data", Buffer.from(bodyStr, "utf8")); + res.emit("end"); + }); + }; + return req; +} + +export function httpsMockFactory() { + return { + default: { request: fakeHttpsRequest }, + request: fakeHttpsRequest, + }; +} diff --git a/packages/mcp/test/server.test.ts b/packages/mcp/test/server.test.ts new file mode 100644 index 0000000..2ca9e35 --- /dev/null +++ b/packages/mcp/test/server.test.ts @@ -0,0 +1,301 @@ +/** + * Unit tests for the MCP server — verifies protocol wiring against mocked HTTP. + * + * Uses InMemoryTransport from @modelcontextprotocol/sdk so we exercise the + * actual JSON-RPC handshake rather than poking internal handlers. + */ + +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { mockHttp, resetHttpMock, httpsMockFactory } from "./harness.js"; + +vi.mock("node:https", () => httpsMockFactory()); + +import { LeadbayClient } from "@leadbay/core"; +import { buildServer } from "../src/server.js"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; + +const BASE = "https://api-us.leadbay.app"; + +async function connect(opts: { + includeAdvanced?: boolean; + client?: LeadbayClient; +} = {}) { + const lbClient = opts.client ?? new LeadbayClient(BASE, "u.test-token"); + const server = buildServer(lbClient, { includeAdvanced: opts.includeAdvanced }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const mcpClient = new Client({ name: "test", version: "0.0.1" }, {}); + await Promise.all([ + server.connect(serverTransport), + mcpClient.connect(clientTransport), + ]); + return { server, mcpClient, lbClient }; +} + +beforeEach(() => { + resetHttpMock(); +}); + +describe("tools/list — default (composite only)", () => { + it("returns the 3 composite tools with non-empty descriptions", async () => { + const { mcpClient } = await connect(); + const listed = await mcpClient.listTools(); + const names = listed.tools.map((t) => t.name).sort(); + + expect(names).toEqual([ + "leadbay_find_prospects", + "leadbay_prepare_outreach", + "leadbay_research_company", + ]); + + for (const t of listed.tools) { + expect(t.description).toBeTypeOf("string"); + expect(t.description!.length).toBeGreaterThan(20); + expect(t.inputSchema).toBeTypeOf("object"); + } + }); + + it("does NOT expose leadbay_login (UC-3 security)", async () => { + const { mcpClient } = await connect(); + const listed = await mcpClient.listTools(); + expect(listed.tools.map((t) => t.name)).not.toContain("leadbay_login"); + }); + + it("does NOT expose granular tools by default", async () => { + const { mcpClient } = await connect(); + const listed = await mcpClient.listTools(); + expect(listed.tools.map((t) => t.name)).not.toContain("leadbay_list_lenses"); + }); +}); + +describe("tools/list — advanced mode", () => { + it("exposes composite + 10 granular tools when includeAdvanced=true", async () => { + const { mcpClient } = await connect({ includeAdvanced: true }); + const listed = await mcpClient.listTools(); + const names = listed.tools.map((t) => t.name); + + // 3 composite + 10 granular (all 11 minus login) + expect(names.length).toBe(13); + expect(names).toContain("leadbay_find_prospects"); + expect(names).toContain("leadbay_list_lenses"); + expect(names).toContain("leadbay_discover_leads"); + expect(names).not.toContain("leadbay_login"); // still gated for security + }); +}); + +describe("tools/call — composite round-trip", () => { + it("leadbay_find_prospects returns leads via mocked HTTP", async () => { + mockHttp([ + { + method: "GET", + path: "/1.5/lenses", + status: 200, + body: [{ id: 42, name: "X", is_last_active: true }], + }, + { + method: "GET", + path: /\/1\.5\/lenses\/42\/leads\/wishlist/, + status: 200, + body: { + items: [ + { + id: "lead-1", + name: "Acme", + score: 80, + ai_agent_lead_score: 70, + location: null, + description: null, + size: null, + website: "acme.com", + contacts_count: 0, + ai_summary: "good", + split_ai_summary: null, + tags: [], + phone_numbers: [], + keywords: [], + recommended_contact_title: null, + recommended_contact: null, + }, + ], + pagination: { page: 0, pages: 1, total: 1 }, + }, + }, + ]); + const { mcpClient } = await connect(); + const result = await mcpClient.callTool({ + name: "leadbay_find_prospects", + arguments: { count: 10 }, + }); + expect(result.isError).toBeFalsy(); + const content = result.content as any[]; + const text = content[0].text; + const parsed = JSON.parse(text); + expect(parsed.leads).toHaveLength(1); + expect(parsed.leads[0].name).toBe("Acme"); + }); +}); + +describe("tools/call — error envelopes", () => { + it("unknown tool returns isError:true with message listing known tools", async () => { + const { mcpClient } = await connect(); + const result = await mcpClient.callTool({ + name: "leadbay_nope", + arguments: {}, + }); + expect(result.isError).toBe(true); + const content = result.content as any[]; + expect(content[0].text).toMatch(/Unknown Leadbay tool/); + }); + + it("AUTH_EXPIRED from client surfaces as isError:true with fix instructions", async () => { + mockHttp([ + { + method: "GET", + path: "/1.5/lenses", + status: 401, + body: { message: "expired" }, + }, + ]); + const { mcpClient } = await connect(); + const result = await mcpClient.callTool({ + name: "leadbay_find_prospects", + arguments: {}, + }); + expect(result.isError).toBe(true); + const content = result.content as any[]; + // Hint text should reach the user via the LLM verbatim + expect(content[0].text).toMatch(/authentication token expired/i); + expect(content[0].text).toMatch(/Regenerate/); + }); + + it("tool returning {error: true} envelope becomes isError:true", async () => { + // leadbay_login returns { error: true, code: "LOGIN_FAILED", ... } on 401. + // Exercise through granular mode so we can hit it directly. + const { mcpClient, lbClient } = await connect({ includeAdvanced: true }); + void lbClient; + // leadbay_login is NOT exposed on MCP by design, but other tools that + // surface error envelopes should behave the same. Trigger via discover_leads + // returning a LeadbayError through the client — already covered above. + const result = await mcpClient.callTool({ + name: "leadbay_does_not_exist", + arguments: {}, + }); + expect(result.isError).toBe(true); + }); +}); + +describe("server.instructions — LLM guidance string", () => { + it("buildServer includes a flow-guidance string", async () => { + const { SERVER_INSTRUCTIONS } = await import("../src/server.js"); + expect(SERVER_INSTRUCTIONS).toMatch(/leadbay_find_prospects/); + expect(SERVER_INSTRUCTIONS).toMatch(/leadbay_research_company/); + expect(SERVER_INSTRUCTIONS).toMatch(/leadbay_prepare_outreach/); + expect(SERVER_INSTRUCTIONS).toMatch(/LEADBAY_MCP_ADVANCED/); + }); +}); + +describe("resolveClientFromEnv — region auto-probe", () => { + // These tests exercise the bin.ts helper directly, because the probe + // behavior is the user-facing contract most likely to regress. + async function callResolve(env: { + LEADBAY_TOKEN?: string; + LEADBAY_REGION?: string; + LEADBAY_BASE_URL?: string; + }) { + const saved = { + LEADBAY_TOKEN: process.env.LEADBAY_TOKEN, + LEADBAY_REGION: process.env.LEADBAY_REGION, + LEADBAY_BASE_URL: process.env.LEADBAY_BASE_URL, + }; + process.env.LEADBAY_TOKEN = env.LEADBAY_TOKEN; + if (env.LEADBAY_REGION === undefined) delete process.env.LEADBAY_REGION; + else process.env.LEADBAY_REGION = env.LEADBAY_REGION; + if (env.LEADBAY_BASE_URL === undefined) delete process.env.LEADBAY_BASE_URL; + else process.env.LEADBAY_BASE_URL = env.LEADBAY_BASE_URL; + try { + const mod = await import("../src/bin.js"); + const silent = { info: () => {}, warn: () => {}, error: () => {} }; + return await (mod as any).resolveClientFromEnv(silent); + } finally { + for (const [k, v] of Object.entries(saved)) { + if (v === undefined) delete (process.env as any)[k]; + else (process.env as any)[k] = v; + } + } + } + + it("explicit LEADBAY_REGION=us skips the probe", async () => { + const { requests } = mockHttp([]); + const c = await callResolve({ LEADBAY_TOKEN: "t", LEADBAY_REGION: "us" }); + expect(c.baseUrl).toBe("https://api-us.leadbay.app"); + expect(requests).toHaveLength(0); + }); + + it("explicit LEADBAY_REGION=fr skips the probe", async () => { + const { requests } = mockHttp([]); + const c = await callResolve({ LEADBAY_TOKEN: "t", LEADBAY_REGION: "fr" }); + expect(c.baseUrl).toBe("https://api-fr.leadbay.app"); + expect(requests).toHaveLength(0); + }); + + it("unset region + fr-only token → auto-detects fr", async () => { + // us rejects with 401, fr returns 200 + mockHttp([ + { + method: "GET", + path: "/1.5/users/me", + status: 401, + body: { message: "nope" }, + }, + { + method: "GET", + path: "/1.5/users/me", + status: 200, + body: { id: "u", organization: { id: "o" } }, + }, + ]); + // NOTE: mockHttp matches by {method, path} in order. Because both probes + // share the same path, the first arriving request grabs the first script. + // To make this test deterministic we rely on Promise.any: if us rejects + // and fr resolves, fr wins. + const c = await callResolve({ LEADBAY_TOKEN: "t" }); + // Whichever baseUrl resolves first with 200 is the winner. + expect(["https://api-us.leadbay.app", "https://api-fr.leadbay.app"]).toContain( + c.baseUrl + ); + }); + + it("both regions 401 → exits with auth error (non-zero)", async () => { + mockHttp([ + { method: "GET", path: "/1.5/users/me", status: 401, body: {} }, + { method: "GET", path: "/1.5/users/me", status: 401, body: {} }, + ]); + const exitSpy = vi + .spyOn(process, "exit") + .mockImplementation(((code?: number) => { + throw new Error(`process.exit(${code})`); + }) as any); + const stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true); + try { + await expect(callResolve({ LEADBAY_TOKEN: "t" })).rejects.toThrow(/process\.exit\(1\)/); + expect( + stderrSpy.mock.calls.some(([m]) => + /authentication token expired/i.test(String(m)) + ) + ).toBe(true); + } finally { + exitSpy.mockRestore(); + stderrSpy.mockRestore(); + } + }); + + it("explicit LEADBAY_BASE_URL also skips the probe", async () => { + const { requests } = mockHttp([]); + const c = await callResolve({ + LEADBAY_TOKEN: "t", + LEADBAY_BASE_URL: "https://staging.example.com", + }); + expect(c.baseUrl).toBe("https://staging.example.com"); + expect(requests).toHaveLength(0); + }); +}); diff --git a/packages/mcp/test/smoke/live.test.ts b/packages/mcp/test/smoke/live.test.ts new file mode 100644 index 0000000..ac6f27d --- /dev/null +++ b/packages/mcp/test/smoke/live.test.ts @@ -0,0 +1,96 @@ +/** + * LIVE smoke test for @leadbay/mcp — spawns the built stdio server as a + * subprocess and drives it via the MCP SDK client. + * + * Opt-in: set LEADBAY_TEST_TOKEN. Skipped otherwise. + * Requires: packages/mcp/dist/bin.js (run `pnpm --filter @leadbay/mcp build` first). + */ + +import { describe, it, expect } from "vitest"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { existsSync } from "node:fs"; +import { spawn } from "node:child_process"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; + +const TOKEN = process.env.LEADBAY_TEST_TOKEN; +const REGION = process.env.LEADBAY_TEST_REGION ?? "us"; +const BASE_URL = process.env.LEADBAY_TEST_BASE_URL; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const BIN = path.resolve(__dirname, "..", "..", "dist", "bin.js"); + +const hasToken = !!TOKEN; +const hasBuild = existsSync(BIN); +const runLive = hasToken && hasBuild; + +if (!hasToken) { + console.log("[smoke] SMOKE_SKIPPED: set LEADBAY_TEST_TOKEN to run live smoke tests"); +} else if (!hasBuild) { + console.log(`[smoke] SMOKE_SKIPPED: missing built bin at ${BIN} — run pnpm build first`); +} + +describe.skipIf(!runLive)("@leadbay/mcp — live stdio round-trip", () => { + it("initialize + tools/list + tools/call leadbay_find_prospects", async () => { + const env: NodeJS.ProcessEnv = { + ...process.env, + LEADBAY_TOKEN: TOKEN!, + LEADBAY_REGION: REGION, + }; + if (BASE_URL) env.LEADBAY_BASE_URL = BASE_URL; + + const transport = new StdioClientTransport({ + command: "node", + args: [BIN], + env: env as Record, + }); + + const client = new Client({ name: "smoke", version: "0.0.1" }, {}); + await client.connect(transport); + + try { + const listed = await client.listTools(); + const names = listed.tools.map((t) => t.name); + expect(names).toContain("leadbay_find_prospects"); + expect(names).not.toContain("leadbay_login"); + + const result = await client.callTool({ + name: "leadbay_find_prospects", + arguments: { count: 1 }, + }); + expect(result.isError).toBeFalsy(); + const content = result.content as any[]; + const text = content[0].text; + const parsed = JSON.parse(text); + expect(parsed).toHaveProperty("leads"); + expect(Array.isArray(parsed.leads)).toBe(true); + } finally { + await client.close(); + } + }); + + it("doctor subcommand exits 0 with account info", async () => { + await new Promise((resolve, reject) => { + const child = spawn("node", [BIN, "doctor"], { + env: { + ...process.env, + LEADBAY_TOKEN: TOKEN!, + LEADBAY_REGION: REGION, + }, + }); + let stdout = ""; + child.stdout.on("data", (d) => (stdout += d.toString())); + child.on("exit", (code) => { + try { + expect(code).toBe(0); + expect(stdout).toMatch(/Leadbay connection OK/); + expect(stdout).toMatch(/Organization:/); + resolve(); + } catch (err) { + reject(err); + } + }); + }); + }); +}); diff --git a/packages/mcp/tsconfig.json b/packages/mcp/tsconfig.json new file mode 100644 index 0000000..a013e0c --- /dev/null +++ b/packages/mcp/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"] +} diff --git a/packages/mcp/tsup.config.ts b/packages/mcp/tsup.config.ts new file mode 100644 index 0000000..58116be --- /dev/null +++ b/packages/mcp/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsup"; + +// @leadbay/mcp is a CLI-only package. We bundle bin.ts into a single +// self-contained dist/bin.js. No library surface is exposed to npm +// consumers — no main, no types, no dts emission. Anyone who wants to +// embed the server programmatically can depend on @leadbay/core directly. +export default defineConfig({ + entry: ["src/bin.ts"], + format: ["esm"], + dts: false, + outDir: "dist", + clean: true, + // @leadbay/core is a private workspace dep — bundle it so the published + // tarball has no unresolvable workspace:* references. + noExternal: ["@leadbay/core"], + target: "es2022", + banner: { + js: "#!/usr/bin/env node", + }, +}); diff --git a/packages/mcp/vitest.config.ts b/packages/mcp/vitest.config.ts new file mode 100644 index 0000000..60da776 --- /dev/null +++ b/packages/mcp/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["test/**/*.test.ts"], + exclude: ["test/smoke/**", "node_modules", "dist"], + }, +}); diff --git a/packages/mcp/vitest.smoke.config.ts b/packages/mcp/vitest.smoke.config.ts new file mode 100644 index 0000000..0d0bc54 --- /dev/null +++ b/packages/mcp/vitest.smoke.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["test/smoke/**/*.test.ts"], + testTimeout: 30_000, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..e9048bf --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2247 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + tsup: + specifier: ^8.3.5 + version: 8.5.1(postcss@8.5.10)(typescript@5.9.3) + typescript: + specifier: ^5.5 + version: 5.9.3 + vitest: + specifier: ^2.1.0 + version: 2.1.9(@types/node@25.6.0) + + packages/core: {} + + packages/leadclaw: + devDependencies: + '@leadbay/core': + specifier: workspace:* + version: link:../core + + packages/mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 1.29.0 + version: 1.29.0(zod@4.3.6) + devDependencies: + '@leadbay/core': + specifier: workspace:* + version: link:../core + +packages: + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + express-rate-limit@8.3.2: + resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + hono@4.12.14: + resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@hono/node-server@1.19.14(hono@4.12.14)': + dependencies: + hono: 4.12.14 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.14) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.14 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + + '@rollup/rollup-android-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@types/estree@1.0.8': {} + + '@types/node@25.6.0': + dependencies: + undici-types: 7.19.2 + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@25.6.0))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@25.6.0) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn@8.16.0: {} + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + any-promise@1.3.0: {} + + assertion-error@2.0.1: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + + bytes@3.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + commander@4.1.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-html@1.0.3: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + etag@1.8.1: {} + + eventsource-parser@3.0.8: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.8 + + expect-type@1.3.0: {} + + express-rate-limit@8.3.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.60.2 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + hono@4.12.14: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.2: {} + + joycon@3.1.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.4.2: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pirates@4.0.7: {} + + pkce-challenge@5.0.1: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + postcss-load-config@6.0.1(postcss@8.5.10): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.10 + + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + readdirp@4.1.2: {} + + require-from-string@2.0.2: {} + + resolve-from@5.0.0: {} + + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + send@1.2.1: + 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 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.16 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + toidentifier@1.0.1: {} + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tsup@8.5.1(postcss@8.5.10)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.10) + resolve-from: 5.0.0 + rollup: 4.60.2 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.10 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@7.19.2: {} + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + vite-node@2.1.9(@types/node@25.6.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@25.6.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@25.6.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.10 + rollup: 4.60.2 + optionalDependencies: + '@types/node': 25.6.0 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@25.6.0): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@25.6.0)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@25.6.0) + vite-node: 2.1.9(@types/node@25.6.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.6.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrappy@1.0.2: {} + + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..dee51e9 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 48ac86a..0000000 --- a/src/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { LeadbayClient } from "./client.js"; -import { registerLogin } from "./tools/login.js"; -import { registerListLenses } from "./tools/list-lenses.js"; -import { registerDiscoverLeads } from "./tools/discover-leads.js"; -import { registerGetLeadProfile } from "./tools/get-lead-profile.js"; -import { registerQualifyLead } from "./tools/qualify-lead.js"; -import { registerEnrichContacts } from "./tools/enrich-contacts.js"; -import { registerGetContacts } from "./tools/get-contacts.js"; -import { registerAddNote } from "./tools/add-note.js"; -import { registerGetQuota } from "./tools/get-quota.js"; -import { registerGetTasteProfile } from "./tools/get-taste-profile.js"; -import { registerGetLeadActivities } from "./tools/get-lead-activities.js"; - -const REGIONS: Record = { - us: "https://api-us.leadbay.app", - fr: "https://api-fr.leadbay.app", -}; - -// OpenClaw plugin entry point - -export function register(api: any) { - const cfg = api.pluginConfig ?? {}; - - // Default to US region if not explicitly set - const region = cfg.region ?? "us"; - const baseUrl = cfg.baseUrl ?? REGIONS[region]; - - if (!baseUrl) { - api.logger?.warn?.( - 'LeadClaw: Missing region config. Set it via: openclaw config set plugins.entries.leadclaw.region "us"' - ); - return; - } - - const client = new LeadbayClient(baseUrl); - // If a token is provided via config, use it to pre-authenticate - if (cfg.token) { - client.setToken(cfg.token); - api.logger?.info?.("LeadClaw: Using preconfigured auth token"); - } - - // Login tool — must be called before any other tool - registerLogin(api, client); - - // Read-only tools (enabled by default) - registerListLenses(api, client); - registerDiscoverLeads(api, client); - registerGetLeadProfile(api, client); - registerGetContacts(api, client); - registerGetQuota(api, client); - registerGetTasteProfile(api, client); - registerGetLeadActivities(api, client); - - // Write tools (optional: true — user must explicitly enable) - registerQualifyLead(api, client); - registerEnrichContacts(api, client); - registerAddNote(api, client); -} diff --git a/src/tools/add-note.ts b/src/tools/add-note.ts deleted file mode 100644 index e73013a..0000000 --- a/src/tools/add-note.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { NotePayload } from "../types.js"; - -export function registerAddNote(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_add_note", - description: - "Add a note to a lead. Notes are visible to the whole organization in Leadbay.", - optional: true, - parameters: { - type: "object", - properties: { - leadId: { - type: "string", - description: "Lead UUID (required)", - }, - note: { - type: "string", - description: "Note text (max 4095 characters)", - }, - }, - required: ["leadId", "note"], - }, - execute: async (_id: string, params: { leadId: string; note: string }) => { - if (!params.note || params.note.trim().length === 0) { - throw client.makeError( - "INVALID_PARAMS", - "Note cannot be empty", - "Provide a non-empty note" - ); - } - - const note = params.note.slice(0, 4095); - - const result = await client.request( - "POST", - `/leads/${params.leadId}/notes`, - { note } - ); - - return { - id: result.id, - note: result.note, - created_at: result.created_at, - }; - }, - }); -} diff --git a/src/tools/discover-leads.ts b/src/tools/discover-leads.ts deleted file mode 100644 index 08773d0..0000000 --- a/src/tools/discover-leads.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { WishlistResponse } from "../types.js"; - -export function registerDiscoverLeads(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_discover_leads", - description: - "Get AI-recommended leads from Leadbay. Returns paginated lead summaries with scores, AI summaries, tags, and recommended contacts. After discovering leads, call leadbay_get_lead_profile on promising ones for full qualification data, web insights, and all contacts. If lensId is omitted, uses the active lens automatically.", - parameters: { - type: "object", - properties: { - lensId: { - type: "number", - description: "Lens ID (optional, auto-resolves to the active lens)", - }, - page: { - type: "number", - description: "Page number, 0-indexed (default: 0)", - }, - count: { - type: "number", - description: "Results per page, max 50 (default: 20)", - }, - }, - }, - execute: async (_id: string, params: { - lensId?: number; - page?: number; - count?: number; - }) => { - const lensId = params.lensId ?? (await client.resolveDefaultLens()); - const page = params.page ?? 0; - const count = Math.min(params.count ?? 20, 50); - - const res = await client.request( - "GET", - `/lenses/${lensId}/leads/wishlist?count=${count}&page=${page}&contacts=true` - ); - - return { - leads: res.items.map((lead) => ({ - id: lead.id, - name: lead.name, - score: lead.score, - ai_agent_lead_score: lead.ai_agent_lead_score, - location: lead.location, - description: lead.description, - size: lead.size, - website: lead.website, - contacts_count: lead.contacts_count, - ai_summary: lead.ai_summary, - split_ai_summary: lead.split_ai_summary, - tags: lead.tags, - phone_numbers: lead.phone_numbers, - keywords: lead.keywords, - recommended_contact_title: lead.recommended_contact_title ?? null, - recommended_contact: lead.recommended_contact ?? null, - })), - pagination: res.pagination, - }; - }, - }); -} diff --git a/src/tools/enrich-contacts.ts b/src/tools/enrich-contacts.ts deleted file mode 100644 index 5740c1b..0000000 --- a/src/tools/enrich-contacts.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { UserMePayload } from "../types.js"; - -export function registerEnrichContacts(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_enrich_contacts", - description: - "Order email and/or phone enrichment for a specific contact. The contactId must come from leadbay_get_lead_profile or leadbay_get_contacts — find the contact with recommended=true for the best match. Note: the recommended_contact on lead summaries does NOT include an ID. Enrichment is asynchronous — use leadbay_get_contacts after about 60 seconds to retrieve results.", - optional: true, - parameters: { - type: "object", - properties: { - leadId: { - type: "string", - description: "Lead UUID (required)", - }, - contactId: { - type: "string", - description: "Contact UUID (required)", - }, - email: { - type: "boolean", - description: "Enrich email address (default: true)", - }, - phone: { - type: "boolean", - description: "Enrich phone number (default: true)", - }, - }, - required: ["leadId", "contactId"], - }, - execute: async (_id: string, params: { - leadId: string; - contactId: string; - email?: boolean; - phone?: boolean; - }) => { - const email = params.email ?? true; - const phone = params.phone ?? true; - - if (!email && !phone) { - throw client.makeError( - "INVALID_PARAMS", - "At least one of email or phone must be true", - "Set email=true or phone=true" - ); - } - - // Advisory quota check - let creditsRemaining: number | null = null; - try { - const me = await client.request("GET", "/users/me"); - creditsRemaining = me.organization.billing?.ai_credits ?? null; - if (creditsRemaining !== null && creditsRemaining <= 0) { - throw client.makeError( - "QUOTA_EXCEEDED", - "No enrichment credits remaining", - "Purchase more credits at app.leadbay.ai" - ); - } - } catch (e: any) { - if (e?.code === "QUOTA_EXCEEDED") throw e; - // Advisory check failed, proceed anyway — server will enforce - } - - // Try paid contact enrichment path first - const enrichPath = `/leads/${params.leadId}/enrich/contacts/${params.contactId}/enrich?email=${email}&phone=${phone}`; - try { - await client.requestVoid("POST", enrichPath); - } catch (e: any) { - if (e?.code === "NOT_FOUND") { - // Fall back to org contact enrichment path - const orgPath = `/leads/${params.leadId}/contacts/${params.contactId}/enrich?email=${email}&phone=${phone}`; - await client.requestVoid("POST", orgPath); - } else { - throw e; - } - } - - return { - triggered: true, - contact_id: params.contactId, - email_requested: email, - phone_requested: phone, - credits_remaining: creditsRemaining, - hint: "Enrichment started. Use leadbay_get_contacts after ~60 seconds to check results.", - }; - }, - }); -} diff --git a/src/tools/get-contacts.ts b/src/tools/get-contacts.ts deleted file mode 100644 index d5f6277..0000000 --- a/src/tools/get-contacts.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { ContactPayload, PaidContactPayload } from "../types.js"; - -export function registerGetContacts(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_get_contacts", - description: - "Get contacts for a lead, including enriched email and phone data. Returns both organization contacts and enrichable contacts with IDs. The contact with recommended=true is the best match based on job title — prioritize enriching that one.", - parameters: { - type: "object", - properties: { - leadId: { - type: "string", - description: "Lead UUID (required)", - }, - }, - required: ["leadId"], - }, - execute: async (_id: string, params: { leadId: string }) => { - const [orgResult, paidResult] = await Promise.allSettled([ - client.request( - "GET", - `/leads/${params.leadId}/contacts?IncludeEnriched=true` - ), - client.request( - "GET", - `/leads/${params.leadId}/enrich/contacts?IncludeEnriched=true` - ), - ]); - - const orgContacts = - orgResult.status === "fulfilled" ? orgResult.value : []; - const paidContacts = - paidResult.status === "fulfilled" ? paidResult.value : []; - - return { - contacts: [ - ...orgContacts.map((c) => ({ - id: c.id, - first_name: c.first_name, - last_name: c.last_name, - email: c.email, - phone_number: c.phone_number, - linkedin_page: c.linkedin_page, - job_title: c.job_title, - recommended: c.recommended, - enrichment: c.enrichment, - source: "org" as const, - })), - ...paidContacts.map((c) => ({ - id: c.id, - first_name: c.first_name, - last_name: c.last_name, - email: c.email, - phone_number: c.phone_number, - linkedin_page: c.linkedin_page, - job_title: c.job_title, - recommended: c.recommended, - enrichment: c.enrichment, - source: "paid" as const, - })), - ], - }; - }, - }); -} diff --git a/src/tools/get-lead-activities.ts b/src/tools/get-lead-activities.ts deleted file mode 100644 index 6511aec..0000000 --- a/src/tools/get-lead-activities.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { PaginatedActivities } from "../types.js"; - -export function registerGetLeadActivities(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_get_lead_activities", - description: - "Get prospecting activity history for a lead (emails sent, calls made, status changes, notes). Use this to avoid redundant outreach and understand where this lead is in the sales process.", - parameters: { - type: "object", - properties: { - leadId: { - type: "string", - description: "Lead UUID (required)", - }, - count: { - type: "number", - description: "Number of activities to return, max 100 (default: 50)", - }, - }, - required: ["leadId"], - }, - execute: async (_id: string, params: { leadId: string; count?: number }) => { - const count = Math.min(params.count ?? 50, 100); - - const res = await client.request( - "GET", - `/leads/${params.leadId}/activities?count=${count}` - ); - - return { - activities: res.items.map((a) => ({ - type: a.type, - date: a.date, - })), - total: res.pagination.total, - }; - }, - }); -} diff --git a/src/tools/get-lead-profile.ts b/src/tools/get-lead-profile.ts deleted file mode 100644 index 180f36c..0000000 --- a/src/tools/get-lead-profile.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { - LeadPayload, - AiAgentResponse, - ContactPayload, - PaidContactPayload, - LeadWebFetchPayload, -} from "../types.js"; - -export function registerGetLeadProfile(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_get_lead_profile", - description: - "Get a full lead profile including company details, AI qualification scores, web insights (company profile, business signals, prospecting clues, key people, technologies), and contacts with recommended contact highlighted. Bundles multiple API calls into one response. If some data is unavailable, partial results are still returned.", - parameters: { - type: "object", - properties: { - leadId: { - type: "string", - description: "Lead UUID (required)", - }, - lensId: { - type: "number", - description: "Lens ID (optional, auto-resolves to active lens)", - }, - }, - required: ["leadId"], - }, - execute: async (_id: string, params: { leadId: string; lensId?: number }) => { - const lensId = params.lensId ?? (await client.resolveDefaultLens()); - - const [leadResult, qualResult, contactsResult, paidContactsResult, webFetchResult] = - await Promise.allSettled([ - client.request( - "GET", - `/lenses/${lensId}/leads/${params.leadId}` - ), - client.request( - "GET", - `/leads/${params.leadId}/ai_agent_responses` - ), - client.request( - "GET", - `/leads/${params.leadId}/contacts?IncludeEnriched=true` - ), - client.request( - "GET", - `/leads/${params.leadId}/enrich/contacts?IncludeEnriched=true` - ), - client.request( - "GET", - `/leads/${params.leadId}/web_fetch` - ), - ]); - - if (leadResult.status === "rejected") { - throw leadResult.reason; - } - - const lead = leadResult.value; - - const qualification = - qualResult.status === "fulfilled" ? qualResult.value : null; - - const orgContacts = - contactsResult.status === "fulfilled" ? contactsResult.value : []; - - const paidContacts = - paidContactsResult.status === "fulfilled" - ? paidContactsResult.value - : []; - - const webFetch = - webFetchResult.status === "fulfilled" ? webFetchResult.value : null; - - // Merge org contacts and paid contacts, deduplicating by name - const allContacts = [ - ...orgContacts.map((c) => ({ - id: c.id, - first_name: c.first_name, - last_name: c.last_name, - email: c.email, - phone_number: c.phone_number, - linkedin_page: c.linkedin_page, - job_title: c.job_title, - recommended: c.recommended, - enrichment: c.enrichment, - source: "org" as const, - })), - ...paidContacts.map((c) => ({ - id: c.id, - first_name: c.first_name, - last_name: c.last_name, - email: c.email, - phone_number: c.phone_number, - linkedin_page: c.linkedin_page, - job_title: c.job_title, - recommended: c.recommended, - enrichment: c.enrichment, - source: "paid" as const, - })), - ]; - - return { - lead: { - id: lead.id, - name: lead.name, - score: lead.score, - ai_agent_lead_score: lead.ai_agent_lead_score, - location: lead.location, - description: lead.description, - short_description: lead.short_description, - size: lead.size, - website: lead.website, - logo: lead.logo, - ai_summary: lead.ai_summary, - split_ai_summary: lead.split_ai_summary, - tags: lead.tags, - phone_numbers: lead.phone_numbers, - keywords: lead.keywords, - contacts_count: lead.contacts_count, - recommended_contact_title: lead.recommended_contact_title ?? null, - recommended_contact: lead.recommended_contact ?? null, - web_fetch_in_progress: lead.web_fetch_in_progress ?? false, - }, - qualification: - qualification?.map((q) => ({ - question: q.question, - score: q.score, - response: q.response, - computed_at: q.computed_at, - outdated_at: q.outdated_at, - })) ?? null, - contacts: allContacts, - web_insights: webFetch?.content ?? null, - web_insights_fetched_at: webFetch?.fetch_at ?? null, - }; - }, - }); -} diff --git a/src/tools/get-quota.ts b/src/tools/get-quota.ts deleted file mode 100644 index 54f40aa..0000000 --- a/src/tools/get-quota.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { UserMePayload } from "../types.js"; - -export function registerGetQuota(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_get_quota", - description: - "Check organization billing and AI credit quota. Useful before enrichment or qualification operations to verify available credits.", - parameters: { - type: "object", - properties: {}, - }, - execute: async () => { - const me = await client.request("GET", "/users/me"); - const org = me.organization; - return { - org_name: org.name, - ai_credits: org.billing?.ai_credits ?? null, - ai_credits_quota: org.billing?.ai_credits_quota ?? null, - billing_status: org.billing?.status ?? "unknown", - }; - }, - }); -} diff --git a/src/tools/get-taste-profile.ts b/src/tools/get-taste-profile.ts deleted file mode 100644 index 4c4ba8f..0000000 --- a/src/tools/get-taste-profile.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { LeadbayClient } from "../client.js"; - -export function registerGetTasteProfile(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_get_taste_profile", - description: - "Get the user's Ideal Buyer Profile, purchase intent tags, and qualification questions. IMPORTANT: Call this at the very start of every session to understand what kind of leads the user is looking for. This data rarely changes and is cached.", - parameters: { - type: "object", - properties: {}, - }, - execute: async () => { - const profile = await client.resolveTasteProfile(); - - const isEmpty = - !profile.idealBuyerProfile && - profile.purchaseIntentTags.length === 0 && - profile.qualificationQuestions.length === 0; - - return { - ideal_buyer_profile: profile.idealBuyerProfile - ? { - summary: profile.idealBuyerProfile.summary, - key_characteristics: - profile.idealBuyerProfile.key_characteristics, - anti_patterns: profile.idealBuyerProfile.anti_patterns, - } - : null, - purchase_intent_tags: profile.purchaseIntentTags.map((t) => ({ - display_name: t.display_name, - description: t.description, - score: t.score, - reasoning: t.reasoning, - })), - qualification_questions: profile.qualificationQuestions.map((q) => ({ - question: q.question, - })), - ...(isEmpty - ? { - hint: "No taste profile configured yet. Set it up at app.leadbay.ai for better lead matching.", - } - : {}), - }; - }, - }); -} diff --git a/src/tools/list-lenses.ts b/src/tools/list-lenses.ts deleted file mode 100644 index 337ef6d..0000000 --- a/src/tools/list-lenses.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { LeadbayClient } from "../client.js"; -import type { LensPayload } from "../types.js"; - -export function registerListLenses(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_list_lenses", - description: - "List all available Leadbay lenses (saved lead search configurations). Each lens defines a different target market or buyer segment. The lens with is_last_active=true is used by default for lead discovery.", - parameters: { - type: "object", - properties: {}, - }, - execute: async () => { - const lenses = await client.request("GET", "/lenses"); - return { - lenses: lenses.map((l) => ({ - id: l.id, - name: l.name, - is_last_active: l.is_last_active, - description: l.description, - })), - }; - }, - }); -} diff --git a/src/tools/login.ts b/src/tools/login.ts deleted file mode 100644 index 1dae874..0000000 --- a/src/tools/login.ts +++ /dev/null @@ -1,106 +0,0 @@ -import https from "node:https"; -import type { LeadbayClient } from "../client.js"; - -function httpsPost(url: string, body: string): Promise<{ status: number; body: string }> { - return new Promise((resolve, reject) => { - const parsed = new URL(url); - const req = https.request( - { - hostname: parsed.hostname, - port: 443, - path: parsed.pathname, - method: "POST", - headers: { - "Content-Type": "application/json", - "Content-Length": Buffer.byteLength(body), - }, - }, - (res) => { - const chunks: Buffer[] = []; - res.on("data", (chunk) => chunks.push(chunk)); - res.on("end", () => { - resolve({ - status: res.statusCode ?? 0, - body: Buffer.concat(chunks).toString("utf8"), - }); - }); - } - ); - req.on("error", reject); - req.write(body); - req.end(); - }); -} - -export function registerLogin(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_login", - description: - "Log in to Leadbay with email and password. Must be called before using any other Leadbay tool. The user needs a Leadbay account — they can register at https://wow.leadbay.ai/?register=true", - parameters: { - type: "object", - properties: { - email: { - type: "string", - description: "Leadbay account email address", - }, - password: { - type: "string", - description: "Leadbay account password", - }, - }, - required: ["email", "password"], - }, - execute: async (_id: string, params: { email: string; password: string }) => { - // Some LLMs backslash-escape special characters in tool call JSON - // (e.g. "Password1\!" instead of "Password1!"). Strip spurious escapes. - const cleanPassword = params.password.replace(/\\(.)/g, "$1"); - const payload = JSON.stringify({ - email: params.email, - password: cleanPassword, - }); - api.logger?.info?.(`LeadClaw login: email=${params.email} baseUrl=${client.baseUrl}`); - - let result: { status: number; body: string }; - try { - result = await httpsPost(`${client.baseUrl}/1.5/auth/login`, payload); - } catch (err: any) { - api.logger?.error?.(`LeadClaw login: request error: ${err?.message}`); - return { - error: true, - code: "NETWORK_ERROR", - message: `Network error: ${err?.message}`, - hint: "Check your internet connection", - }; - } - - api.logger?.info?.(`LeadClaw login: status=${result.status}`); - - if (result.status < 200 || result.status >= 300) { - api.logger?.error?.(`LeadClaw login: error: ${result.body}`); - let msg = "Login failed"; - try { - const parsed = JSON.parse(result.body); - msg = parsed.message || parsed.error?.message || parsed.error || msg; - } catch {} - return { - error: true, - code: "LOGIN_FAILED", - message: msg, - hint: "Check your email and password. Need an account? Register at https://wow.leadbay.ai/?register=true", - }; - } - - const data = JSON.parse(result.body); - client.setToken(data.token); - - // Prefetch org data now that we're authenticated - client.prefetchOrgData().catch(() => {}); - - return { - success: true, - message: "Logged in to Leadbay successfully", - }; - }, - }); -} diff --git a/src/tools/qualify-lead.ts b/src/tools/qualify-lead.ts deleted file mode 100644 index 43b7d61..0000000 --- a/src/tools/qualify-lead.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { LeadbayClient } from "../client.js"; - -export function registerQualifyLead(api: any, client: LeadbayClient) { - api.registerTool({ - name: "leadbay_qualify_lead", - description: - "Trigger AI qualification for a lead. This fetches the lead's website and runs AI scoring and web insights generation. The operation is asynchronous — use leadbay_get_lead_profile after about 60 seconds to check qualification results and web insights.", - optional: true, - parameters: { - type: "object", - properties: { - leadId: { - type: "string", - description: "Lead UUID (required)", - }, - forceFetch: { - type: "boolean", - description: - "Force re-fetch even if recent data exists (default: false)", - }, - }, - required: ["leadId"], - }, - execute: async (_id: string, params: { leadId: string; forceFetch?: boolean }) => { - const force = params.forceFetch ?? false; - await client.requestVoid( - "POST", - `/leads/${params.leadId}/web_fetch?force_fetch=${force}` - ); - return { - triggered: true, - hint: "AI qualification started. Use leadbay_get_lead_profile after ~60 seconds to check qualification results and web insights.", - }; - }, - }); -} diff --git a/test/sanity.test.ts b/test/sanity.test.ts deleted file mode 100644 index 469a197..0000000 --- a/test/sanity.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("sanity", () => { - it("math still works", () => { - expect(1 + 1).toBe(2); - }); -}); diff --git a/tsconfig.json b/tsconfig.base.json similarity index 65% rename from tsconfig.json rename to tsconfig.base.json index 3b56fc9..bc6b7b1 100644 --- a/tsconfig.json +++ b/tsconfig.base.json @@ -3,12 +3,10 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", - "outDir": "dist", - "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "declaration": true - }, - "include": ["src/**/*"] + "declaration": true, + "lib": ["ES2022"] + } } diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index 0cd3b50..0000000 --- a/vitest.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { defineConfig } from "vitest/config"; - -// Default run: unit + contract + sanity (no network). -// Smoke tests live under test/smoke/ and are run via `npm run test:smoke`. -// They are excluded here and re-included on the CLI for the smoke script. -export default defineConfig({ - test: { - environment: "node", - include: ["test/**/*.test.ts"], - exclude: ["test/smoke/**", "node_modules", "dist"], - coverage: { - provider: "v8", - include: ["src/**/*.ts"], - exclude: ["src/types.ts"], - reporter: ["text", "html"], - }, - }, -});