diff --git a/.gitignore b/.gitignore index 0d1eda5..3a79174 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json .idea # Finder (MacOS) folder config -.DS_Store +.DS_Store \ No newline at end of file diff --git a/bun.lock b/bun.lock index a989fc5..acb6ba1 100644 --- a/bun.lock +++ b/bun.lock @@ -8,12 +8,15 @@ "@qdrant/js-client-rest": "^1.13.0", "@types/markdown-it": "^14.1.2", "@xenova/transformers": "^2.17.2", + "dotenv": "^16.4.5", + "fuels": "^0.100.3", "markdown-it": "^14.1.0", "onnxruntime-node": "^1.18.0", "zod": "^3.23.8", }, "devDependencies": { "@types/bun": "latest", + "@types/node": "^20.11.20", "esbuild": "^0.25.2", "typescript": "^5", "vitest": "^3.1.1", @@ -76,14 +79,58 @@ "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + "@fuel-ts/abi-coder": ["@fuel-ts/abi-coder@0.100.6", "", { "dependencies": { "@fuel-ts/crypto": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/hasher": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/utils": "0.100.6", "type-fest": "4.34.1" } }, "sha512-xr52EXg5Q+Qj/PR9nzUjbUg58xvstjWw+iBxVaCtcGjqRsoRafDwXP4xCLqIIAPz1MUy20Yfo/Plj+KeC/mzpQ=="], + + "@fuel-ts/abi-typegen": ["@fuel-ts/abi-typegen@0.100.6", "", { "dependencies": { "@fuel-ts/errors": "0.100.6", "@fuel-ts/utils": "0.100.6", "@fuel-ts/versions": "0.100.6", "commander": "13.1.0", "glob": "10.4.5", "handlebars": "4.7.8", "mkdirp": "3.0.1", "ramda": "0.30.1", "rimraf": "5.0.10" }, "bin": { "fuels-typegen": "typegen.js" } }, "sha512-eS+rJgUGZMTPtIRiFo8bo/E3eYuS6nxBEL9WCopMAeCL/elg1QcBpOSc+/kbktkbBDFT57yUZ2D2mL7XjE+cyA=="], + + "@fuel-ts/account": ["@fuel-ts/account@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/address": "0.100.6", "@fuel-ts/crypto": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/hasher": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/merkle": "0.100.6", "@fuel-ts/transactions": "0.100.6", "@fuel-ts/utils": "0.100.6", "@fuel-ts/versions": "0.100.6", "@fuels/vm-asm": "0.60.2", "@noble/curves": "1.8.1", "events": "3.3.0", "graphql": "16.10.0", "graphql-request": "6.1.0", "graphql-tag": "2.12.6", "ramda": "0.30.1" } }, "sha512-FPf2fsqxHCb5rcA6h7U3LyFiF24hgOY5GjLIGf1810qPTpkJtEaMUgNTmmf7jO1P1Jj8gNY/ckJCWReWtBSsCA=="], + + "@fuel-ts/address": ["@fuel-ts/address@0.100.6", "", { "dependencies": { "@fuel-ts/crypto": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/utils": "0.100.6", "@noble/hashes": "1.7.1" } }, "sha512-AodPB16TfazwQ0aY7I1W8hno0K0Apz+tIvYhsmu1RbNRpbcoMTq6CDNQbnpUaOrQXAEO++/ffuKeTC0xTHKz4Q=="], + + "@fuel-ts/contract": ["@fuel-ts/contract@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/account": "0.100.6", "@fuel-ts/crypto": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/hasher": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/merkle": "0.100.6", "@fuel-ts/program": "0.100.6", "@fuel-ts/transactions": "0.100.6", "@fuel-ts/utils": "0.100.6", "@fuels/vm-asm": "0.60.2", "ramda": "0.30.1" } }, "sha512-9vQfcPvxOphOtDTAVQH18BXHxq3oRxGNbdJOvog5H/8UELKAEAq7GjEnbxfOeQFHYJTJo2ozAfY+Aocxn7Jhlg=="], + + "@fuel-ts/crypto": ["@fuel-ts/crypto@0.100.6", "", { "dependencies": { "@fuel-ts/errors": "0.100.6", "@fuel-ts/utils": "0.100.6", "@noble/hashes": "1.7.1" } }, "sha512-F6zAP+MnJ6e6QPav1kDl9E6DZ+wbAyjCFW/bqVT+tdiWn9p/wuf8kGdg3qGpShmfOHyiIWbMkgxJYl5Xf1wMQg=="], + + "@fuel-ts/errors": ["@fuel-ts/errors@0.100.6", "", { "dependencies": { "@fuel-ts/versions": "0.100.6" } }, "sha512-dFJsISeTeK61TFm8wmfQjOdbfikY9RDEq5X8yhDIMsyXYYl2lvGBdtItpNKVTOga7IK4V2MP/aczLMbq72Z9SQ=="], + + "@fuel-ts/hasher": ["@fuel-ts/hasher@0.100.6", "", { "dependencies": { "@fuel-ts/crypto": "0.100.6", "@fuel-ts/utils": "0.100.6", "@noble/hashes": "1.7.1" } }, "sha512-mnaCtSip7V6VYEu18sE/gNTyI2bZvZCFCmCnzvkumB86t1CQ7Gpr9GrWyyNzF/V7ucnuBg5hXrXTF9l0hfUaPw=="], + + "@fuel-ts/math": ["@fuel-ts/math@0.100.6", "", { "dependencies": { "@fuel-ts/errors": "0.100.6", "@types/bn.js": "5.1.6", "bn.js": "5.2.1" } }, "sha512-uL6xPAj8uZ9neqS/IkYquZ5gUDGrzTZLEmLx6+ODLLeo/eNg22k3HWaFKpCsVQWN3fn1RvBELpsZlrdp/PkEJg=="], + + "@fuel-ts/merkle": ["@fuel-ts/merkle@0.100.6", "", { "dependencies": { "@fuel-ts/hasher": "0.100.6", "@fuel-ts/math": "0.100.6" } }, "sha512-LYFpE8Aj/yBi6veGGRcV30z8I2okNNKQgTkPQUmavII0cXAswX7MTFC6N2ehnNVjEmv5dgQJITQ1XoxzaAyk+w=="], + + "@fuel-ts/program": ["@fuel-ts/program@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/account": "0.100.6", "@fuel-ts/address": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/transactions": "0.100.6", "@fuel-ts/utils": "0.100.6", "@fuels/vm-asm": "0.60.2", "ramda": "0.30.1" } }, "sha512-rz/Cf5Sl9NXNSxb0inPR0cga8Uz+x+fGODx9v5Y/wtXXxim0l4A5o0JbiKz+GreK+PxlidYT5pjQNqiU7+A5sQ=="], + + "@fuel-ts/recipes": ["@fuel-ts/recipes@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/abi-typegen": "0.100.6", "@fuel-ts/account": "0.100.6", "@fuel-ts/address": "0.100.6", "@fuel-ts/contract": "0.100.6", "@fuel-ts/program": "0.100.6", "@fuel-ts/transactions": "0.100.6", "@fuel-ts/utils": "0.100.6" } }, "sha512-KQz2+2h5B/7SQ/Z/NP2jSy9N/5vQ7xo9Hobg8MbQ3TdMewj3yYpVFHncbxCz1a2s3RTDGbFpb8qjIhk4pGBVRQ=="], + + "@fuel-ts/script": ["@fuel-ts/script@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/account": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/program": "0.100.6", "@fuel-ts/transactions": "0.100.6", "@fuel-ts/utils": "0.100.6" } }, "sha512-UkkRUM59v8DKTCUstvb69vP2JrDZlepEKjpOznYXMq68qOGHMlsrfs7xsXk9jndxF9oaFryzV7Sgp2WdRAU6Pg=="], + + "@fuel-ts/transactions": ["@fuel-ts/transactions@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/address": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/hasher": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/utils": "0.100.6" } }, "sha512-Y31b5tUQx7k/Xe7m/kMEgbfFkxGWBWiem3c8UNM/lBtl7ttF50piZkt+hBzP0YE+NWsDm4pPEZh0Is1QHpA/lQ=="], + + "@fuel-ts/utils": ["@fuel-ts/utils@0.100.6", "", { "dependencies": { "@fuel-ts/errors": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/versions": "0.100.6", "fflate": "0.8.2" }, "peerDependencies": { "vitest": "3.0.9" } }, "sha512-zLLvjzpstVR0o6TgKBLUAReZYl0jenqoEpRXR0vsNNjZjnNNx3ZXU0q5TVFy+pKIbHYqPbanA+RE+d3zf0/xsg=="], + + "@fuel-ts/versions": ["@fuel-ts/versions@0.100.6", "", { "dependencies": { "chalk": "4", "cli-table": "0.3.11" }, "bin": { "fuels-versions": "versions.js" } }, "sha512-bS8mrRhrKwCWRxHw+eAQIcbUOU7U3KbFwzvjFf4133bU3GZXf6OYkZOQsgr1zkItEQSf9PBivyA+3dgDHY09hQ=="], + + "@fuels/vm-asm": ["@fuels/vm-asm@0.60.2", "", {}, "sha512-wkCu63jTGJWpRZQirTaB8S4/gyoebEJLk3AKfnykt/lgWp1U9iHOcCICVHQP547i+y8jEVKwk18+huINFyYVFQ=="], + + "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], + "@huggingface/jinja": ["@huggingface/jinja@0.2.2", "", {}, "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.9.0", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA=="], + "@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], + + "@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -150,6 +197,8 @@ "@sevinf/maybe": ["@sevinf/maybe@0.5.0", "", {}, "sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg=="], + "@types/bn.js": ["@types/bn.js@5.1.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w=="], + "@types/bun": ["@types/bun@1.2.9", "", { "dependencies": { "bun-types": "1.2.9" } }, "sha512-epShhLGQYc4Bv/aceHbmBhOz1XgUnuTZgcxjxk+WXwNyDXavv5QHD1QEFV0FwbTSQtNq6g4ZcV6y0vZakTjswg=="], "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], @@ -162,7 +211,7 @@ "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], - "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], + "@types/node": ["@types/node@20.17.48", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], @@ -184,12 +233,22 @@ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="], + "b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "bare-events": ["bare-events@2.5.4", "", {}, "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA=="], "bare-fs": ["bare-fs@4.1.2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA=="], @@ -202,16 +261,26 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "bn.js": ["bn.js@5.2.1", "", {}, "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="], + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "bun-types": ["bun-types@1.2.9", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw=="], + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], @@ -222,10 +291,16 @@ "chai": ["chai@5.2.0", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "cli-table": ["cli-table@0.3.11", "", { "dependencies": { "colors": "1.0.3" } }, "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ=="], + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -234,6 +309,10 @@ "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], + "colors": ["colors@1.0.3", "", {}, "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw=="], + + "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], + "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -244,6 +323,8 @@ "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], @@ -264,10 +345,16 @@ "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + "dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], @@ -294,6 +381,8 @@ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + "eventsource": ["eventsource@3.0.6", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA=="], "eventsource-parser": ["eventsource-parser@3.0.1", "", {}, "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA=="], @@ -308,10 +397,16 @@ "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], "flatbuffers": ["flatbuffers@1.12.0", "", {}, "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -320,6 +415,8 @@ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fuels": ["fuels@0.100.6", "", { "dependencies": { "@fuel-ts/abi-coder": "0.100.6", "@fuel-ts/abi-typegen": "0.100.6", "@fuel-ts/account": "0.100.6", "@fuel-ts/address": "0.100.6", "@fuel-ts/contract": "0.100.6", "@fuel-ts/crypto": "0.100.6", "@fuel-ts/errors": "0.100.6", "@fuel-ts/hasher": "0.100.6", "@fuel-ts/math": "0.100.6", "@fuel-ts/program": "0.100.6", "@fuel-ts/recipes": "0.100.6", "@fuel-ts/script": "0.100.6", "@fuel-ts/transactions": "0.100.6", "@fuel-ts/utils": "0.100.6", "@fuel-ts/versions": "0.100.6", "@fuels/vm-asm": "0.60.2", "bundle-require": "5.1.0", "chalk": "4", "chokidar": "3.6.0", "commander": "13.1.0", "esbuild": "0.25.1", "glob": "10.4.5", "handlebars": "4.7.8", "joycon": "3.1.1", "lodash.camelcase": "4.3.0", "portfinder": "1.0.32", "toml": "3.0.0", "uglify-js": "3.19.3", "yup": "1.6.1" }, "bin": { "fuels": "fuels.js" } }, "sha512-PIkptWvn04VVSwBiHU6YoDThdVoGx442j++o9BoTJaWarOK8W7bmrtfoX9ImNnv5IKUHCpSsb8N6wQBbGfs0xw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "get-intrinsic": ["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.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -328,14 +425,28 @@ "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "glob": ["glob@10.4.5", "", { "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" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graphql": ["graphql@16.10.0", "", {}, "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ=="], + + "graphql-request": ["graphql-request@6.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" }, "peerDependencies": { "graphql": "14 - 16" } }, "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw=="], + + "graphql-tag": ["graphql-tag@2.12.6", "", { "dependencies": { "tslib": "^2.1.0" }, "peerDependencies": { "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg=="], + "guid-typescript": ["guid-typescript@1.0.9", "", {}, "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="], + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], @@ -356,18 +467,40 @@ "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + "long": ["long@4.0.0", "", {}, "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="], "loupe": ["loupe@3.1.3", "", {}, "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug=="], + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], @@ -388,6 +521,8 @@ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], @@ -406,10 +541,16 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + "node-abi": ["node-abi@3.74.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w=="], "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -428,10 +569,14 @@ "onnxruntime-web": ["onnxruntime-web@1.14.0", "", { "dependencies": { "flatbuffers": "^1.12.0", "guid-typescript": "^1.0.9", "long": "^4.0.0", "onnx-proto": "^4.0.4", "onnxruntime-common": "~1.14.0", "platform": "^1.3.6" } }, "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -440,14 +585,20 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="], "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="], + "portfinder": ["portfinder@1.0.32", "", { "dependencies": { "async": "^2.6.4", "debug": "^3.2.7", "mkdirp": "^0.5.6" } }, "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg=="], + "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="], + "protobufjs": ["protobufjs@6.11.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/long": "^4.0.1", "@types/node": ">=13.7.0", "long": "^4.0.0" }, "bin": { "pbjs": "bin/pbjs", "pbts": "bin/pbts" } }, "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], @@ -458,6 +609,8 @@ "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + "ramda": ["ramda@0.30.1", "", {}, "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], @@ -466,6 +619,10 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], "rollup": ["rollup@4.40.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w=="], @@ -504,12 +661,16 @@ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], @@ -522,10 +683,20 @@ "streamx": ["streamx@2.22.0", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], "tar-fs": ["tar-fs@3.0.8", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg=="], @@ -534,6 +705,8 @@ "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], + "tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -544,11 +717,21 @@ "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="], + + "toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tslib": ["tslib@2.7.0", "", {}, "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + "type-fest": ["type-fest@4.34.1", "", {}, "sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g=="], "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], @@ -556,9 +739,11 @@ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], + "undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="], - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], @@ -572,28 +757,134 @@ "vitest": ["vitest@3.1.1", "", { "dependencies": { "@vitest/expect": "3.1.1", "@vitest/mocker": "3.1.1", "@vitest/pretty-format": "^3.1.1", "@vitest/runner": "3.1.1", "@vitest/snapshot": "3.1.1", "@vitest/spy": "3.1.1", "@vitest/utils": "3.1.1", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.8.1", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.1.1", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.1.1", "@vitest/ui": "3.1.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yup": ["yup@1.6.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA=="], + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], + "@types/bn.js/@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], + + "@types/ws/@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], + "@xenova/transformers/onnxruntime-node": ["onnxruntime-node@1.14.0", "", { "dependencies": { "onnxruntime-common": "~1.14.0" }, "os": [ "linux", "win32", "darwin", ] }, "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w=="], + "bun-types/@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], + + "fuels/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], + "onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.14.0", "", {}, "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew=="], + "portfinder/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + + "portfinder/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "prebuild-install/tar-fs": ["tar-fs@2.1.2", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA=="], + "protobufjs/@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], + + "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yup/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + + "@types/bn.js/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@types/ws/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@xenova/transformers/onnxruntime-node/onnxruntime-common": ["onnxruntime-common@1.14.0", "", {}, "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew=="], + "bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "fuels/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], + + "fuels/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], + + "fuels/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], + + "fuels/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], + + "fuels/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], + + "fuels/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], + + "fuels/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], + + "fuels/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], + + "fuels/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], + + "fuels/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], + + "fuels/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], + + "fuels/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], + + "fuels/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], + + "fuels/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], + + "fuels/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], + + "fuels/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], + + "fuels/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], + + "fuels/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.1", "", { "os": "none", "cpu": "arm64" }, "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g=="], + + "fuels/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], + + "fuels/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], + + "fuels/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], + + "fuels/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], + + "fuels/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], + + "fuels/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], + + "fuels/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], + "prebuild-install/tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], "prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/package.json b/package.json index add2d7f..95e86f7 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,18 @@ { "name": "fuel-mcp-server", + "version": "0.0.1", "scripts": { "build": "tsc && node postbuild.cjs && mkdir -p bin/napi-v3/darwin/arm64 && cp node_modules/onnxruntime-node/bin/napi-v3/darwin/arm64/* bin/napi-v3/darwin/arm64/ && mkdir -p build/Release && cp node_modules/sharp/build/Release/sharp-darwin-arm64v8.node build/Release/ && cp -r node_modules/sharp/vendor ./vendor", "build:post": "node postbuild.cjs", + "start": "bun run src/index.ts", "mcp-server": "bun run src/mcp-server.ts", - "index": "bun run src/indexer.ts", + "index": "bun run src/docs/indexer.ts", + "query": "bun run src/docs/query.ts --run", "test": "bun test && bun run vitest run" }, "devDependencies": { "@types/bun": "latest", + "@types/node": "^20.11.20", "esbuild": "^0.25.2", "typescript": "^5", "vitest": "^3.1.1" @@ -21,8 +25,10 @@ "@qdrant/js-client-rest": "^1.13.0", "@types/markdown-it": "^14.1.2", "@xenova/transformers": "^2.17.2", + "dotenv": "^16.4.5", + "fuels": "^0.100.3", "markdown-it": "^14.1.0", "onnxruntime-node": "^1.18.0", "zod": "^3.23.8" } -} \ No newline at end of file +} diff --git a/src/common/chunker.test.ts b/src/common/chunker.test.ts new file mode 100644 index 0000000..bc8bdb6 --- /dev/null +++ b/src/common/chunker.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect } from "bun:test"; +import { chunkMarkdown } from "./chunker"; + +const estimateTokens = (text: string) => text.length; + +describe("chunkMarkdown", () => { + it("should chunk basic markdown text based on target size", () => { + const markdown = ` +# Title + +This is the first paragraph. It has some text. + +This is the second paragraph. It also has text, maybe a bit more. + +## Subtitle + +Another paragraph here. + `.trim(); + const targetSize = 50; + const chunks = chunkMarkdown(markdown, targetSize, estimateTokens); + + expect(chunks).toHaveLength(3); + expect(chunks[0]!.content).toContain("# Title"); + expect(chunks[0]!.content).toContain("This is the first paragraph."); + expect(estimateTokens(chunks[0]!.content)).toBeLessThanOrEqual(targetSize * 1.5); + expect(chunks[1]!.content).toContain("This is the second paragraph."); + expect(estimateTokens(chunks[1]!.content)).toBeLessThanOrEqual(targetSize * 1.5); + expect(chunks[2]!.content).toContain("## Subtitle"); + expect(chunks[2]!.content).toContain("Another paragraph here."); + expect(estimateTokens(chunks[2]!.content)).toBeLessThanOrEqual(targetSize * 1.5); + }); + + it("should not split code blocks", () => { + const markdown = ` +Intro text. + +\`\`\`javascript +// This is a code block +function hello() { + console.log("Hello"); +} +\`\`\` + +More text. + +\`\`\`python +# Another code block +print("World") +\`\`\` + `.trim(); + const targetSize = 50; + const chunks = chunkMarkdown(markdown, targetSize, estimateTokens); + + expect(chunks).toHaveLength(4); + expect(chunks[0]!.content).toBe("Intro text."); + expect(chunks[1]!.content).toContain("function hello()"); + expect(chunks[1]!.type).toBe("code"); + expect(chunks[2]!.content).toBe("More text."); + expect(chunks[3]!.content).toContain('print("World")'); + expect(chunks[3]!.type).toBe("code"); + }); + + it("should create a large chunk if a single code block exceeds target size", () => { + const longCode = Array(10).fill("console.log('line');").join("\n"); + const markdown = ` +Some intro text. + +\`\`\`javascript +${longCode} +\`\`\` + +Some outro text. + `.trim(); + const targetSize = 50; + const chunks = chunkMarkdown(markdown, targetSize, estimateTokens); + + expect(chunks).toHaveLength(3); + expect(chunks[0]!.content).toBe("Some intro text."); + expect(chunks[1]!.content).toContain(longCode); + expect(chunks[1]!.type).toBe("code"); + expect(estimateTokens(chunks[1]!.content)).toBeGreaterThan(targetSize); + expect(chunks[2]!.content).toBe("Some outro text."); + }); + + it("should handle markdown with only code blocks", () => { + const markdown = ` +\`\`\`javascript +console.log("first"); +\`\`\` + +\`\`\`python +print("second") +\`\`\` + `.trim(); + const targetSize = 30; + const chunks = chunkMarkdown(markdown, targetSize, estimateTokens); + + expect(chunks).toHaveLength(2); + expect(chunks[0]!.content).toContain('console.log("first");'); + expect(chunks[0]!.type).toBe("code"); + expect(chunks[1]!.content).toContain('print("second")'); + expect(chunks[1]!.type).toBe("code"); + }); + + it("should handle empty markdown", () => { + const markdown = ""; + const targetSize = 100; + const chunks = chunkMarkdown(markdown, targetSize, estimateTokens); + expect(chunks).toHaveLength(0); + }); + + it("should handle markdown smaller than target size", () => { + const markdown = "# Title\n\nJust a little bit of text."; + const targetSize = 1000; + const chunks = chunkMarkdown(markdown, targetSize, estimateTokens); + expect(chunks).toHaveLength(1); + expect(chunks[0]!.content).toBe(markdown); + expect(chunks[0]!.type).toBe("text"); + }); +}); diff --git a/src/common/chunker.ts b/src/common/chunker.ts new file mode 100644 index 0000000..653e996 --- /dev/null +++ b/src/common/chunker.ts @@ -0,0 +1,93 @@ +import MarkdownIt from 'markdown-it'; + +export interface MarkdownChunk { + content: string; + type: 'text' | 'code'; +} + +const md = new MarkdownIt(); + +export function chunkMarkdown( + markdown: string, + targetTokenSize: number, + estimateTokens: (text: string) => number +): MarkdownChunk[] { + if (!markdown?.trim()) { + return []; + } + + const chunks: MarkdownChunk[] = []; + const codeBlockRegex = /(```[\s\S]*?```)/g; + const parts = markdown.split(codeBlockRegex).filter(part => part); + + let currentTextChunk = ""; + + for (const part of parts) { + if (part.startsWith('```') && part.endsWith('```')) { + if (currentTextChunk.trim()) { + chunks.push(...splitTextChunk(currentTextChunk, targetTokenSize, estimateTokens)); + currentTextChunk = ""; + } + chunks.push({ content: part.trim(), type: 'code' }); + } else { + currentTextChunk += part; + } + } + + if (currentTextChunk.trim()) { + chunks.push(...splitTextChunk(currentTextChunk, targetTokenSize, estimateTokens)); + } + + return chunks; +} + +function splitTextChunk( + text: string, + targetTokenSize: number, + estimateTokens: (text: string) => number +): MarkdownChunk[] { + const textChunks: MarkdownChunk[] = []; + const separators = ['\n\n', '\n']; + let currentSegments: string[] = [text.trim()]; + + for (const sep of separators) { + const nextSegments: string[] = []; + let needsFurtherSplitting = false; + for(const segment of currentSegments) { + const estimatedSize = estimateTokens(segment!); + if (estimatedSize > targetTokenSize * 1.2 && segment!.includes(sep)) { + nextSegments.push(...segment!.split(sep).map(s => s.trim()).filter(Boolean)); + needsFurtherSplitting = true; + } else { + nextSegments.push(segment); + } + } + currentSegments = nextSegments; + } + + let currentChunkContent = ""; + for (let i = 0; i < currentSegments.length; i++) { + const segment = currentSegments[i]; + const segmentSize = estimateTokens(segment!); + const currentChunkSize = estimateTokens(currentChunkContent); + const combinedSize = estimateTokens(currentChunkContent ? `${currentChunkContent}\n\n${segment!}` : segment!); + + if (segmentSize > targetTokenSize * 1.2 && !currentChunkContent) { + textChunks.push({ content: segment!, type: 'text' }); + continue; + } + + if (currentChunkContent && combinedSize > targetTokenSize * 1.2) { + textChunks.push({ content: currentChunkContent, type: 'text' }); + currentChunkContent = segment!; + } else { + currentChunkContent = currentChunkContent ? `${currentChunkContent}\n\n${segment!}` : segment!; + } + } + + if (currentChunkContent) { + textChunks.push({ content: currentChunkContent, type: 'text' }); + } + + return textChunks; +} diff --git a/src/common/utils.ts b/src/common/utils.ts new file mode 100644 index 0000000..ac97e29 --- /dev/null +++ b/src/common/utils.ts @@ -0,0 +1,36 @@ +/** + * Common utility functions + */ + +export function log(...messages: any[]) { + if (process.env.LOG_LEVEL === "debug") { + console.log(...messages); + } +} + +export function processValue(value: unknown): unknown { + if (value === null || value === undefined) { + return value; + } + + if (typeof value === 'bigint' || (typeof (value as any).toString === 'function' && typeof (value as any).toJSON === 'undefined')) { + return value.toString(); + } + + if (Array.isArray(value)) { + return value.map(processValue); + } + + if (typeof value === 'object' && value !== null) { + const result: { [key: string]: unknown } = {}; + const obj = value as { [key: string]: unknown }; + for (const key in obj) { + if (typeof obj[key] !== 'function' && !key.startsWith('_') && key !== 'parent' && key !== 'functionInvocationScopes') { + result[key] = processValue(obj[key]); + } + } + return result; + } + + return value; +} diff --git a/src/docs/indexer.test.ts b/src/docs/indexer.test.ts new file mode 100644 index 0000000..269dfec --- /dev/null +++ b/src/docs/indexer.test.ts @@ -0,0 +1,310 @@ +import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test'; +import * as fsPromises from 'node:fs/promises'; +import path from 'node:path'; +import { QdrantClient } from '@qdrant/js-client-rest'; +import { pipeline } from '@xenova/transformers'; +import { indexDocsQdrant } from './indexer'; +import { chunkMarkdown } from '../common/chunker'; + +// --- Mocks --- + +// Mock fs/promises +mock.module('node:fs/promises', () => ({ + readdir: mock(async (dirPath: string) => { + if (dirPath === './test-docs-empty') return []; + if (dirPath === './test-docs-no-md') return ['not-a-markdown.txt']; + if (dirPath === './test-docs') return ['doc1.md', 'doc2.md', 'image.png']; + if (dirPath === './test-docs-large') { + return Array.from({ length: 150 }, (_, i) => `large_doc_${i + 1}.md`); + } + throw new Error(`ENOENT: no such file or directory, scandir '${dirPath}'`); + }), + readFile: mock(async (filePath: string, encoding: string) => { + expect(encoding).toBe('utf-8'); + const fileName = path.basename(filePath); + if (fileName === 'doc1.md') return '# Doc 1\n\nContent paragraph.'; + if (fileName === 'doc2.md') return '## Doc 2 Title\n\n```js\nconsole.log("hello");\n```\n\nMore text.'; + if (fileName.startsWith('large_doc_')) return `# Large Doc ${fileName}\n\nSome content.`; + throw new Error(`ENOENT: no such file or directory, open '${filePath}'`); + }), +})); + +// Mock @xenova/transformers pipeline +let mockEmbedder = mock((texts: string[], options: any) => { + console.log(`Default mock embedder called with ${texts.length} texts.`); + const embeddingDim = 384; + const embeddings = texts.map((_, i) => Array(embeddingDim).fill(i * 0.1)); + const flatEmbeddings = new Float32Array(texts.length * embeddingDim); + embeddings.forEach((emb, textIdx) => { + emb.forEach((val, dimIdx) => { + flatEmbeddings[textIdx * embeddingDim + dimIdx] = val; + }); + }); + return Promise.resolve({ data: flatEmbeddings, dims: [texts.length, embeddingDim] }); +}); + +const pipelineMock = mock(async (task: string, model: string) => { + console.log(`Mock pipeline loaded for task: ${task}, model: ${model}`); + expect(task).toBe('feature-extraction'); + return mockEmbedder; +}); + +mock.module('@xenova/transformers', () => ({ + pipeline: pipelineMock, + env: { cacheDir: '' }, +})); + +// Mock QdrantClient +const mockQdrantClientInstance = { + getCollections: mock(async (options?: { consistency?: any }) => { + console.log('Mock getCollections called'); + if (mockQdrantClientInstance.simulateCollectionExists) { + return { collections: [{ name: mockQdrantClientInstance.simulateCollectionNameExists }] }; + } + return { collections: [] }; + }), + createCollection: mock(async (name: string, params: any) => { + console.log(`Mock createCollection called for '${name}'`); + expect(name).toBeString(); + expect(params.vectors.size).toBe(384); + expect(params.vectors.distance).toBe('Cosine'); + mockQdrantClientInstance.simulateCollectionExists = true; + mockQdrantClientInstance.simulateCollectionNameExists = name; + return true; + }), + upsert: mock(async (name: string, data: { wait: boolean, points: any[] }) => { + console.log(`Mock upsert called for '${name}' with ${data.points.length} points.`); + expect(name).toBeString(); + expect(data.wait).toBe(true); + expect(Array.isArray(data.points)).toBe(true); + data.points.forEach(p => { + expect(p.id).toBeString(); + expect(p.vector).toBeArray(); + expect(p.vector.length).toBe(384); + expect(p.payload).toBeObject(); + expect(p.payload.source).toBeString(); + expect(p.payload.type).toBeString(); + expect(p.payload.content).toBeString(); + }); + mockQdrantClientInstance.upsertCallCount += 1; + mockQdrantClientInstance.pointsUpserted.push(...data.points); + return { status: 'ok', result: { operation_id: 123, status: 'completed' } }; + }), + simulateCollectionExists: false, + simulateCollectionNameExists: '' as string, + upsertCallCount: 0, + pointsUpserted: [] as any[], + resetMock: () => { + mockQdrantClientInstance.getCollections.mockClear(); + mockQdrantClientInstance.createCollection.mockClear(); + mockQdrantClientInstance.upsert.mockClear(); + mockQdrantClientInstance.simulateCollectionExists = false; + mockQdrantClientInstance.simulateCollectionNameExists = ''; + mockQdrantClientInstance.upsertCallCount = 0; + mockQdrantClientInstance.pointsUpserted = []; + } +}; +mock.module('@qdrant/js-client-rest', () => ({ + QdrantClient: mock(() => mockQdrantClientInstance) +})); + + +// --- Test Suite --- + +describe('indexDocsQdrant', () => { + const consoleLogMock = mock(); + const consoleErrorMock = mock(); + const originalLog = console.log; + const originalError = console.error; + + beforeEach(() => { + pipelineMock.mockClear(); + mockEmbedder.mockClear(); + mockQdrantClientInstance.resetMock(); + + consoleLogMock.mockClear(); + consoleErrorMock.mockClear(); + console.log = consoleLogMock; + console.error = consoleErrorMock; + + delete process.env.QDRANT_URL; + delete process.env.QDRANT_API_KEY; + delete process.env.QDRANT_COLLECTION; + delete process.env.EMBEDDING_MODEL; + delete process.env.CHUNK_SIZE; + }); + + afterEach(() => { + console.log = originalLog; + console.error = originalError; + }); + + it('should successfully index markdown files', async () => { + const docsDir = './test-docs'; + const collectionName = 'test-collection'; + + await indexDocsQdrant(docsDir, collectionName); + + expect(mockQdrantClientInstance.getCollections).toHaveBeenCalledTimes(1); + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledTimes(1); + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith(collectionName, { vectors: { size: 384, distance: 'Cosine' } }); + expect(mockQdrantClientInstance.upsert).toHaveBeenCalledTimes(1); + + expect(mockQdrantClientInstance.pointsUpserted.length).toBeGreaterThan(0); + const firstPoint = mockQdrantClientInstance.pointsUpserted[0]; + expect(firstPoint.payload.source).toMatch(/doc[12]\.md/); + expect(firstPoint.payload.content).toBeString(); + expect(firstPoint.vector.length).toBe(384); + + expect(pipelineMock).toHaveBeenCalledTimes(1); + expect(mockEmbedder).toHaveBeenCalledTimes(1); + + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Starting Qdrant indexing process...`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Found 2 markdown files to process.`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Collection '${collectionName}' not found. Creating...`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Upserting batch to Qdrant...`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Qdrant Indexing finished!`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Total chunks upserted to Qdrant: ${mockQdrantClientInstance.pointsUpserted.length}`)); + expect(consoleErrorMock).not.toHaveBeenCalled(); + }); + + it('should use existing collection if found', async () => { + const docsDir = './test-docs'; + const collectionName = 'test-collection'; + mockQdrantClientInstance.simulateCollectionExists = true; + mockQdrantClientInstance.simulateCollectionNameExists = collectionName; + + await indexDocsQdrant(docsDir, collectionName); + + expect(mockQdrantClientInstance.getCollections).toHaveBeenCalledTimes(1); + expect(mockQdrantClientInstance.createCollection).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.upsert).toHaveBeenCalledTimes(1); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Collection '${collectionName}' already exists.`)); + expect(consoleLogMock).not.toHaveBeenCalledWith(expect.stringContaining(`Creating...`)); + }); + + it('should handle directories with no markdown files', async () => { + const docsDir = './test-docs-no-md'; + await indexDocsQdrant(docsDir); + + expect(pipelineMock).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.getCollections).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.createCollection).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.upsert).not.toHaveBeenCalled(); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining("No markdown files found in the specified directory.")); + }); + + it('should handle empty directories', async () => { + const docsDir = './test-docs-empty'; + await indexDocsQdrant(docsDir); + + expect(pipelineMock).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.getCollections).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.createCollection).not.toHaveBeenCalled(); + expect(mockQdrantClientInstance.upsert).not.toHaveBeenCalled(); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining("No markdown files found in the specified directory.")); + }); + + it('should handle errors during Qdrant connection', async () => { + const docsDir = './test-docs'; + const collectionName = 'error-connect-collection'; + const connectError = new Error("Qdrant unavailable"); + mockQdrantClientInstance.getCollections.mockImplementationOnce(async () => { + throw connectError; + }); + + await expect(indexDocsQdrant(docsDir, collectionName)) + .rejects + .toThrow(`Failed to initialize Qdrant client/collection: Failed to ensure Qdrant collection: ${connectError.message}`); + + expect(consoleErrorMock).toHaveBeenCalledWith(expect.stringContaining(`Error initializing Qdrant client or ensuring collection '${collectionName}'`), expect.any(Error)); + expect(pipelineMock).not.toHaveBeenCalled(); + }); + + it('should handle errors during embedding', async () => { + const docsDir = './test-docs'; + const collectionName = 'error-embed-collection'; + const embedError = new Error("Embedding failed"); + mockEmbedder.mockImplementationOnce(async () => { + throw embedError; + }); + + await expect(indexDocsQdrant(docsDir, collectionName)) + .rejects + .toThrow(`Failed during embedding generation: ${embedError.message}`); + + expect(pipelineMock).toHaveBeenCalledTimes(1); + expect(mockEmbedder).toHaveBeenCalledTimes(1); + expect(consoleErrorMock).toHaveBeenCalledWith(expect.stringContaining("Error generating embeddings for batch 1"), embedError); + expect(mockQdrantClientInstance.upsert).not.toHaveBeenCalled(); + }); + + it('should handle errors during Qdrant upsert', async () => { + const docsDir = './test-docs'; + const collectionName = 'error-upsert-collection'; + const upsertError = new Error("Upsert failed"); + mockQdrantClientInstance.upsert.mockImplementationOnce(async () => { + throw upsertError; + }); + + await expect(indexDocsQdrant(docsDir, collectionName)) + .rejects + .toThrow(`Failed during Qdrant upsert operation: ${upsertError.message}`); + + expect(pipelineMock).toHaveBeenCalledTimes(1); + expect(mockEmbedder).toHaveBeenCalledTimes(1); + expect(mockQdrantClientInstance.upsert).toHaveBeenCalledTimes(1); + expect(consoleErrorMock).toHaveBeenCalledWith(expect.stringContaining("Error upserting batch 1 to Qdrant"), upsertError); + }); + + it('should use environment variables for configuration', async () => { + process.env.QDRANT_URL = 'http://qdrant-prod:6333'; + process.env.QDRANT_API_KEY = 'test-key'; + process.env.QDRANT_COLLECTION = 'prod-collection'; + process.env.EMBEDDING_MODEL = 'Xenova/custom-model'; + process.env.CHUNK_SIZE = '500'; + + const docsDir = './test-docs'; + const expectedCollectionName = process.env.QDRANT_COLLECTION || 'bun_qdrant_docs'; + const expectedModelName = process.env.EMBEDDING_MODEL || 'Xenova/all-MiniLM-L6-v2'; + const expectedChunkSize = process.env.CHUNK_SIZE ? parseInt(process.env.CHUNK_SIZE, 10) : 2000; + + await indexDocsQdrant(docsDir, expectedCollectionName, expectedModelName, expectedChunkSize); + + const consoleCalls = consoleLogMock.mock.calls; + const collectionLogFound = consoleCalls.some(args => + typeof args[0] === 'string' && args[0].includes(`Collection: ${expectedCollectionName}`) + ); + expect(collectionLogFound).toBe(true); + + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Embedding Model: ${expectedModelName}`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Target Chunk Size (tokens): ${expectedChunkSize}`)); + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Connecting to Qdrant at http://qdrant-prod:6333...`)); + + expect(pipelineMock).toHaveBeenCalledWith('feature-extraction', expectedModelName); + + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith(expectedCollectionName, expect.anything()); + expect(mockQdrantClientInstance.upsert).toHaveBeenCalledWith(expectedCollectionName, expect.anything()); + }); + + it('should process files in batches', async () => { + const docsDir = './test-docs-large'; + const collectionName = 'batch-test-collection'; + const batchSize = 100; + + const sampleContent = `# Large Doc large_doc_1.md\n\nSome content.`; + const sampleChunks = chunkMarkdown(sampleContent, 2000, (t) => t.split(/\s+/).length); + const expectedTotalChunks = 150 * sampleChunks.length; + const expectedBatches = Math.ceil(expectedTotalChunks / batchSize); + + await indexDocsQdrant(docsDir, collectionName); + + expect(mockQdrantClientInstance.upsert).toHaveBeenCalledTimes(expectedBatches); + expect(mockQdrantClientInstance.pointsUpserted.length).toBe(expectedTotalChunks); + + for (let i = 1; i <= expectedBatches; i++) { + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Processing batch ${i} of ${expectedBatches}`)); + } + expect(consoleLogMock).toHaveBeenCalledWith(expect.stringContaining(`Total chunks upserted to Qdrant: ${expectedTotalChunks}`)); + }); +}); diff --git a/src/docs/indexer.ts b/src/docs/indexer.ts new file mode 100644 index 0000000..dfb8b65 --- /dev/null +++ b/src/docs/indexer.ts @@ -0,0 +1,280 @@ +import { QdrantClient } from '@qdrant/js-client-rest'; +import { pipeline, env } from '@xenova/transformers'; +import * as fsPromises from "fs/promises"; +import path from "path"; +import { chunkMarkdown, type MarkdownChunk } from '../common/chunker.js'; +import { log } from '../common/utils.js'; +import crypto from "crypto"; + +// Disable local cache for transformers.js models +env.cacheDir = ''; + +// Constants +const BATCH_SIZE = 100; // Process N chunks at a time for embedding/adding +const DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2"; +const DEFAULT_QDRANT_COLLECTION = "bun_qdrant_docs"; +const TARGET_TOKEN_SIZE = 2000; +const EMBEDDING_DIMENSION = 384; // Dimension for Xenova/all-MiniLM-L6-v2 + +/** + * Estimates token count (simple whitespace split as a proxy). + * Replace with a proper tokenizer for the specific model if accuracy is critical. + */ +function estimateTokens(text: string): number { + return text.split(/\s+/).length; +} + +/** + * Ensures a Qdrant collection exists, creating it if necessary. + */ +async function getOrCreateQdrantCollection(client: QdrantClient, collectionName: string): Promise { + try { + const collectionsResponse = await client.getCollections(); + const collectionExists = collectionsResponse.collections.some(c => c.name === collectionName); + + if (!collectionExists) { + log(`Collection '${collectionName}' not found. Creating...`); + await client.createCollection(collectionName, { + vectors: { size: EMBEDDING_DIMENSION, distance: 'Cosine' }, + }); + log(`Collection '${collectionName}' created successfully.`); + // Short delay to allow collection creation to finalize + await new Promise(resolve => setTimeout(resolve, 1000)); + } else { + log(`Collection '${collectionName}' already exists.`); + } + } catch (error) { + console.error(`Error ensuring collection '${collectionName}' exists:`, error); + throw new Error(`Failed to ensure Qdrant collection: ${error instanceof Error ? error.message : String(error)}`); + } +} + +/** + * Indexes markdown documents from a directory into a Qdrant collection. + */ +export async function indexDocsQdrant( + docsDir: string, + collectionName: string = DEFAULT_QDRANT_COLLECTION, + modelName: string = DEFAULT_MODEL, + targetChunkSize: number = TARGET_TOKEN_SIZE +): Promise { + log(`Starting Qdrant indexing process...`); + log(`Docs directory: ${docsDir}`); + log(`Collection: ${collectionName}`); + log(`Embedding Model: ${modelName}`); + log(`Target Chunk Size (tokens): ${targetChunkSize}`); + + // Find markdown files FIRST + let files: string[]; + try { + files = await fsPromises.readdir(docsDir); + } catch (error) { + console.error(`Error reading directory '${docsDir}':`, error); + throw new Error(`Failed to read directory: ${error instanceof Error ? error.message : String(error)}`); + } + + const markdownFiles = files.filter(file => file.endsWith('.md')); + if (markdownFiles.length === 0) { + log("No markdown files found in the specified directory. Exiting."); + return; // Exit early if no files found + } + log(`Found ${markdownFiles.length} markdown files to process.`); + + // Initialize Qdrant client and check collection ONLY if files are found + const qdrantUrl = process.env.QDRANT_URL || "http://localhost:6333"; + const qdrantApiKey = process.env.QDRANT_API_KEY; // Optional API key + + log(`Connecting to Qdrant at ${qdrantUrl}...`); + let client: QdrantClient; + try { + client = new QdrantClient({ + url: qdrantUrl, + apiKey: qdrantApiKey, + }); + // Ensure the collection exists + await getOrCreateQdrantCollection(client, collectionName); + log(`Successfully ensured collection '${collectionName}' exists.`); + } catch (error) { + console.error(`\n❌ Error initializing Qdrant client or ensuring collection '${collectionName}':`, error); + console.error(`\n Troubleshooting Tips:`); + console.error(` 1. Ensure Qdrant is running. If using Docker: docker ps | grep qdrant`); + console.error(` 2. Check if Qdrant is accessible at ${qdrantUrl}`); + console.error(` 3. If using an API key, ensure QDRANT_API_KEY is set correctly.`); + console.error(` (Configure connection via QDRANT_URL and QDRANT_API_KEY env vars)`); + // Include original error message if available + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to initialize Qdrant client/collection: ${errorMessage}`); + } + + // Initialize embedding model pipeline (already confirmed files exist) + let embedder: any; + try { + log(`Loading embedding model '${modelName}'... (This might take a while the first time)`); + embedder = await pipeline('feature-extraction', modelName); + log(`Embedding model loaded successfully.`); + } catch (error) { + console.error(`Error loading embedding model '${modelName}':`, error); + throw new Error(`Failed to load embedding model: ${error instanceof Error ? error.message : String(error)}`); + } + + let totalChunksProcessed = 0; + let allChunks: { id: string; chunk: MarkdownChunk; source: string }[] = []; + + // Process files: Read, Chunk + for (const file of markdownFiles) { + const filePath = path.join(docsDir, file); + // log(`Processing file: ${file}...`); + try { + const content = await fsPromises.readFile(filePath, "utf-8"); + const chunks = chunkMarkdown(content, targetChunkSize, estimateTokens); + + if (chunks.length > 0) { + //log(` - Chunked into ${chunks.length} segments.`); + allChunks.push(...chunks.map((chunk, index) => ({ + // Qdrant IDs can be numbers or UUIDs. Using string for consistency with previous indexer. + // Ensure these IDs are unique and stable if re-indexing. + id: crypto.randomUUID(), + chunk, + source: file, + }))); + } else { + log(` - No chunks generated (file might be empty or only contain whitespace).`); + } + } catch (error) { + console.error(`Error processing file ${file}:`, error); + // Decide whether to continue with other files or stop + // For now, we log the error and continue + } + } + + if (allChunks.length === 0) { + log("No content chunks were generated from the markdown files."); + return; + } + + log(`Total chunks generated: ${allChunks.length}. Preparing for embedding and adding to Qdrant...`); + + // Process Chunks in Batches: Embed, Upsert to Qdrant + for (let i = 0; i < allChunks.length; i += BATCH_SIZE) { + const batch = allChunks.slice(i, i + BATCH_SIZE); + log(`Processing batch ${Math.floor(i / BATCH_SIZE) + 1} of ${Math.ceil(allChunks.length / BATCH_SIZE)} (size: ${batch.length})...`); + + const batchTexts = batch.map(item => item.chunk.content); + let batchEmbeddings: number[][] = []; + + try { + log(" - Generating embeddings..."); + const output = await embedder(batchTexts, { pooling: 'mean', normalize: true }); + + // Extract embeddings (handle potential structure variations) + if (output && output.data instanceof Float32Array && output.dims && output.dims.length === 2) { + const embeddingDim = output.dims[1]; + if (embeddingDim !== EMBEDDING_DIMENSION) { + console.warn(`Warning: Expected embedding dimension ${EMBEDDING_DIMENSION}, but got ${embeddingDim}. Ensure model matches dimension.`); + } + for (let j = 0; j < output.dims[0]; ++j) { + batchEmbeddings.push(Array.from(output.data.slice(j * embeddingDim, (j + 1) * embeddingDim))); + } + } else if (Array.isArray(output) && output[0]?.embedding) { // Alternative structure check + batchEmbeddings = output.map(emb => Array.from(emb.embedding || [])); + if (batchEmbeddings[0]?.length !== EMBEDDING_DIMENSION && batchEmbeddings.length > 0) { + console.warn(`Warning: Expected embedding dimension ${EMBEDDING_DIMENSION}, but got ${batchEmbeddings[0]?.length}. Ensure model matches dimension.`); + } + } else { + console.warn("Unexpected embedding output structure:", output); + throw new Error("Could not extract embeddings from pipeline output."); + } + log(` - Generated ${batchEmbeddings.length} embeddings.`); + + } catch (error) { + console.error(`Error generating embeddings for batch ${Math.floor(i / BATCH_SIZE) + 1}:`, error); + throw new Error(`Failed during embedding generation: ${error instanceof Error ? error.message : String(error)}`); + } + + if (batchEmbeddings.length !== batch.length) { + console.error(`Mismatch between number of chunks (${batch.length}) and generated embeddings (${batchEmbeddings.length}) in batch ${Math.floor(i / BATCH_SIZE) + 1}. Skipping batch.`); + continue; // Skip this batch + } + + try { + log(" - Upserting batch to Qdrant..."); + // Prepare points for Qdrant upsert + const points = batch + .map((item, index) => { + const vector = batchEmbeddings[index]; + if (!vector || vector.length !== EMBEDDING_DIMENSION) { + console.warn(`Warning: Missing or invalid dimension embedding for chunk ${item.id} (source: ${item.source}) in batch. Skipping.`); + return null; + } + if (typeof item.id !== 'string' || item.id.length !== 36) { + console.warn(`Warning: Invalid ID format for chunk from ${item.source}. ID: ${item.id}. Skipping.`); + return null; + } + return { + id: item.id, + vector: vector, + payload: { // Payload stores metadata and the original text chunk + source: item.source, + type: item.chunk.type, + content: item.chunk.content, // Store original content in payload + // Removed start_line and end_line as they are not in MarkdownChunk + } + }; + }) + .filter(point => point !== null); + + if (points.length === 0) { + log(" - No valid points to upsert in this batch."); + continue; + } + + // Log point details before upsert attempt + log(` - Preparing to upsert batch ${Math.floor(i / BATCH_SIZE) + 1} with ${points.length} points.`); + + await client.upsert(collectionName, { + wait: true, // Wait for operation to complete + points: points as any // Cast as any to bypass stricter type check after filtering nulls + // QdrantClient types might need refinement for this specific structure + }); + + totalChunksProcessed += points.length; + log(` - Batch upserted successfully (${points.length} points).`); + } catch (error) { + console.error(`Error upserting batch ${Math.floor(i / BATCH_SIZE) + 1} to Qdrant:`, error); + // Consider more robust error handling (e.g., retries for specific errors) + throw new Error(`Failed during Qdrant upsert operation: ${error instanceof Error ? error.message : String(error)}`); + } + } + + log(`--------------------------------------------------`); + log(`Qdrant Indexing finished!`); + log(`Total markdown files processed: ${markdownFiles.length}`); + log(`Total chunks upserted to Qdrant: ${totalChunksProcessed}`); + log(`--------------------------------------------------`); +} + +// Example of running the script directly +async function runQdrantIndexer() { + // Check if executed directly + if (import.meta.url === `file://${process.argv[1]}`) { + const docsPath = process.argv[2] || './docs'; // Get path from command line or default + const collectionName = process.env.QDRANT_COLLECTION || DEFAULT_QDRANT_COLLECTION; + const model = process.env.EMBEDDING_MODEL || DEFAULT_MODEL; + let chunkSize = process.env.CHUNK_SIZE ? parseInt(process.env.CHUNK_SIZE, 10) : TARGET_TOKEN_SIZE; + + if (isNaN(chunkSize) || chunkSize <= 0) { + console.error(`Invalid CHUNK_SIZE environment variable: ${process.env.CHUNK_SIZE}. Using default ${TARGET_TOKEN_SIZE}.`); + chunkSize = TARGET_TOKEN_SIZE; + } + + try { + await indexDocsQdrant(docsPath, collectionName, model, chunkSize); + } catch (error) { + console.error("\n--- Qdrant Indexing failed --- ", error); + process.exit(1); + } + } +} + +// Run the indexer if this script is executed directly +runQdrantIndexer(); diff --git a/src/docs/query.test.ts b/src/docs/query.test.ts new file mode 100644 index 0000000..80bc6f8 --- /dev/null +++ b/src/docs/query.test.ts @@ -0,0 +1,172 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +const mockSearchFn = vi.fn(); +const mockGetCollectionFn = vi.fn(); +const { mockPipelineFn } = vi.hoisted(() => { + return { mockPipelineFn: vi.fn() } +}); + +vi.mock('@qdrant/js-client-rest', () => { + const QdrantClientMock = vi.fn().mockImplementation(() => ({ + search: mockSearchFn, + getCollection: mockGetCollectionFn, + })); + return { + QdrantClient: QdrantClientMock, + __esModule: true, + }; +}); + +vi.mock('@xenova/transformers', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + pipeline: mockPipelineFn, + __esModule: true, + }; +}); + +import { QdrantClient } from '@qdrant/js-client-rest'; +import { queryDocs } from './query'; + +describe('queryDocs (Qdrant)', () => { + const testQuery = "What is Bun?"; + const testCollection = "test_collection"; + const testModel = "test_model"; + const testNResults = 3; + const testQdrantUrl = "http://test-qdrant:6333"; + const testApiKey = "test-api-key"; + const mockEmbedding = [0.1, 0.2, 0.3]; + const mockSearchResults = [{ id: 1, score: 0.9, payload: { text: "Bun is a runtime." } }]; + + beforeEach(() => { + vi.clearAllMocks(); + + (QdrantClient as any).mockImplementation(() => ({ + search: mockSearchFn, + getCollection: mockGetCollectionFn, + })); + + mockPipelineFn.mockResolvedValue(async () => { + return { data: new Float32Array(mockEmbedding) }; + }); + mockGetCollectionFn.mockResolvedValue({ name: testCollection }); + mockSearchFn.mockResolvedValue(mockSearchResults); + + vi.spyOn(console, 'log').mockImplementation(() => {}); + vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should query Qdrant successfully with default parameters', async () => { + const results = await queryDocs(testQuery); + + expect(QdrantClient).toHaveBeenCalledWith({ + url: process.env.QDRANT_URL || "http://localhost:6333", + apiKey: process.env.QDRANT_API_KEY, + }); + expect(mockGetCollectionFn).toHaveBeenCalledWith(process.env.QDRANT_COLLECTION || "bun_qdrant_docs"); + expect(mockPipelineFn).toHaveBeenCalledWith('feature-extraction', "Xenova/all-MiniLM-L6-v2"); + expect(mockPipelineFn).toHaveBeenCalledTimes(1); + expect(mockSearchFn).toHaveBeenCalledWith( + process.env.QDRANT_COLLECTION || "bun_qdrant_docs", + expect.objectContaining({ + limit: 5, + vector: expect.arrayContaining([ + expect.closeTo(0.1, 5), + expect.closeTo(0.2, 5), + expect.closeTo(0.3, 5) + ]), + with_payload: true, + }) + ); + expect(results).toEqual(mockSearchResults); + }); + + it('should query Qdrant successfully with custom parameters', async () => { + const results = await queryDocs(testQuery, testCollection, testModel, testNResults, testQdrantUrl, testApiKey); + + expect(QdrantClient).toHaveBeenCalledWith({ + url: testQdrantUrl, + apiKey: testApiKey, + }); + expect(mockGetCollectionFn).toHaveBeenCalledWith(testCollection); + expect(mockPipelineFn).toHaveBeenCalledWith('feature-extraction', testModel); + expect(mockPipelineFn).toHaveBeenCalledTimes(1); + expect(mockSearchFn).toHaveBeenCalledWith( + testCollection, + expect.objectContaining({ + limit: testNResults, + vector: expect.arrayContaining([ + expect.closeTo(0.1, 5), + expect.closeTo(0.2, 5), + expect.closeTo(0.3, 5) + ]), + with_payload: true, + }) + ); + expect(results).toEqual(mockSearchResults); + }); + + it('should throw error if query text is empty', async () => { + await expect(queryDocs("")).rejects.toThrow("Query text cannot be empty."); + }); + + it('should throw error if Qdrant client initialization fails', async () => { + const initError = new Error("Connection refused"); + (QdrantClient as any).mockImplementation(() => { + throw initError; + }); + + await expect(queryDocs(testQuery, testCollection, testModel, testNResults, testQdrantUrl)) + .rejects.toThrow(`Failed to initialize Qdrant client: ${initError.message}`); + }); + + it('should throw error if collection does not exist', async () => { + const collectionError = new Error("Not Found") as any; + collectionError.status = 404; + mockGetCollectionFn.mockRejectedValue(collectionError); + await expect(queryDocs(testQuery, testCollection)) + .rejects.toThrow(`Collection '${testCollection}' not found.`); + }); + + it('should throw error if checking collection fails with non-404 error', async () => { + const otherError = new Error("Internal Server Error"); + mockGetCollectionFn.mockRejectedValue(otherError); + await expect(queryDocs(testQuery, testCollection)) + .rejects.toThrow(`Failed to check Qdrant collection: ${otherError.message}`); + }); + + it('should throw error if embedding model loading fails', async () => { + const modelError = new Error("Model not found"); + mockPipelineFn.mockRejectedValue(modelError); + await expect(queryDocs(testQuery, testCollection, testModel)) + .rejects.toThrow(`Failed to load embedding model: ${modelError.message}`); + }); + + it('should throw error if query embedding generation fails', async () => { + const embeddingError = new Error("Embedding failed"); + mockPipelineFn.mockResolvedValue(async () => { throw embeddingError; }); + await expect(queryDocs(testQuery, testCollection, testModel)) + .rejects.toThrow(`Failed during query embedding: ${embeddingError.message}`); + }); + + it('should throw error if embedding output format is unexpected', async () => { + mockPipelineFn.mockResolvedValue(async () => { + return { someOtherFormat: [1, 2, 3] }; + }); + await expect(queryDocs(testQuery, testCollection, testModel)) + .rejects.toThrow("Could not extract embedding from pipeline output for query."); + }); + + it('should throw error if Qdrant search fails', async () => { + const searchError = new Error("Search failed"); + mockSearchFn.mockRejectedValue(searchError); + await expect(queryDocs(testQuery, testCollection, testModel, testNResults, testQdrantUrl)) + .rejects.toThrow(`Failed during Qdrant query: ${searchError.message}`); + }); +}); diff --git a/src/docs/query.ts b/src/docs/query.ts new file mode 100644 index 0000000..e6c55ae --- /dev/null +++ b/src/docs/query.ts @@ -0,0 +1,304 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { QdrantClient } from '@qdrant/js-client-rest'; +import { pipeline, env } from '@xenova/transformers'; +import { log } from "../common/utils.js"; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Disable local cache for transformers.js models +env.cacheDir = ''; + +// Constants +const DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2"; +const DEFAULT_COLLECTION = process.env.QDRANT_COLLECTION || "bun_qdrant_docs"; +const DEFAULT_QDRANT_URL = process.env.QDRANT_URL || "http://localhost:6333"; + +// Promise for tracking Qdrant readiness - to be set by mcp-server.ts +let qdrantReadyPromise: Promise | null = null; + +export function setQdrantReadyPromise(promise: Promise) { + qdrantReadyPromise = promise; +} + +/** + * Register all documentation-related tools with the MCP server + */ +export function registerDocTools(server: McpServer) { + // Register search tool + registerSearchTool(server); + + // Register standard context tool + registerStdContextTool(server); +} + +/** + * Register the documentation search tool + */ +function registerSearchTool(server: McpServer) { + server.tool( + "searchFuelDocs", + "Search Fuel and Sway documentation using semantic search", + { + query: z.string().describe("The search query for Fuel and Sway documentation."), + collectionName: z.string().optional().describe("Optional: Specify the Qdrant collection name."), + modelName: z.string().optional().describe("Optional: Specify the embedding model name."), + nResults: z.number().int().positive().optional().describe("Optional: Specify the number of search results (default 5).") + }, + async ({ query, collectionName, modelName, nResults }) => { + log(`MCP Tool 'searchFuelDocs' called with query: "${query}"`); + + // --- Wait for Qdrant to be ready before proceeding --- + if (!qdrantReadyPromise) { + log("Error: Qdrant initialization was not started."); + return { + content: [{ type: "text", text: "Error: Qdrant initialization process not found." }], + isError: true + }; + } + try { + log("Waiting for Qdrant readiness..."); + await qdrantReadyPromise; + log("Qdrant is ready. Proceeding with query."); + } catch (initError: any) { + log(`Error during Qdrant initialization: ${initError?.message}`); + console.error("Qdrant initialization failed:", initError); + return { + content: [{ type: "text", text: `Error waiting for Qdrant initialization: ${initError?.message}` }], + isError: true + }; + } + // --- End Qdrant Readiness Check --- + + try { + // Execute the query + const results = await queryDocs( + query, + collectionName, // Will use default if undefined + modelName, // Will use default if undefined + nResults // Will use default if undefined + ); + + // Format results for MCP response + // Assuming results is an array of Qdrant point objects like: + // [{ id: '...', score: 0.9, payload: { content: '...', source: '...' } }, ...] + const formattedResults = Array.isArray(results) + ? results.map((hit: any) => { + const payload = hit.payload || {}; + const score = hit.score; + const content = payload.content || 'No content found'; // Adjust 'content' key if needed based on indexing + const source = payload.source || 'unknown'; // Adjust 'source' key if needed + return `Source: ${source}\\nScore: ${score?.toFixed(4)}\\nContent:\\n${content}\\n---`; + }).join('\\n\\n') + : JSON.stringify(results, null, 2); // Fallback if format is unexpected + + return { + content: [{ + type: "text", + text: `Search Results for "${query}":\\n\\n${formattedResults}` + }] + }; + } catch (err: unknown) { + const error = err as Error; + console.error(`Error in searchFuelDocs tool: ${error.message}`); + return { + content: [{ + type: "text", + text: `Error executing search: ${error.message}` + }], + isError: true // Indicate that an error occurred + }; + } + } + ); +} + +/** + * Register the standard context tool + */ +function registerStdContextTool(server: McpServer) { + server.tool( + "provideStdContext", + "Provide Sway standard library context", + {}, // No input parameters needed + async () => { + const filePath = path.join(__dirname, '..', '..', 'sway', 'std_paths_data.txt'); + log(`MCP Tool 'provideStdContext' called. Reading file: ${filePath}`); + + try { + const data = await fs.readFile(filePath, 'utf-8'); + log(`Successfully read ${filePath}. Length: ${data.length}`); + return { + content: [{ + type: "text", + text: `Sway Standard Library Paths and Types:\n\n${data}` + }] + }; + } catch (err: unknown) { + const error = err as Error; + console.error(`Error in provideStdContext tool reading ${filePath}: ${error.message}`); + log(`Error reading ${filePath}: ${error.message}`); + return { + content: [{ + type: "text", + text: `Error reading Sway standard library context file: ${error.message}` + }], + isError: true + }; + } + } + ); +} + +/** + * Queries the Qdrant collection with a given prompt. + */ +export async function queryDocs( + queryText: string, + collectionName: string = DEFAULT_COLLECTION, + modelName: string = DEFAULT_MODEL, + nResults: number = 5, // Number of results to retrieve + qdrantUrl: string = DEFAULT_QDRANT_URL, + qdrantApiKey?: string // Optional API key for Qdrant Cloud +): Promise { // Return type can be refined based on Qdrant result structure + if (!queryText) { + throw new Error("Query text cannot be empty."); + } + + log(`Starting Qdrant query process...`); + log(`Collection: ${collectionName}`); + log(`Embedding Model: ${modelName}`); + log(`Query: "${queryText}"`); + log(`Number of results: ${nResults}`); + log(`Qdrant URL: ${qdrantUrl}`); + if (qdrantApiKey) { + log(`Qdrant API Key: Provided (hidden)`); + } + + // Initialize Qdrant client + let client: QdrantClient; + try { + log(`Connecting to Qdrant at ${qdrantUrl}...`); + client = new QdrantClient({ + url: qdrantUrl, + apiKey: qdrantApiKey, + }); + log(`Successfully initialized Qdrant client.`); + } catch (error) { + console.error(`\n❌ Error initializing Qdrant client at ${qdrantUrl}:`, error); + console.error(`\n Troubleshooting Tips:`); + console.error(` 1. Ensure Qdrant is running (e.g., via Docker).`); + console.error(` 2. Verify the Qdrant URL ('${qdrantUrl}') is correct.`); + console.error(` 3. If using Qdrant Cloud, ensure the API key is valid.`); + console.error(` (Configure via QDRANT_URL, QDRANT_API_KEY env vars)`); + throw new Error(`Failed to initialize Qdrant client: ${error instanceof Error ? error.message : String(error)}`); + } + + // Check if collection exists (optional but good practice) + try { + await client.getCollection(collectionName); + log(`Collection '${collectionName}' exists.`); + } catch (error: any) { + if (error?.status === 404) { + console.error(`\n❌ Error: Collection '${collectionName}' not found in Qdrant.`); + console.error(` Troubleshooting Tips:`); + console.error(` 1. Did you run the indexing script first (e.g., bun run src/docs/indexer.ts)?`); + console.error(` 2. Verify the collection name matches the one used during indexing.`); + console.error(` (Configure via QDRANT_COLLECTION env var)`); + throw new Error(`Collection '${collectionName}' not found.`); + } else { + // Handle other potential errors during collection check + console.error(`\n❌ Error checking Qdrant collection '${collectionName}':`, error); + throw new Error(`Failed to check Qdrant collection: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // Initialize embedding model pipeline + let embedder: any; + try { + log(`Loading embedding model '${modelName}'...`); + embedder = await pipeline('feature-extraction', modelName); + log(`Embedding model loaded successfully.`); + } catch (error) { + console.error(`Error loading embedding model '${modelName}':`, error); + throw new Error(`Failed to load embedding model: ${error instanceof Error ? error.message : String(error)}`); + } + + // Embed the query text + let queryEmbedding: number[]; + try { + log("Generating query embedding..."); + const output = await embedder(queryText, { pooling: 'mean', normalize: true }); + + // Adapt embedding extraction based on transformers.js output structure + if (output && output.data instanceof Float32Array) { + queryEmbedding = Array.from(output.data); + } else { + console.error("Unexpected embedding output format:", output); + throw new Error("Could not extract embedding from pipeline output for query."); + } + log(`Query embedding generated successfully (dimensions: ${queryEmbedding.length}).`); + } catch (error) { + console.error("Error generating query embedding:", error); + throw new Error(`Failed during query embedding: ${error instanceof Error ? error.message : String(error)}`); + } + + // Query Qdrant + try { + log(`Querying Qdrant collection '${collectionName}'...`); + const results = await client.search(collectionName, { + vector: queryEmbedding, + limit: nResults, + with_payload: true, // Include payload in results + // with_vector: false, // Optionally include vectors + }); + log(`Qdrant query successful.`); + return results; + } catch (error) { + console.error(`Error querying Qdrant collection '${collectionName}':`, error); + throw new Error(`Failed during Qdrant query: ${error instanceof Error ? error.message : String(error)}`); + } +} + +// Example of running the script directly +async function run() { + const query = process.argv.find((arg, i) => i > 1 && !arg.startsWith('--')); // Find first non-flag arg after script name + if (!query) { + console.error("Please provide a query string as a command-line argument."); + console.error('Example: bun run src/docs/query.ts --run "What is Qdrant?"'); + process.exit(1); + } + + const collectionName = DEFAULT_COLLECTION; // Uses env var QDRANT_COLLECTION or default + const model = process.env.EMBEDDING_MODEL || DEFAULT_MODEL; + const numResults = process.env.NUM_RESULTS ? parseInt(process.env.NUM_RESULTS, 10) : 5; + const qdrantUrl = DEFAULT_QDRANT_URL; // Uses env var QDRANT_URL or default + const qdrantApiKey = process.env.QDRANT_API_KEY; // Uses env var QDRANT_API_KEY + + if (isNaN(numResults) || numResults <= 0) { + console.warn(`Invalid NUM_RESULTS environment variable: ${process.env.NUM_RESULTS}. Using default ${5}.`); + } + + try { + const queryResults = await queryDocs( + query, + collectionName, + model, + numResults, + qdrantUrl, + qdrantApiKey + ); + log("\n--- Qdrant Query Results --- "); + // Pretty print the results (can be customized) + log(JSON.stringify(queryResults, null, 2)); + log("\n--------------------------"); + } catch (error) { + console.error("\n--- Qdrant Query failed --- ", error); + process.exit(1); + } +} + +// Only run if --run flag is present +if (import.meta.url === `file://${process.argv[1]}` && process.argv.includes('--run')) { + run(); +} diff --git a/src/ignition/client.ts b/src/ignition/client.ts new file mode 100644 index 0000000..3de67da --- /dev/null +++ b/src/ignition/client.ts @@ -0,0 +1,54 @@ +import { Provider, Wallet, Address } from 'fuels'; +import dotenv from 'dotenv'; +import { log } from "../common/utils"; + +dotenv.config(); + +export const FUEL_RPC_URL = process.env.FUEL_RPC_URL ?? 'https://mainnet.fuel.network/v1/graphql'; + +let _provider: Provider | null = null; + +export function getProvider(): Provider { + if (!_provider) { + log(`Initializing Fuel provider with RPC URL: ${FUEL_RPC_URL}`); + _provider = new Provider(FUEL_RPC_URL); + } + return _provider; +} + +export function getWallet(): Wallet | null { + const privateKey = process.env.PRIVATE_KEY; + if (!privateKey) { + log('No PRIVATE_KEY found in environment variables'); + return null; + } + + const provider = getProvider(); + log('Creating wallet from private key'); + return Wallet.fromPrivateKey(privateKey, provider); +} + +export function isValidAddress(address: string): boolean { + try { + new Address(address); + return true; + } catch { + return false; + } +} + +export async function getFuelEcosystemData() { + try { + log('Fetching Fuel ecosystem data from GitHub'); + const response = await fetch('https://raw.githubusercontent.com/FuelLabs/fuel-ecosystem/main/projects.json'); + if (!response.ok) { + throw new Error(`Failed to fetch ecosystem data: ${response.statusText}`); + } + + const data = await response.json() as any[]; + log(`Fetched ecosystem data with ${data.length} projects`); + return data; + } catch (error) { + throw new Error(`Error fetching ecosystem data: ${error}`); + } +} diff --git a/src/ignition/registry.ts b/src/ignition/registry.ts new file mode 100644 index 0000000..67fc05a --- /dev/null +++ b/src/ignition/registry.ts @@ -0,0 +1,89 @@ +import { log } from "../common/utils"; +import { getFuelEcosystemData } from "./client"; + +// Cache for verified assets +let _verifiedAssetsCache: any[] | null = null; + +export async function getVerifiedAssets() { + if (_verifiedAssetsCache !== null) { + log('Using cached verified assets'); + return _verifiedAssetsCache; + } + + try { + log('Fetching verified assets from Fuel network'); + const response = await fetch('https://verified-assets.fuel.network/assets.json'); + if (!response.ok) { + throw new Error(`Failed to fetch verified assets: ${response.statusText}`); + } + + const assets = await response.json() as Array<{ + name: string; + symbol: string; + icon: string; + networks: Array<{ chain: string; [key: string]: any }>; + }>; + + // Filter and map to only include mainnet network info + const mainnetAssets = assets + .map((asset) => { + const mainnetNetwork = asset.networks.find((n) => n.chain === 'mainnet' && n.type === 'fuel'); + if (!mainnetNetwork) return null; + return { + name: asset.name, + symbol: asset.symbol, + icon: asset.icon, + network: mainnetNetwork, + }; + }) + .filter( + (asset): asset is { name: string; symbol: string; icon: string; network: any } => asset !== null + ); + + // Cache the results + _verifiedAssetsCache = mainnetAssets; + log(`Cached ${mainnetAssets.length} verified assets`); + + return mainnetAssets; + } catch (error) { + log(`Error fetching verified assets: ${error}`); + throw error; + } +} + +export async function findProject(projectName: string, network: string = 'mainnet') { + const ecosystemData = await getFuelEcosystemData(); + + return ecosystemData.find((p: { name: string }) => + p.name.toLowerCase() === projectName.toLowerCase() + ); +} + +export async function findContract(projectName: string, contractName: string, network: string = 'mainnet') { + const project = await findProject(projectName, network); + if (!project) return null; + + const networkKey = network === 'testnet' ? 'sepolia' : network; + + return project.contracts?.[networkKey]?.find((c: { name: string; }) => + c.name.toLowerCase() === contractName.toLowerCase() + ); +} + +export async function fetchContractABI(projectName: string, contractName: string, network: string = 'mainnet') { + const contract = await findContract(projectName, contractName, network); + if (!contract || !contract.abi) return null; + + try { + log(`Fetching ABI from ${contract.abi}`); + const abiResponse = await fetch(contract.abi); + if (!abiResponse.ok) { + throw new Error(`Failed to fetch ABI: ${abiResponse.statusText}`); + } + + return await abiResponse.json(); + } catch (error) { + log(`Error fetching ABI: ${error}`); + throw error; + } +} diff --git a/src/ignition/tools.ts b/src/ignition/tools.ts new file mode 100644 index 0000000..c67cd0c --- /dev/null +++ b/src/ignition/tools.ts @@ -0,0 +1,641 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from 'zod'; +import { Provider, Address, Contract, bn, Wallet } from 'fuels'; +import { log, processValue } from "../common/utils"; +import { getProvider, getWallet, getFuelEcosystemData } from "./client"; +import { getVerifiedAssets, findProject, findContract, fetchContractABI } from "./registry"; + +export function registerBlockchainTools(server: McpServer) { + registerAssetBalanceTool(server); + registerListCoinsTool(server); + registerTransferAssetsTool(server); + + registerBlockInfoTool(server); + + registerFindProjectTool(server); + registerFetchContractABITool(server); + registerCallContractTool(server); + + // Resources + registerVerifiedAssetsResource(server); +} + +function registerAssetBalanceTool(server: McpServer) { + server.tool( + 'getAssetBalance', + 'Fetch the balance of a given asset for an address', + { + address: z.string().describe('Fuel address to query'), + assetId: z.string().describe('Asset ID (hex)') + }, + async ({ address, assetId }) => { + try { + const provider = getProvider(); + const balance = await provider.getBalance(address, assetId); + return { + content: [ + { type: 'text', text: JSON.stringify({ assetId, balance: balance.toString() }, null, 2) } + ] + }; + } catch (error) { + log(`Error in getAssetBalance tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error fetching asset balance: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerListCoinsTool(server: McpServer) { + server.tool( + 'listCoins', + 'List UTXOs (coins) for an address with pagination', + { + address: z.string().describe('Fuel address to query'), + assetId: z.string().optional().describe('Optional asset ID (hex) to filter by'), + first: z.number().optional().describe('Number of items to retrieve (for pagination)'), + after: z.string().optional().describe('Cursor for pagination (from previous response)') + }, + async ({ address, assetId, first, after }) => { + try { + const provider = getProvider(); + const paginationArgs = first || after ? { + first: first, + after: after + } : undefined; + + const { coins, pageInfo } = await provider.getCoins( + address, + assetId, + paginationArgs + ); + + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + coins: coins.map((coin: any) => ({ + id: coin.id, + amount: coin.amount.toString(), + assetId: coin.assetId, + owner: coin.owner + })), + pageInfo: { + hasNextPage: pageInfo.hasNextPage, + endCursor: pageInfo.endCursor + } + }, null, 2) + } + ] + }; + } catch (error) { + log(`Error in listCoins tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error listing coins: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerTransferAssetsTool(server: McpServer) { + server.tool( + 'transferAssets', + 'Send assets from one address to another', + { + recipient: z.string().describe('Recipient address'), + amount: z.string().describe('Amount to send (as string to handle large numbers)'), + assetId: z.string().describe('Asset ID to send (defaults to base asset)') + }, + async ({ recipient, amount, assetId }) => { + try { + const wallet = getWallet(); + if (!wallet) { + return { + content: [ + { type: 'text', text: `Error: PRIVATE_KEY not found in environment variables` } + ], + isError: true + }; + } + + const provider = getProvider(); + if (!assetId) { + assetId = await provider.getBaseAssetId(); + } + + const recipientAddress = recipient.startsWith('fuel') + ? recipient + : new Address(recipient).toString(); + + const amountToSend = bn(amount); + + const tx = await wallet.transfer(recipientAddress, amountToSend, assetId); + + const result = await tx.waitForResult(); + + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + status: 'success', + transactionId: tx.id, + blockId: result.blockId, + sender: wallet.address.toString(), + recipient: recipientAddress, + amount: amount, + assetId: assetId, + gasUsed: result.gasUsed?.toString() + }, null, 2) + } + ] + }; + } catch (error) { + log(`Error in transferAssets tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error transferring assets: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerBlockInfoTool(server: McpServer) { + server.tool( + 'getBlockInfo', + 'Fetch details about a specific block or latest block', + { + height: z.number().optional().describe('Block height to query (optional)'), + id: z.string().optional().describe('Block ID to query (optional)'), + }, + async ({ height, id }) => { + try { + const provider = getProvider(); + let block; + + if (height === undefined && id === undefined) { + block = await provider.getBlock('latest'); + } + else if (height !== undefined) { + block = await provider.getBlock(height); + } + else if (id !== undefined) { + block = await provider.getBlock(id); + } + + if (!block) { + return { + content: [ + { type: 'text', text: 'Block not found' } + ], + isError: true + }; + } + + const formattedBlock = { + id: block.id, + height: block.height, + time: block.time, + transactionCount: block.transactionIds?.length || 0, + daHeight: block.header?.daHeight, + prevRoot: block.header?.prevRoot, + }; + + return { + content: [ + { type: 'text', text: JSON.stringify(formattedBlock, null, 2) } + ] + }; + } catch (error) { + log(`Error in getBlockInfo tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error fetching block info: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerFindProjectTool(server: McpServer) { + server.tool( + 'findProject', + 'Find projects and their contracts in the Fuel ecosystem', + { + projectName: z.string().optional().describe('Optional: Filter by project name (case-insensitive, partial match)'), + network: z.enum(['mainnet', 'testnet', 'sepolia', 'all']).default('mainnet').describe('Network to filter contracts by') + }, + async ({ projectName, network }) => { + try { + const ecosystemData = await getFuelEcosystemData(); + + let filteredProjects = ecosystemData; + if (projectName) { + filteredProjects = ecosystemData.filter((p: { name: string; }) => + p.name.toLowerCase().includes(projectName.toLowerCase()) + ); + + if (filteredProjects.length === 0) { + return { + content: [ + { type: 'text', text: `No projects found matching "${projectName}"` } + ] + }; + } + } + + interface ProjectInfo { + name: string; + description: string; + isLiveMainnet: boolean; + tags: string[]; + url: string; + github: string; + twitter: string; + discord: string; + contracts: { + mainnet: any[]; + sepolia: any[]; + }; + predicates: { + mainnet: any[]; + sepolia: any[]; + }; + } + + const result = filteredProjects.map((project: { + name: any; + description: any; + isLiveMainnet: any; + tags: any; + url: any; + github: any; + twitter: any; + discord: any; + contracts: { + mainnet: any[]; + sepolia: any[]; + }; + predicates: { + mainnet: any[]; + sepolia: any[]; + }; + }) => { + const projectInfo: ProjectInfo = { + name: project.name, + description: project.description, + isLiveMainnet: project.isLiveMainnet, + tags: project.tags, + url: project.url, + github: project.github, + twitter: project.twitter, + discord: project.discord, + contracts: { + mainnet: [], + sepolia: [] + }, + predicates: { + mainnet: [], + sepolia: [] + } + }; + + if ((network === 'mainnet' || network === 'all') && project.contracts?.mainnet) { + projectInfo.contracts.mainnet = project.contracts.mainnet.map((c: any) => ({ + id: c.id, + name: c.name, + description: c.description || '', + hasAbi: !!c.abi, + abiUrl: c.abi || null + })); + } + + if ((network === 'testnet' || network === 'sepolia' || network === 'all') && project.contracts?.sepolia) { + projectInfo.contracts.sepolia = project.contracts.sepolia.map((c: any) => ({ + id: c.id, + name: c.name, + description: c.description || '', + hasAbi: !!c.abi, + abiUrl: c.abi || null + })); + } + + if ((network === 'mainnet' || network === 'all') && project.predicates?.mainnet) { + projectInfo.predicates.mainnet = project.predicates.mainnet.map((p: any) => ({ + blobId: p.blob_id, + name: p.name, + description: p.description || '', + hasAbi: !!p.abi, + abiUrl: p.abi || null + })); + } + + if ((network === 'testnet' || network === 'sepolia' || network === 'all') && project.predicates?.sepolia) { + projectInfo.predicates.sepolia = project.predicates.sepolia.map((p: any) => ({ + blobId: p.blob_id, + name: p.name, + description: p.description || '', + hasAbi: !!p.abi, + abiUrl: p.abi || null + })); + } + + return projectInfo; + }); + + // Filter out projects with no contracts or predicates for the selected network + const finalResult = result.filter((project: any) => { + if (network === 'all') return true; + + const hasMainnetItems = + (network === 'mainnet' && + ((project.contracts.mainnet && project.contracts.mainnet.length > 0) || + (project.predicates.mainnet && project.predicates.mainnet.length > 0))); + + const hasTestnetItems = + ((network === 'testnet' || network === 'sepolia') && + ((project.contracts.sepolia && project.contracts.sepolia.length > 0) || + (project.predicates.sepolia && project.predicates.sepolia.length > 0))); + + return hasMainnetItems || hasTestnetItems; + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(finalResult, null, 2) + } + ] + }; + } catch (error) { + log(`Error in findProject tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error finding projects: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerFetchContractABITool(server: McpServer) { + server.tool( + 'fetchContractABI', + 'Fetch the ABI for a contract from the Fuel ecosystem', + { + projectName: z.string().describe('Name of the Fuel ecosystem project'), + contractName: z.string().describe('Name of the specific contract within the project'), + network: z.enum(['mainnet', 'testnet', 'sepolia']).default('mainnet').describe('Network where the contract is deployed') + }, + async ({ projectName, contractName, network }) => { + try { + const contract = await findContract(projectName, contractName, network); + if (!contract) { + return { + content: [ + { type: 'text', text: `Error: Contract "${contractName}" not found in project "${projectName}" on ${network}` } + ], + isError: true + }; + } + + const abiJson = await fetchContractABI(projectName, contractName, network); + if (!abiJson) { + return { + content: [ + { type: 'text', text: `Error: ABI not found for contract "${contractName}" in project "${projectName}"` } + ], + isError: true + }; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + project: projectName, + contract: contractName, + contractId: contract.id, + network, + abi: abiJson + }, null, 2) + } + ] + }; + } catch (error) { + log(`Error in fetchContractABI tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error fetching contract ABI: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerCallContractTool(server: McpServer) { + server.tool( + 'callContract', + 'Execute any contract method on a Fuel contract with optional asset transfers', + { + contractId: z.string().describe('Contract ID to interact with'), + methodName: z.string().describe('Method name to call'), + args: z.array(z.any()).optional().default([]).describe('Array of arguments to pass to the method'), + abi: z.any().optional().describe('Contract ABI as JSON object (optional if projectName and contractName are provided)'), + projectName: z.string().optional().describe('Name of the Fuel ecosystem project (optional if abi is provided)'), + contractName: z.string().optional().describe('Name of the specific contract within the project (optional if abi is provided)'), + network: z.enum(['mainnet', 'testnet', 'sepolia']).default('mainnet').describe('Network where the contract is deployed'), + isStateChanging: z.boolean().default(false).describe('Set to true for state-changing calls, false for read-only calls'), + transfer: z.object({ + amount: z.union([z.string(), z.number()]).describe('Amount to transfer'), + assetId: z.string().optional().describe('Asset ID (defaults to base asset)') + }).optional().describe('Optional asset transfer to include with the contract call'), + }, + async ({ contractId, methodName, args, abi, projectName, contractName, network, isStateChanging, transfer }) => { + try { + // Check if we need to fetch the ABI from ecosystem data + if (!abi && projectName && contractName) { + abi = await fetchContractABI(projectName, contractName, network); + + // Make sure we have a contract ID + if (!contractId) { + const contract = await findContract(projectName, contractName, network); + if (contract) contractId = contract.id; + } + } + + if (!abi) { + return { + content: [ + { type: 'text', text: `Error: ABI is required. Either provide it directly or specify projectName and contractName to fetch it automatically` } + ] + }; + } + + if (!contractId) { + return { + content: [ + { type: 'text', text: `Error: Contract ID is required.` } + ] + }; + } + + // Create wallet from private key for state-changing calls + let account; + if (isStateChanging) { + account = getWallet(); + if (!account) { + return { + content: [ + { type: 'text', text: `Error: Environment variable PRIVATE_KEY is required for state-changing calls` } + ], + isError: true + }; + } + } else { + // For read-only calls, we can use the provider directly + account = getProvider(); + + // Check if user is trying to send transfers with a read-only call + if (transfer) { + return { + content: [ + { type: 'text', text: `Error: Asset transfers can only be used with state-changing calls (isStateChanging=true)` } + ], + isError: true + }; + } + } + + // Create the contract instance + const contract = new Contract(contractId, abi, account); + + // Check if the method exists + if (!contract.functions[methodName]) { + return { + content: [ + { type: 'text', text: `Error: Method '${methodName}' not found in contract ABI` } + ], + isError: true + }; + } + + // Prepare the function call + let functionCall = contract.functions[methodName](...args); + + // Add transfers if specified and this is a state-changing call + if (isStateChanging && transfer) { + // If assetId is not specified, use the base asset ID + const provider = getProvider(); + if (!transfer.assetId) { + transfer.assetId = await provider.getBaseAssetId(); + } + + functionCall = functionCall.callParams({ + forward: [bn(transfer.amount), transfer.assetId] + }); + } + + // Execute the call based on whether it's state-changing or read-only + let result; + if (isStateChanging) { + // For state-changing calls, use call() and wait for result + const { waitForResult, transactionId } = await functionCall.call(); + const txResult = await waitForResult(); + + // Extract only relevant data from transaction result to avoid circular references + result = { + transactionId: transactionId, + status: 'completed', + gasUsed: txResult.gasUsed?.toString(), + receipts: (txResult as any).receipts?.length ?? 0, + returnValue: txResult.value !== undefined ? + processValue(txResult.value) : null + }; + } else { + // For read-only calls, use get() + const readResult = await functionCall.get(); + + // Extract only the value and any relevant metadata + result = { + status: 'success', + gasUsed: readResult.gasUsed?.toString(), + value: processValue(readResult.value) + }; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + contractId, + methodName, + isStateChanging, + result + }, null, 2) + } + ] + }; + } catch (error) { + log(`Error in callContract tool: ${error}`); + return { + content: [ + { type: 'text', text: `Error calling contract method: ${error}` } + ], + isError: true + }; + } + } + ); +} + +function registerVerifiedAssetsResource(server: McpServer) { + server.resource( + 'verifiedMainnetAssets', + 'fuel-assets://mainnet', + async () => { + try { + const assets = await getVerifiedAssets(); + return { + contents: [ + { + uri: 'fuel-assets://mainnet', + text: JSON.stringify(assets, null, 2) + } + ] + }; + } catch (error) { + log(`Error in verifiedMainnetAssets resource: ${error}`); + return { + contents: [ + { + uri: 'fuel-assets://mainnet', + text: JSON.stringify({ error: `Failed to fetch assets: ${error}` }, null, 2) + } + ] + }; + } + } + ); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2a9b749 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,89 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import { registerDocTools } from "./docs/query.js"; +import { registerBlockchainTools } from "./ignition/tools.js"; +import { env } from '@xenova/transformers'; + +// Disable local cache for transformers.js models +env.cacheDir = ''; + +/** + * Create and configure a new Fuel MCP server + */ +export function createServer() { + // Get package.json version + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + const packageJsonPath = join(__dirname, "..", "package.json"); + const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + + /** + * Main Fuel MCP Server + * + * Provides access to Fuel documentation and blockchain tools through the + * Model Context Protocol + */ + const server = new McpServer({ + name: "fuel", + version: packageJson.version, + description: "Fuel Network MCP Server for documentation search and blockchain interaction" + }); + + // Register documentation tools + registerDocTools(server); + + // Register blockchain tools + registerBlockchainTools(server); + + return server; +} + +/** + * Connect and start the MCP server + */ +export async function startServer(server: McpServer, transport: StdioServerTransport) { + console.error("Fuel MCP Server starting..."); + + try { + await server.connect(transport); + console.error("Fuel MCP Server running on stdio"); + + // Keep the process running + process.on('SIGINT', () => { + console.error("Server shutting down..."); + process.exit(0); + }); + + return true; + } catch (error) { + console.error("Failed to start server:", error); + return false; + } +} + +/** + * Main entry point + */ +async function main() { + const server = createServer(); + const transport = new StdioServerTransport(); + + const success = await startServer(server, transport); + if (!success) { + process.exit(1); + } +} + +// Only run the main function if this file is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); + }); +} + +// Export main for testing +export { main }; diff --git a/src/mcp-server.ts b/src/mcp-server.ts index c962249..5cdf9cd 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -1,15 +1,14 @@ #!/usr/bin/env node -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -import { queryDocs, log } from "./query"; // Adjust path if necessary -import { env } from '@xenova/transformers'; import { spawn, exec } from 'child_process'; import net from 'net'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import { promisify } from 'util'; +import { log } from './common/utils.js'; +import { createServer } from './index.js'; +import { setQdrantReadyPromise } from './docs/query.js'; // Promisify exec for easier async/await usage const execAsync = promisify(exec); @@ -25,152 +24,33 @@ if (repoArgIndex > -1 && process.argv.length > repoArgIndex + 1) { log(`Using local repository path from --repo argument: ${localRepoPath}`); } else if (providedPath) { // If path provided but not absolute console.warn(`Warning: Provided --repo path "${providedPath}" is not absolute. Ignoring --repo argument. Please provide an absolute path.`); - // Optionally, exit here: - // console.error("Error: --repo path must be absolute."); - // process.exit(1); } else { // If --repo was the last argument console.warn(`Warning: Missing path argument after --repo. Ignoring --repo flag.`); } } // --- End Argument Parsing --- -// Disable local cache for transformers.js models, needed by queryDocs dependencies -env.cacheDir = ''; - // Promise to track Qdrant readiness (clone + docker start) let qdrantReadyPromise: Promise | null = null; -const server = new McpServer({ - name: "FuelMCPServer", - version: "0.1.0" -}); - -// Define the search tool -server.tool( - "searchFuelDocs", - { - query: z.string().describe("The search query for Fuel and Sway documentation."), - collectionName: z.string().optional().describe("Optional: Specify the ChromaDB collection name."), - modelName: z.string().optional().describe("Optional: Specify the embedding model name."), - nResults: z.number().int().positive().optional().describe("Optional: Specify the number of search results (default 5).") - }, - async ({ query, collectionName, modelName, nResults }) => { - log(`MCP Tool 'searchFuelDocs' called with query: "${query}"`); - - // --- Wait for Qdrant to be ready before proceeding --- - if (!qdrantReadyPromise) { - log("Error: Qdrant initialization was not started."); - return { - content: [{ type: "text", text: "Error: Qdrant initialization process not found." }], - isError: true - }; - } - try { - log("Waiting for Qdrant readiness..."); - await qdrantReadyPromise; - log("Qdrant is ready. Proceeding with query."); - } catch (initError: any) { - log(`Error during Qdrant initialization: ${initError?.message}`); - console.error("Qdrant initialization failed:", initError); - return { - content: [{ type: "text", text: `Error waiting for Qdrant initialization: ${initError?.message}` }], - isError: true - }; - } - // --- End Qdrant Readiness Check --- - - const executeQuery = async () => { - return await queryDocs( - query, - collectionName, // Will use default if undefined - modelName, // Will use default if undefined - nResults // Will use default if undefined - ); - }; - - try { - // First attempt - const results = await executeQuery(); - - // Format results for MCP response - // Assuming results is an array of Qdrant point objects like: - // [{ id: '...', score: 0.9, payload: { content: '...', source: '...' } }, ...] - const formattedResults = Array.isArray(results) - ? results.map((hit: any) => { - const payload = hit.payload || {}; - const score = hit.score; - const content = payload.content || 'No content found'; // Adjust 'content' key if needed based on indexing - const source = payload.source || 'unknown'; // Adjust 'source' key if needed - return `Source: ${source}\\nScore: ${score?.toFixed(4)}\\nContent:\\n${content}\\n---`; - }).join('\\n\\n') - : JSON.stringify(results, null, 2); // Fallback if format is unexpected - - return { - content: [{ - type: "text", - text: `Search Results for "${query}":\\n\\n${formattedResults}` - }] - }; - } catch (err: unknown) { - const error = err as Error; - console.error(`Error in searchFuelDocs tool: ${error.message}`); - // No need for the connection retry logic here anymore, - // as qdrantReadyPromise should handle ensuring it's running. - return { - content: [{ - type: "text", - text: `Error executing search: ${error.message}` - }], - isError: true // Indicate that an error occurred - }; - } - } -); - -// Define the provideStdContext tool -server.tool( - "provideStdContext", - {}, // No input parameters needed - async () => { - const filePath = path.join(__dirname, '..', 'sway', 'std_paths_data.txt'); // Construct path relative to the script's directory - log(`MCP Tool 'provideStdContext' called. Reading file: ${filePath}`); - - try { - const data = await fs.readFile(filePath, 'utf-8'); - log(`Successfully read ${filePath}. Length: ${data.length}`); - return { - content: [{ - type: "text", - text: `Sway Standard Library Paths and Types:\n\n${data}` - }] - }; - } catch (err: unknown) { - const error = err as Error; - console.error(`Error in provideStdContext tool reading ${filePath}: ${error.message}`); - log(`Error reading ${filePath}: ${error.message}`); - return { - content: [{ - type: "text", - text: `Error reading Sway standard library context file: ${error.message}` - }], - isError: true // Indicate that an error occurred - }; - } - } -); - -// Start the server using Stdio transport +/** + * Initialize and start the Fuel MCP server + * This is the main function for direct server startup via stdio + */ async function startServer() { try { - // Initiate Qdrant check/startup in the background, pass localRepoPath + // Initialize server with tools from our modular structure + const server = createServer(); + + // Initiate Qdrant check/startup in the background ensureQdrantIsRunning(localRepoPath); - log("Initiated Qdrant check/startup in background."); + console.error("Initiated Qdrant check/startup in background."); + // Connect to stdio const transport = new StdioServerTransport(); - log("Connecting MCP server via stdio..."); - // Connect should now happen quickly without being blocked by clone/docker + console.error("Connecting MCP server via stdio..."); await server.connect(transport); - log("MCP Server connected and ready (Qdrant initialization may still be in progress)."); + console.error("MCP Server connected and ready (Qdrant initialization may still be in progress)."); } catch (error) { console.error("Failed to start MCP server:", error); process.exit(1); @@ -178,7 +58,12 @@ async function startServer() { } // Start the server directly -startServer(); +if (import.meta.url === `file://${process.argv[1]}`) { + startServer().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); + }); +} // Function to check if a port is in use function isPortInUse(port: number): Promise { @@ -308,9 +193,14 @@ function ensureQdrantIsRunning(localRepoPath?: string) { } })(); // Immediately invoke the async IIFE + // Make the promise available to the query module + setQdrantReadyPromise(qdrantReadyPromise); + // Handle unhandled rejections for the promise globally (optional but good practice) qdrantReadyPromise?.catch(error => { console.error("Unhandled error during Qdrant initialization:", error); // Potentially exit or signal critical failure }); -} \ No newline at end of file +} + +export { startServer, ensureQdrantIsRunning };