diff --git a/bun.lock b/bun.lock index d43d6c181a..e3166dc5c0 100644 --- a/bun.lock +++ b/bun.lock @@ -29,6 +29,8 @@ "@types/bun": "1.3.11", "@types/node": "25.5.0", "@types/react": "19.2.14", + "@typescript-eslint/eslint-plugin": "^8.69.0", + "@typescript-eslint/parser": "^8.69.0", "ajv": "8.18.0", "auto-bind": "5.0.1", "axios": "1.16.0", @@ -43,6 +45,7 @@ "duck-duck-scrape": "^2.2.7", "emoji-regex": "10.6.0", "env-paths": "3.0.0", + "eslint": "9", "execa": "9.6.1", "fflate": "0.8.2", "figures": "6.1.0", @@ -234,12 +237,40 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.7", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.2", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw=="], + + "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="], "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -506,10 +537,32 @@ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.69.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/type-utils": "8.69.0", "@typescript-eslint/utils": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.69.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.69.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.69.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.69.0", "@typescript-eslint/types": "^8.69.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0" } }, "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.69.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0", "@typescript-eslint/utils": "8.69.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.69.0", "", {}, "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.69.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.69.0", "@typescript-eslint/tsconfig-utils": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.69.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="], "@vscode/ripgrep": ["@vscode/ripgrep@1.18.0", "", { "optionalDependencies": { "@vscode/ripgrep-darwin-arm64": "1.18.0", "@vscode/ripgrep-darwin-x64": "1.18.0", "@vscode/ripgrep-linux-arm": "1.18.0", "@vscode/ripgrep-linux-arm64": "1.18.0", "@vscode/ripgrep-linux-ia32": "1.18.0", "@vscode/ripgrep-linux-ppc64": "1.18.0", "@vscode/ripgrep-linux-riscv64": "1.18.0", "@vscode/ripgrep-linux-s390x": "1.18.0", "@vscode/ripgrep-linux-x64": "1.18.0", "@vscode/ripgrep-win32-arm64": "1.18.0", "@vscode/ripgrep-win32-ia32": "1.18.0", "@vscode/ripgrep-win32-x64": "1.18.0" } }, "sha512-ns5lWe44tSfbTMbVUsyB+I1819PVSw4AdpgK0RNkzfWfwy6+3IUNSxwSrfTno1/oWaS/hERNz+XLWVyga2aJBQ=="], @@ -542,6 +595,10 @@ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], @@ -554,6 +611,8 @@ "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], @@ -562,6 +621,8 @@ "axios": ["axios@1.16.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], @@ -572,6 +633,8 @@ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], @@ -584,6 +647,8 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -606,6 +671,8 @@ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -628,6 +695,8 @@ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], @@ -674,10 +743,24 @@ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], @@ -696,6 +779,10 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], @@ -712,9 +799,15 @@ "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "finalhandler": ["finalhandler@2.1.1", "", { "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-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], @@ -748,6 +841,10 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -794,8 +891,12 @@ "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-in-the-middle": ["import-in-the-middle@3.3.3", "", { "dependencies": { "cjs-module-lexer": "^2.2.0", "es-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -806,8 +907,12 @@ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], @@ -828,14 +933,20 @@ "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="], + "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], @@ -844,9 +955,13 @@ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "knip": ["knip@6.16.1", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", "oxc-parser": "^0.133.0", "oxc-resolver": "^11.20.0", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.9.0", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-TKMn1rxgH6h9vXR9Y0B+Cq7AdPTr9EI02IwoT65NzqYUkvoDQAaJ/aPybiFpAhZ1px6cNYYwXf86iHkBgzCo9w=="], - "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], @@ -866,6 +981,8 @@ "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -888,6 +1005,8 @@ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "mnemonist": ["mnemonist@0.39.8", "", { "dependencies": { "obliterator": "^2.0.1" } }, "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ=="], "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], @@ -896,6 +1015,8 @@ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "needle": ["needle@3.5.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, "bin": { "needle": "bin/needle" } }, "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -918,13 +1039,15 @@ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "oxc-parser": ["oxc-parser@0.133.0", "", { "dependencies": { "@oxc-project/types": "^0.133.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.133.0", "@oxc-parser/binding-android-arm64": "0.133.0", "@oxc-parser/binding-darwin-arm64": "0.133.0", "@oxc-parser/binding-darwin-x64": "0.133.0", "@oxc-parser/binding-freebsd-x64": "0.133.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", "@oxc-parser/binding-linux-arm64-musl": "0.133.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-musl": "0.133.0", "@oxc-parser/binding-openharmony-arm64": "0.133.0", "@oxc-parser/binding-wasm32-wasi": "0.133.0", "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", "@oxc-parser/binding-win32-x64-msvc": "0.133.0" } }, "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw=="], "oxc-resolver": ["oxc-resolver@11.20.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.20.0", "@oxc-resolver/binding-android-arm64": "11.20.0", "@oxc-resolver/binding-darwin-arm64": "11.20.0", "@oxc-resolver/binding-darwin-x64": "11.20.0", "@oxc-resolver/binding-freebsd-x64": "11.20.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.20.0", "@oxc-resolver/binding-linux-arm64-musl": "11.20.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.20.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-musl": "11.20.0", "@oxc-resolver/binding-openharmony-arm64": "11.20.0", "@oxc-resolver/binding-wasm32-wasi": "11.20.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.20.0", "@oxc-resolver/binding-win32-x64-msvc": "11.20.0" } }, "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g=="], - "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], @@ -932,6 +1055,8 @@ "pandemonium": ["pandemonium@2.4.1", "", { "dependencies": { "mnemonist": "^0.39.2" } }, "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], @@ -954,6 +1079,8 @@ "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], @@ -964,6 +1091,8 @@ "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], @@ -988,6 +1117,8 @@ "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -1070,10 +1201,14 @@ "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "turndown": ["turndown@7.2.2", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], @@ -1090,6 +1225,8 @@ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], @@ -1110,6 +1247,8 @@ "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -1130,6 +1269,8 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -1228,6 +1369,14 @@ "@aws-sdk/xml-builder/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "@smithy/core/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], @@ -1262,6 +1411,10 @@ "@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@3.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], @@ -1272,6 +1425,12 @@ "cliui/wrap-ansi": ["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=="], + "eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "fetch-blob/node-domexception": ["node-domexception@file:vendor/node-domexception-shim", {}], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -1308,10 +1467,14 @@ "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "@smithy/eventstream-codec/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="], "@smithy/eventstream-codec/@aws-crypto/crc32/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "cli-highlight/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], @@ -1328,10 +1491,16 @@ "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "eslint/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "qrcode/yargs/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=="], "qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], @@ -1354,6 +1523,8 @@ "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "cli-highlight/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cli-highlight/yargs/cliui/wrap-ansi": ["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=="], @@ -1368,6 +1539,8 @@ "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "qrcode/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "qrcode/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -1386,6 +1559,10 @@ "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "qrcode/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], } } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..11ea6a3f2c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,120 @@ +// Minimal flat config — only the rule we need to track `any` usage. +// Part of the phased `any`-reduction work landing on fix/reduce-any-types. +// +// Note: the repo has many existing `// eslint-disable custom-rules/` +// comments referencing rules from a previous (now-removed) custom plugin. +// We register a stub `custom-rules` plugin whose rules are all "off"; this +// makes the comments valid so eslint doesn't error on unknown rule names. +import tseslint from "@typescript-eslint/eslint-plugin"; +import tsparser from "@typescript-eslint/parser"; + +const DEPRECATED_CUSTOM_RULE_NAMES = [ + "bootstrap-isolation", + "no-cross-platform-process-issues", + "no-direct-json-operations", + "no-direct-ps-commands", + "no-lookbehind-regex", + "no-process-cwd", + "no-process-env-top-level", + "no-process-exit", + "no-sync-fs", + "no-top-level-dynamic-import", + "no-top-level-side-effects", + "prefer-use-keybindings", + "prefer-use-terminal-size", + "prompt-spacing", + "require-bun-typeof-guard", + "require-tool-match-name", + "safe-env-boolean-check", +]; + +// Stub plugin: each rule is a no-op listener so eslint resolves the name +// but does nothing. +const stubRules = Object.fromEntries( + DEPRECATED_CUSTOM_RULE_NAMES.map((name) => [ + name, + { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + ]), +); + +/** @type {import("eslint").ESLint.Plugin} */ +const customRulesPlugin = { + rules: stubRules, +}; + +// Stub plugin for legacy `eslint-plugin-n/*` references in source comments. +const eslintPluginNPlugin = { + rules: { + "no-unsupported-features/node-builtins": { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + }, +}; + +// Stub plugin for legacy `react-hooks/*` references in source comments. +const reactHooksPlugin = { + rules: { + "exhaustive-deps": { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + "rules-of-hooks": { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + }, +}; + +export default [ + { + ignores: [ + "dist/**", + "node_modules/**", + "vendor/**", + "web/**", + "vscode-extension/**", + "coverage/**", + "reports/**", + ], + }, + { + files: ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}", "scripts/**/*.ts"], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2023, + sourceType: "module", + ecmaFeatures: { jsx: true }, + }, + }, + plugins: { + "@typescript-eslint": tseslint, + "custom-rules": customRulesPlugin, + "eslint-plugin-n": eslintPluginNPlugin, + "react-hooks": reactHooksPlugin, + }, + linterOptions: { + // Source has many `// eslint-disable` comments that may now refer to + // rules not enabled here; don't fail on unused directives. + reportUnusedDisableDirectives: "off", + }, + rules: { + // Flags both `: any` annotations and `as any` casts. + // warn (not error) so existing code keeps building while we + // surface counts and reduce incrementally. + "@typescript-eslint/no-explicit-any": "warn", + }, + }, +]; \ No newline at end of file diff --git a/package.json b/package.json index 03229c8915..1325a4f537 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,10 @@ "typecheck:type-tests": "bun run scripts/typecheck-type-tests.ts", "smoke": "bun run build && node dist/cli.mjs --version", "deadcode": "knip --include files,dependencies", - "check": "bun run smoke && bun run deadcode && bun run test:full", + "lint": "eslint .", + "lint:any-budget": "bun run scripts/any-usage-report.ts --check", + "check": "bun run lint:any-budget && bun run smoke && bun run deadcode && bun run test:full", + "check:strict": "bun run typecheck && bun run lint && bun run lint:any-budget && bun run smoke && bun run deadcode && bun run test:full", "verify:privacy": "bun run scripts/verify-no-phone-home.ts", "install:verify": "bun run scripts/verify-clean-install.ts", "install:verify:published": "bun run scripts/verify-clean-install.ts --published", @@ -86,7 +89,6 @@ "devDependencies": { "@alcalzone/ansi-tokenize": "0.3.0", "@anthropic-ai/bedrock-sdk": "0.29.1", - "@sentry/node": "^10.70.0", "@anthropic-ai/foundry-sdk": "0.2.3", "@anthropic-ai/sandbox-runtime": "0.0.55", "@anthropic-ai/sdk": "0.94.0", @@ -98,11 +100,14 @@ "@grpc/grpc-js": "^1.14.3", "@grpc/proto-loader": "^0.8.0", "@modelcontextprotocol/sdk": "1.29.0", + "@sentry/node": "^10.70.0", "@smithy/core": "3.24.3", "@smithy/node-http-handler": "4.7.3", "@types/bun": "1.3.11", "@types/node": "25.5.0", "@types/react": "19.2.14", + "@typescript-eslint/eslint-plugin": "^8.69.0", + "@typescript-eslint/parser": "^8.69.0", "ajv": "8.18.0", "auto-bind": "5.0.1", "axios": "1.16.0", @@ -117,16 +122,17 @@ "duck-duck-scrape": "^2.2.7", "emoji-regex": "10.6.0", "env-paths": "3.0.0", + "eslint": "9.39.5", "execa": "9.6.1", "fflate": "0.8.2", "figures": "6.1.0", "fuse.js": "7.1.0", - "graphology": "0.26.0", "get-east-asian-width": "1.5.0", "google-auth-library": "10.6.2", + "graphology": "0.26.0", + "graphology-metrics": "2.4.0", "https-proxy-agent": "7.0.6", "ignore": "7.0.5", - "graphology-metrics": "2.4.0", "indent-string": "5.0.0", "js-tiktoken": "1.0.21", "jsonc-parser": "3.3.1", @@ -153,8 +159,8 @@ "typescript": "5.9.3", "undici": "7.28.0", "usehooks-ts": "3.1.1", - "web-tree-sitter": "0.25.10", "vscode-languageserver-protocol": "3.17.5", + "web-tree-sitter": "0.25.10", "wrap-ansi": "9.0.2", "ws": "8.21.0", "xss": "1.0.15", diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json new file mode 100644 index 0000000000..dc5285de8d --- /dev/null +++ b/reports/any-usage.baseline.json @@ -0,0 +1,4 @@ +{ + "total": 779, + "generatedAt": "2026-09-06T02:24:17.929Z" +} diff --git a/scripts/any-usage-report.ts b/scripts/any-usage-report.ts new file mode 100644 index 0000000000..25bce93ecd --- /dev/null +++ b/scripts/any-usage-report.ts @@ -0,0 +1,331 @@ +// Walk src/, tests/, scripts/ and report `any` usage by category. +// Produces reports/any-usage.{json,md} and a budget gate against +// reports/any-usage.baseline.json. +// +// Part of the phased `any`-reduction work landing on fix/reduce-any-types. + +import { mkdir, readFile, writeFile } from 'fs/promises' +import { resolve } from 'path' + +const ROOT = process.cwd() +const REPORT_DIR = resolve(ROOT, 'reports') +const JSON_OUT = resolve(REPORT_DIR, 'any-usage.json') +const MD_OUT = resolve(REPORT_DIR, 'any-usage.md') +const BASELINE_PATH = resolve(REPORT_DIR, 'any-usage.baseline.json') + +const SCAN_DIRS = ['src', 'tests', 'scripts'] +const EXCLUDE_DIR_NAMES = new Set([ + 'node_modules', + 'dist', + 'vendor', + 'web', + 'vscode-extension', + 'coverage', + 'reports', +]) + +type CategoryCounts = { + annotation: number + cast: number + indexSig: number + ignore: number + genericArg: number +} + +type FileReport = { + path: string + total: number + byCategory: CategoryCounts +} + +type Report = { + generatedAt: string + total: number + byCategory: CategoryCounts + files: FileReport[] +} + +// Regexes (each match counted; multi-line `m`, no `g` so we don't loop with /lastIndex). +// Match `any` as a complete identifier (avoid `many`, `company`, etc.). +const ANNOTATION_RE = /(?]*\bany\b[^>]*>/m + +function emptyCategory(): CategoryCounts { + return { annotation: 0, cast: 0, indexSig: 0, ignore: 0, genericArg: 0 } +} + +async function walk(dir: string, out: string[]): Promise { + const { readdir, lstat } = await import('fs/promises') + let entries: string[] + try { + entries = await readdir(dir) + } catch (err) { + // Propagate enumeration failures so a partial scan doesn't silently + // report a too-low count. The caller wraps this in a try and exits + // nonzero with the original error. + throw new Error(`readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`) + } + for (const name of entries) { + if (EXCLUDE_DIR_NAMES.has(name)) continue + const full = resolve(dir, name) + // Use lstat so we don't follow symlinks — a self-referencing link would + // recurse forever and stall the budget check. + const st = await lstat(full) + if (st.isSymbolicLink()) continue + if (st.isDirectory()) { + await walk(full, out) + } else if (/\.tsx?$/.test(name)) { + out.push(full) + } + } +} + +// Replace the contents of comments, strings, and template literals with +// spaces of equal length so the type-annotation regex only matches real +// TypeScript code. Newlines and offsets are preserved so report line numbers +// stay correct. +function stripNonCode(source: string): string { + let out = '' + let i = 0 + const n = source.length + while (i < n) { + const c = source[i] + const c2 = i + 1 < n ? source[i + 1] : '' + // Block comment /* ... */ — preserve @ts-ignore markers in IGNORE_RE + // by NOT stripping the directive prefix; IGNORE_RE only matches + // `// @ts-...` (line) so block comments are safe to fully strip. + if (c === '/' && c2 === '*') { + const end = source.indexOf('*/', i + 2) + if (end === -1) { + out += ' '.repeat(n - i) + i = n + break + } + // Replace with spaces, keep newlines so line numbers stay stable. + for (let j = i; j < end + 2; j++) { + out += source[j] === '\n' ? '\n' : ' ' + } + i = end + 2 + continue + } + // Line comment — keep `// @ts-...` markers for IGNORE_RE; strip the + // rest of the line. This preserves the existing ignore-directive + // accounting while still excluding `: any` in prose. + if (c === '/' && c2 === '/') { + // Find next newline + let end = i + while (end < n && source[end] !== '\n') end++ + const segment = source.slice(i, end) + const isDirective = /^\s*\/\/\s*@(ts-ignore|ts-expect-error|ts-nocheck)\b/.test(segment) + if (isDirective) { + out += segment + } else { + out += ' '.repeat(segment.length) + } + i = end + continue + } + // String literals + if (c === '"' || c === "'") { + const quote = c + let end = i + 1 + while (end < n) { + if (source[end] === '\\' && end + 1 < n) { + end += 2 + continue + } + if (source[end] === quote) { + end++ + break + } + if (source[end] === '\n') { + // Unterminated string — bail and keep the rest verbatim. + break + } + end++ + } + out += ' '.repeat(end - i) + i = end + continue + } + // Template literal — simpler handling: strip ${...} interpolation + // contents as well, since the inner expression has its own type scope. + if (c === '`') { + let end = i + 1 + while (end < n && source[end] !== '`') { + if (source[end] === '\\' && end + 1 < n) { + end += 2 + continue + } + if (source[end] === '$' && source[end + 1] === '{') { + // Strip interpolation body + let depth = 1 + end += 2 + while (end < n && depth > 0) { + if (source[end] === '{') depth++ + else if (source[end] === '}') depth-- + if (depth > 0) end++ + } + continue + } + end++ + } + if (end < n) end++ // consume closing ` + out += ' '.repeat(end - i) + i = end + continue + } + out += c + i++ + } + return out +} + +function countMatches(source: string, re: RegExp): number { + // Rebuild with `g` for counting while preserving the anchored pattern. + const flags = re.flags.replace('g', '') + 'g' + const global = new RegExp(re.source, flags) + let n = 0 + while (global.exec(source) !== null) { + n++ + if (global.lastIndex === 0) break + } + return n +} + +function categorize(source: string): CategoryCounts { + const counts = emptyCategory() + counts.annotation = countMatches(source, ANNOTATION_NEAR_RE) + counts.cast = countMatches(source, CAST_RE) + counts.indexSig = countMatches(source, INDEX_SIG_RE) + counts.ignore = countMatches(source, IGNORE_RE) + counts.genericArg = countMatches(source, GENERIC_RE) + return counts +} + +function totalOf(c: CategoryCounts): number { + return c.annotation + c.cast + c.indexSig + c.ignore + c.genericArg +} + +async function scan(): Promise { + const files: FileReport[] = [] + const totals = emptyCategory() + for (const dir of SCAN_DIRS) { + const root = resolve(ROOT, dir) + const found: string[] = [] + await walk(root, found) + for (const absPath of found) { + const raw = await readFile(absPath, 'utf8') + // Strip comments, strings, and template literals so `: any` in prose + // and `': any'` literals don't count as type annotations. + const source = stripNonCode(raw) + if (!ANNOTATION_RE.test(source) && !CAST_RE.test(source) && !IGNORE_RE.test(source)) { + continue + } + const byCategory = categorize(source) + const total = totalOf(byCategory) + if (total === 0) continue + files.push({ + path: absPath.slice(ROOT.length + 1), + total, + byCategory, + }) + for (const k of Object.keys(totals) as (keyof CategoryCounts)[]) { + totals[k] += byCategory[k] + } + } + } + files.sort((a, b) => b.total - a.total) + return { + generatedAt: new Date().toISOString(), + total: totalOf(totals), + byCategory: totals, + files, + } +} + +function renderMarkdown(report: Report, baselineTotal: number | null): string { + const lines: string[] = [] + lines.push('# `any` Usage Report') + lines.push('') + lines.push(`Generated: ${report.generatedAt}`) + lines.push('') + lines.push('## Totals') + lines.push('') + lines.push(`| Category | Count |`) + lines.push(`| --- | ---: |`) + lines.push(`| \`: any\` annotation | ${report.byCategory.annotation} |`) + lines.push(`| \`as any\` cast | ${report.byCategory.cast} |`) + lines.push(`| \`[key: ...]: any\` index signature | ${report.byCategory.indexSig} |`) + lines.push(`| \`// @ts-ignore\` (and friends) | ${report.byCategory.ignore} |`) + lines.push(`| Generic \`any\` arg | ${report.byCategory.genericArg} |`) + lines.push(`| **Total** | **${report.total}** |`) + if (baselineTotal !== null) { + const delta = report.total - baselineTotal + const sign = delta > 0 ? '+' : '' + lines.push('') + lines.push(`Baseline: ${baselineTotal} Delta: ${sign}${delta}`) + } + lines.push('') + lines.push(`## Top offenders (${Math.min(20, report.files.length)} of ${report.files.length})`) + lines.push('') + lines.push('| File | Total | Annot | Cast | IndexSig | Ignore | Generic |') + lines.push('| --- | ---: | ---: | ---: | ---: | ---: | ---: |') + for (const f of report.files.slice(0, 20)) { + lines.push( + `| ${f.path} | ${f.total} | ${f.byCategory.annotation} | ${f.byCategory.cast} | ${f.byCategory.indexSig} | ${f.byCategory.ignore} | ${f.byCategory.genericArg} |`, + ) + } + lines.push('') + return lines.join('\n') +} + +async function readBaselineTotal(): Promise { + try { + const raw = await readFile(BASELINE_PATH, 'utf8') + const parsed = JSON.parse(raw) as Report + return typeof parsed.total === 'number' ? parsed.total : null + } catch { + return null + } +} + +async function main(): Promise { + const checkMode = process.argv.includes('--check') + await mkdir(REPORT_DIR, { recursive: true }) + + const report = await scan() + const baselineTotal = await readBaselineTotal() + + await writeFile(JSON_OUT, JSON.stringify(report, null, 2) + '\n') + await writeFile(MD_OUT, renderMarkdown(report, baselineTotal)) + + // Always refresh baseline file unless --check (we don't want CI to clobber it). + if (!checkMode) { + await writeFile(BASELINE_PATH, JSON.stringify({ total: report.total, generatedAt: report.generatedAt }, null, 2) + '\n') + } + + if (checkMode) { + if (baselineTotal === null) { + console.error(`any-usage baseline is missing or invalid: ${BASELINE_PATH}`) + process.exit(1) + } + if (report.total > baselineTotal) { + console.error( + `any-usage budget exceeded: current=${report.total} > baseline=${baselineTotal} (delta=+${report.total - baselineTotal})`, + ) + process.exit(1) + } + console.log(`any-usage budget ok: current=${report.total} baseline=${baselineTotal}`) + return + } + + console.log(`any-usage report written: total=${report.total} files=${report.files.length}`) +} + +await main() \ No newline at end of file diff --git a/scripts/generate-sdk-types.ts b/scripts/generate-sdk-types.ts index 722458e230..b9429dcbed 100644 --- a/scripts/generate-sdk-types.ts +++ b/scripts/generate-sdk-types.ts @@ -55,12 +55,12 @@ const TypeOverrideMap: Record = { // Materialize placeholder schemas once so we can detect them by identity (===) // when they appear as fields inside other schemas. -const placeholderInstances = new Map() +const placeholderInstances = new Map() for (const name of Object.keys(TypeOverrideMap)) { - const thunk = (schemas as any)[name] - if (typeof thunk === 'function') { + const candidate = (schemas as Record)[name] + if (typeof candidate === 'function') { try { - placeholderInstances.set(thunk(), TypeOverrideMap[name]) + placeholderInstances.set((candidate as () => unknown)(), TypeOverrideMap[name]) } catch { /* ignore */ } } } @@ -228,15 +228,16 @@ const EXPORT_ORDER = [ // Zod v4 uses schema.def.type as the discriminator (lowercase strings). // All schemas have .def with { type: string, ... }. -function convert(schema: any, depth = 0): string { - if (!schema || !schema.def) return 'unknown' +function convert(schema: unknown, depth = 0): string { + if (!schema || typeof schema !== 'object' || !('def' in schema)) return 'unknown' // Check if this schema is a known placeholder (identity comparison) const override = placeholderInstances.get(schema) if (override) return override - const def = schema.def - const type: string = def.type + const def = (schema as { def: unknown }).def as Record | null + if (!def || typeof def !== 'object') return 'unknown' + const type = typeof def.type === 'string' ? def.type : '' switch (type) { case 'string': @@ -259,16 +260,16 @@ function convert(schema: any, depth = 0): string { return 'never' case 'literal': { // def.values is an array of literal values - const vals = def.values as any[] + const vals = Array.isArray(def.values) ? def.values : [] return vals.map(v => JSON.stringify(v)).join(' | ') } case 'enum': { // def.entries is { key: value } or an array const entries = def.entries if (Array.isArray(entries)) { - return entries.map((v: any) => JSON.stringify(v)).join(' | ') + return entries.map(v => JSON.stringify(v)).join(' | ') } - return Object.values(entries) + return Object.values(entries as Record) .filter((v): v is string => typeof v === 'string') .map(v => JSON.stringify(v)) .join(' | ') @@ -285,7 +286,7 @@ function convert(schema: any, depth = 0): string { return `${needsArrayElementParens(element) ? `(${element})` : element}[]` } case 'tuple': { - const items = (def.items as any[]).map(t => convert(t, depth)) + const items = (Array.isArray(def.items) ? def.items : []).map(t => convert(t, depth)) return `[${items.join(', ')}]` } case 'record': @@ -295,7 +296,7 @@ function convert(schema: any, depth = 0): string { case 'union': case 'discriminated_union': { // def.options for discriminated, def.options for plain union - const members = (def.options as any[]).map(t => { + const members = (Array.isArray(def.options) ? def.options : []).map(t => { const ts = convert(t, depth) return needsParens(ts) ? `(${ts})` : ts }) @@ -309,8 +310,11 @@ function convert(schema: any, depth = 0): string { return `${convert(def.innerType, depth)} | null` case 'default': return convert(def.innerType, depth) - case 'lazy': - return convert(def.getter(), depth) + case 'lazy': { + const getter = def.getter + if (typeof getter !== 'function') return 'unknown' + return convert((getter as () => unknown)(), depth) + } case 'transform': case 'effects': return convert(def.schema, depth) @@ -334,12 +338,12 @@ function convert(schema: any, depth = 0): string { } } -function convertObject(def: any, depth: number): string { - let shape: Record +function convertObject(def: Record, depth: number): string { + let shape: Record if (typeof def.shape === 'function') { - shape = def.shape() + shape = (def.shape as () => Record)() } else if (typeof def.shape === 'object' && def.shape !== null) { - shape = def.shape + shape = def.shape as Record } else { return 'Record' } @@ -359,9 +363,12 @@ function convertObject(def: any, depth: number): string { return '{\n' + fields.join('\n') + '\n' + closeIndent + '}' } -function isOptional(schema: any): boolean { - if (!schema?.def) return false - return schema.def.type === 'optional' || schema.def.type === 'default' +function isOptional(schema: unknown): boolean { + if (!schema || typeof schema !== 'object' || !('def' in schema)) return false + const def = (schema as { def: unknown }).def + if (!def || typeof def !== 'object') return false + const defType = (def as { type?: unknown }).type + return defType === 'optional' || defType === 'default' } function needsParens(ts: string): boolean { @@ -388,7 +395,7 @@ export function generateSdkTypes(): string { let errors = 0 for (const schemaName of EXPORT_ORDER) { - const thunk = (schemas as any)[schemaName] + const thunk = (schemas as Record)[schemaName] if (typeof thunk !== 'function') { console.warn(` ⚠ Not found: ${schemaName}`) errors++ @@ -403,11 +410,12 @@ export function generateSdkTypes(): string { continue } - let schema: any + let schema: unknown try { - schema = thunk() - } catch (e: any) { - console.warn(` ⚠ Materialize failed: ${schemaName}: ${e.message}`) + schema = (thunk as () => unknown)() + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e) + console.warn(` ⚠ Materialize failed: ${schemaName}: ${msg}`) errors++ continue } @@ -417,14 +425,18 @@ export function generateSdkTypes(): string { try { const ts = convert(schema) // Check for top-level description - const desc = Reflect.get(schema, 'description') as string | undefined + const desc = + schema && typeof schema === 'object' + ? (Reflect.get(schema, 'description') as string | undefined) + : undefined if (desc) { lines.push(`/** ${desc} */`) } lines.push(`export type ${typeName} = ${ts}`) lines.push('') - } catch (e: any) { - console.warn(` ⚠ Convert failed: ${schemaName}: ${e.message}`) + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e) + console.warn(` ⚠ Convert failed: ${schemaName}: ${msg}`) errors++ lines.push(`// ⚠ Failed: ${schemaName}`) lines.push(`export type ${typeName} = any`) diff --git a/scripts/grpc-cli.ts b/scripts/grpc-cli.ts index 90467e3428..fabcd6b5a4 100644 --- a/scripts/grpc-cli.ts +++ b/scripts/grpc-cli.ts @@ -13,7 +13,9 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { oneofs: true, }) -const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any +const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as unknown as { + openclaude: { v1: { AgentService: new (addr: string, creds: grpc.ChannelCredentials) => { Chat: () => grpc.ClientDuplexStream } } } +} const openclaudeProto = protoDescriptor.openclaude.v1 const rl = readline.createInterface({ @@ -35,22 +37,33 @@ async function main() { grpc.credentials.createInsecure() ) - let call: grpc.ClientDuplexStream | null = null + let call: grpc.ClientDuplexStream | null = null const startStream = () => { call = client.Chat() let textStreamed = false - call.on('data', async (serverMessage: any) => { + call.on('data', async (raw: unknown) => { + // gRPC streaming payloads are dynamically loaded from the .proto file at + // startup, so the runtime message shape has no static type. Narrow at + // the boundary and trust field access below. + const serverMessage = raw as { + text_chunk?: { text?: string } + tool_start?: { tool_name?: string; arguments_json?: string } + tool_result?: { tool_name?: string; output?: string } + action_required?: { question?: string; prompt_id?: string } + done?: { full_text?: string } + error?: { message?: string } + } if (serverMessage.text_chunk) { - process.stdout.write(serverMessage.text_chunk.text) + process.stdout.write(serverMessage.text_chunk.text ?? '') textStreamed = true } else if (serverMessage.tool_start) { console.log(`\n\x1b[36m[Tool Call]\x1b[0m \x1b[1m${serverMessage.tool_start.tool_name}\x1b[0m`) console.log(`\x1b[90m${serverMessage.tool_start.arguments_json}\x1b[0m\n`) } else if (serverMessage.tool_result) { console.log(`\n\x1b[32m[Tool Result]\x1b[0m \x1b[1m${serverMessage.tool_result.tool_name}\x1b[0m`) - const out = serverMessage.tool_result.output + const out = serverMessage.tool_result.output ?? '' if (out.length > 500) { console.log(`\x1b[90m${out.substring(0, 500)}...\n(Output truncated, total length: ${out.length})\x1b[0m`) } else { @@ -59,8 +72,8 @@ async function main() { } else if (serverMessage.action_required) { const action = serverMessage.action_required console.log(`\n\x1b[33m[Action Required]\x1b[0m`) - const reply = await askQuestion(`\x1b[1m${action.question}\x1b[0m (y/n) > `) - + const reply = await askQuestion(`\x1b[1m${action.question ?? ''}\x1b[0m (y/n) > `) + call?.write({ input: { prompt_id: action.prompt_id, @@ -75,7 +88,7 @@ async function main() { console.log('\n\x1b[32m[Generation Complete]\x1b[0m') promptUser() } else if (serverMessage.error) { - console.error(`\n\x1b[31m[Server Error]\x1b[0m ${serverMessage.error.message}`) + console.error(`\n\x1b[31m[Server Error]\x1b[0m ${serverMessage.error.message ?? ''}`) promptUser() } }) diff --git a/src/commands/cache-probe/cache-probe.ts b/src/commands/cache-probe/cache-probe.ts index cd77bf71d8..888def344f 100644 --- a/src/commands/cache-probe/cache-probe.ts +++ b/src/commands/cache-probe/cache-probe.ts @@ -96,7 +96,7 @@ export function resolveCacheProbeRequestApiKey( function getField(obj: unknown, path: string): unknown { return path .split('.') - .reduce((o: any, k: string) => (o != null ? o[k] : undefined), obj) + .reduce((o: unknown, k: string) => (o != null && typeof o === 'object' ? (o as Record)[k] : undefined), obj) } interface ProbeResult { @@ -123,7 +123,7 @@ async function sendProbe( headers, body: JSON.stringify(body), }) - } catch (err: any) { + } catch (err: unknown) { return { label, status: 0, @@ -131,7 +131,7 @@ async function sendProbe( headers: {}, usage: null, responseId: null, - error: err.message, + error: err instanceof Error ? err.message : String(err), } } const elapsed = Date.now() - start @@ -419,21 +419,24 @@ export const call: LocalCommandCall = async (args) => { // --- Simulate what main's shim code does with this usage --- // codexShim.ts makeUsage() — used for Responses API (GPT-5+/Codex) - function mainMakeUsage(u: any) { + function mainMakeUsage(u: unknown) { + const rec = (u ?? {}) as Record return { - input_tokens: u?.input_tokens ?? 0, - output_tokens: u?.output_tokens ?? 0, + input_tokens: typeof rec.input_tokens === 'number' ? rec.input_tokens : 0, + output_tokens: typeof rec.output_tokens === 'number' ? rec.output_tokens : 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, // ← main hardcodes this to 0 } } // openaiShim.ts convertChunkUsage() — used for Chat Completions - function mainConvertChunkUsage(u: any) { + function mainConvertChunkUsage(u: unknown) { + const rec = (u ?? {}) as Record + const details = (rec.prompt_tokens_details ?? {}) as Record return { - input_tokens: u?.prompt_tokens ?? 0, - output_tokens: u?.completion_tokens ?? 0, + input_tokens: typeof rec.prompt_tokens === 'number' ? rec.prompt_tokens : 0, + output_tokens: typeof rec.completion_tokens === 'number' ? rec.completion_tokens : 0, cache_creation_input_tokens: 0, - cache_read_input_tokens: u?.prompt_tokens_details?.cached_tokens ?? 0, + cache_read_input_tokens: typeof details.cached_tokens === 'number' ? details.cached_tokens : 0, } } diff --git a/src/components/ClaudeMdExternalIncludesDialog.tsx b/src/components/ClaudeMdExternalIncludesDialog.tsx index 414385929f..06513be515 100644 --- a/src/components/ClaudeMdExternalIncludesDialog.tsx +++ b/src/components/ClaudeMdExternalIncludesDialog.tsx @@ -5,6 +5,7 @@ import { logEvent } from 'src/services/analytics/index.js'; import { Box, Link, Text } from '../ink.js'; import type { ExternalClaudeMdInclude } from '../utils/claudemd.js'; import { saveCurrentProjectConfig } from '../utils/config.js'; +import type { ProjectConfig } from '../utils/config.js'; import { getClaudeConfigHomeDir } from '../utils/envUtils.js'; import { getDisplayPath } from '../utils/file.js'; import { Select } from './CustomSelect/index.js'; @@ -15,16 +16,16 @@ type Props = { externalIncludes?: ExternalClaudeMdInclude[]; scope?: 'User' | 'Project'; }; -function acceptProject(current: any) { +function acceptProject(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApproved: true, hasClaudeMdExternalIncludesWarningShown: true }; } -function acceptUser(current: any) { +function acceptUser(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApprovedForUser: true, hasClaudeMdExternalIncludesWarningShownForUser: true }; } -function declineProject(current: any) { +function declineProject(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApproved: false, hasClaudeMdExternalIncludesWarningShown: true }; } -function declineUser(current: any) { +function declineUser(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApprovedForUser: false, hasClaudeMdExternalIncludesWarningShownForUser: true }; } function getUserClaudeMdDisplayPath(): string { diff --git a/src/components/EffortPicker.tsx b/src/components/EffortPicker.tsx index ce40c6922b..8cc99d8c41 100644 --- a/src/components/EffortPicker.tsx +++ b/src/components/EffortPicker.tsx @@ -34,7 +34,7 @@ type Props = { export function EffortPicker({ onSelect, onCancel }: Props) { const model = useMainLoopModel() - const appStateEffort = useAppState((s: any) => s.effortValue) + const appStateEffort = useAppState((s) => s.effortValue) const setAppState = useSetAppState() const provider = getAPIProvider() const usesOpenAIEffort = modelUsesOpenAIEffort(model) diff --git a/src/components/ProviderManager.tsx b/src/components/ProviderManager.tsx index ed2f5c4f03..ee6927dcfc 100644 --- a/src/components/ProviderManager.tsx +++ b/src/components/ProviderManager.tsx @@ -1718,18 +1718,21 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode { env: { CLAUDE_CODE_USE_GITHUB: '1', OPENAI_MODEL: GITHUB_PROVIDER_DEFAULT_MODEL, - OPENAI_API_KEYS: undefined as any, - OPENAI_API_KEY: undefined as any, - OPENAI_ORG: undefined as any, - OPENAI_PROJECT: undefined as any, - OPENAI_ORGANIZATION: undefined as any, - OPENAI_BASE_URL: undefined as any, - OPENAI_API_BASE: undefined as any, - CLAUDE_CODE_USE_OPENAI: undefined as any, - CLAUDE_CODE_USE_GEMINI: undefined as any, - CLAUDE_CODE_USE_BEDROCK: undefined as any, - CLAUDE_CODE_USE_VERTEX: undefined as any, - CLAUDE_CODE_USE_FOUNDRY: undefined as any, + // `undefined` instructs the merge to clear these keys from saved + // settings. EnvironmentVariablesSchema coerces to string, so we route + // through `unknown` instead of `any` for the literal assignment. + OPENAI_API_KEYS: undefined as unknown as string, + OPENAI_API_KEY: undefined as unknown as string, + OPENAI_ORG: undefined as unknown as string, + OPENAI_PROJECT: undefined as unknown as string, + OPENAI_ORGANIZATION: undefined as unknown as string, + OPENAI_BASE_URL: undefined as unknown as string, + OPENAI_API_BASE: undefined as unknown as string, + CLAUDE_CODE_USE_OPENAI: undefined as unknown as string, + CLAUDE_CODE_USE_GEMINI: undefined as unknown as string, + CLAUDE_CODE_USE_BEDROCK: undefined as unknown as string, + CLAUDE_CODE_USE_VERTEX: undefined as unknown as string, + CLAUDE_CODE_USE_FOUNDRY: undefined as unknown as string, }, }) if (error) { @@ -1767,10 +1770,10 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode { const { error } = updateSettingsForSource('userSettings', { env: { - CLAUDE_CODE_USE_GITHUB: undefined as any, - OPENAI_MODEL: undefined as any, - OPENAI_BASE_URL: undefined as any, - OPENAI_API_BASE: undefined as any, + CLAUDE_CODE_USE_GITHUB: undefined as unknown as string, + OPENAI_MODEL: undefined as unknown as string, + OPENAI_BASE_URL: undefined as unknown as string, + OPENAI_API_BASE: undefined as unknown as string, }, }) if (error) { diff --git a/src/components/wizard/WizardProvider.tsx b/src/components/wizard/WizardProvider.tsx index 5155d83def..ceb01164e0 100644 --- a/src/components/wizard/WizardProvider.tsx +++ b/src/components/wizard/WizardProvider.tsx @@ -3,9 +3,9 @@ import React, { createContext, type ReactNode, useCallback, useEffect, useMemo, import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'; import type { WizardContextValue, WizardProviderProps } from './types.js'; -// Use any here for the context since it will be cast properly when used -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const WizardContext = createContext | null>(null); +// Use the default generic (Record) for the context value; consumers +// that need a tighter type can supply it via useContext>. +export const WizardContext = createContext(null); export function WizardProvider = Record>(t0: WizardProviderProps): React.ReactNode { const $ = _c(38); const { diff --git a/src/entrypoints/mcp.ts b/src/entrypoints/mcp.ts index f2ac485f06..5b0fb7ba08 100644 --- a/src/entrypoints/mcp.ts +++ b/src/entrypoints/mcp.ts @@ -202,20 +202,19 @@ export async function startMCPServer( if (typeof data === 'string') { content = [{ type: 'text', text: data }] } else if (Array.isArray(data)) { - content = data.map((block: any) => { - if (block.type === 'text') { - return { type: 'text', text: block.text || '' } - } else if (block.type === 'image' && block.source) { - return { - type: 'image', - data: block.source.data, - mimeType: block.source.media_type, - } - } else { - // eslint-disable-next-line custom-rules/no-top-level-side-effects, no-console - console.warn(`Unmapped content block type from tool ${name}: ${block.type || 'unknown'}`) - return { type: 'text', text: jsonStringify(block) } + content = data.flatMap((block: unknown) => { + // Boundary data — defensively skip primitives/null instead of crashing. + if (!block || typeof block !== 'object') return [] + const b = block as { type?: unknown; text?: unknown; source?: { type?: unknown; media_type?: unknown; data?: unknown } } + if (b.type === 'text') { + return [{ type: 'text', text: String(b.text ?? '') } as CallToolResult['content'][number]] + } + if (b.type === 'image' && b.source && typeof b.source.data === 'string' && typeof b.source.media_type === 'string') { + return [{ type: 'image', data: b.source.data, mimeType: b.source.media_type } as CallToolResult['content'][number]] } + // eslint-disable-next-line custom-rules/no-top-level-side-effects, no-console + console.warn(`Unmapped content block type from tool ${name}: ${String(b.type ?? 'unknown')}`) + return [{ type: 'text', text: jsonStringify(block) } as CallToolResult['content'][number]] }) as CallToolResult['content'] } else { content = [{ type: 'text', text: jsonStringify(data) }] @@ -223,7 +222,7 @@ export async function startMCPServer( return { content, - isError: !!(finalResult as any).isError, + isError: !!(finalResult as { isError?: unknown }).isError, } } catch (error) { logError(error) diff --git a/src/entrypoints/sdk.d.ts b/src/entrypoints/sdk.d.ts index 5dfcbceb8f..4959430009 100644 --- a/src/entrypoints/sdk.d.ts +++ b/src/entrypoints/sdk.d.ts @@ -186,6 +186,26 @@ export type SessionMessage = { // Re-export precise SDK message types from generated types // These use camelCase field names and discriminated unions for full IntelliSense +// Note: ToolAnnotations and CallToolResult shapes mirror the MCP SDK but are +// declared locally to keep the public SDK declarations free of the optional +// `@modelcontextprotocol/sdk` peer dependency. They are stable SDK shapes. +type ToolAnnotations = { + title?: string + readOnlyHint?: boolean + destructiveHint?: boolean + idempotentHint?: boolean + openWorldHint?: boolean +} +type CallToolResult = { + content?: Array< + | { type: 'text'; text: string } + | { type: 'image'; data: string; mimeType: string } + | { type: 'resource'; resource: { uri: string; text?: string; mimeType?: string } } + | { type: string; [key: string]: unknown } + > + isError?: boolean + [key: string]: unknown +} import type { AccountInfo, AgentInfo, @@ -441,12 +461,12 @@ export interface SDKSession { // MCP tool types // ============================================================================ -export interface SdkMcpToolDefinition { +export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema - handler: (args: any, extra: unknown) => Promise - annotations?: any + handler: (args: any, extra: unknown) => Promise + annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean } @@ -525,13 +545,13 @@ export function unstable_v2_prompt( // MCP tool functions // ============================================================================ -export function tool( +export function tool( name: string, description: string, inputSchema: Schema, - handler: (args: any, extra: unknown) => Promise, + handler: (args: any, extra: unknown) => Promise, extras?: { - annotations?: any + annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean }, diff --git a/src/entrypoints/sdk/index.ts b/src/entrypoints/sdk/index.ts index cda612f82c..e37149e5f4 100644 --- a/src/entrypoints/sdk/index.ts +++ b/src/entrypoints/sdk/index.ts @@ -168,7 +168,7 @@ export { * ) * ``` */ -export function tool( +export function tool( name: string, description: string, inputSchema: Schema, diff --git a/src/entrypoints/sdk/query.ts b/src/entrypoints/sdk/query.ts index 02406e0860..550234de77 100644 --- a/src/entrypoints/sdk/query.ts +++ b/src/entrypoints/sdk/query.ts @@ -6,7 +6,7 @@ */ import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' -import { randomUUID } from 'crypto' +import { randomUUID, type UUID } from 'crypto' import { dirname } from 'path' import { QueryEngine } from '../../QueryEngine.js' import { @@ -824,11 +824,11 @@ class QueryImpl implements Query { // Find the last assistant message UUID that has a file-history snapshot const fileHistory = state.fileHistory for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - const messageId = (msg as any)?.uuid as string | undefined + const msg = messages[i] as { uuid?: string } | undefined + const messageId = msg?.uuid if (!messageId) continue - if (fileHistoryCanRestore(fileHistory, messageId as any)) { + if (fileHistoryCanRestore(fileHistory, messageId as UUID)) { // Synchronous check — return canRewind: true with the messageId. // Use rewindFilesAsync() to actually perform the rewind. return { canRewind: true } @@ -850,11 +850,11 @@ class QueryImpl implements Query { const fileHistory = state.fileHistory let targetMessageId: string | undefined for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - const messageId = (msg as any)?.uuid as string | undefined + const msg = messages[i] as { uuid?: string } | undefined + const messageId = msg?.uuid if (!messageId) continue - if (fileHistoryCanRestore(fileHistory, messageId as any)) { + if (fileHistoryCanRestore(fileHistory, messageId as UUID)) { targetMessageId = messageId break } @@ -865,7 +865,7 @@ class QueryImpl implements Query { } // Get diff stats before rewinding (async) - const diffStats = await fileHistoryGetDiffStats(fileHistory, targetMessageId as any) + const diffStats = await fileHistoryGetDiffStats(fileHistory, targetMessageId as UUID) // Perform the actual rewind try { @@ -874,7 +874,7 @@ class QueryImpl implements Query { ...prev, fileHistory: updater(prev.fileHistory), })), - targetMessageId as any, + targetMessageId as UUID, ) return { @@ -910,7 +910,7 @@ class QueryImpl implements Query { supportedAgents(): string[] { const state = this.appStateStore.getState() const agents = state.agentDefinitions?.activeAgents - return agents?.map((a: any) => a.agentType).filter(Boolean) ?? [] + return agents?.map((a) => a.agentType).filter(Boolean) ?? [] } mcpServerStatus(): McpServerStatus[] { @@ -925,10 +925,10 @@ class QueryImpl implements Query { base.serverInfo = client.serverInfo } if (client.type === 'failed') { - base.error = (client as any).error + base.error = client.error } if ('config' in client) { - const cfg = (client as any).config + const cfg = client.config if (cfg?.scope) base.scope = cfg.scope } return base diff --git a/src/entrypoints/sdk/v2.ts b/src/entrypoints/sdk/v2.ts index faf2ecd8f4..a39d3ffa55 100644 --- a/src/entrypoints/sdk/v2.ts +++ b/src/entrypoints/sdk/v2.ts @@ -42,6 +42,7 @@ import type { PermissionResult, SDKResultMessage as GeneratedSDKResultMessage, } from './coreTypes.generated.js' +import type { Message } from '../../types/message.js' import type { SDKMessage, SDKPermissionTimeoutMessage, @@ -177,7 +178,7 @@ export type SDKResultMessage = GeneratedSDKResultMessage * These definitions can be passed to `createSdkMcpServer()` to register * custom MCP tools. */ -export interface SdkMcpToolDefinition { +export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema @@ -518,7 +519,7 @@ class SDKSessionImpl implements SDKSession { function createEngineFromOptions( options: SDKSessionOptions, permissionTarget: PermissionTarget & { pushTimeout?: (msg: SDKPermissionTimeoutMessage) => void }, - initialMessages?: any[], + initialMessages?: Message[], sessionId?: string, ): { engine: QueryEngine; appStateStore: Store; abortController: AbortController } { const { @@ -678,7 +679,7 @@ export async function unstable_v2_resumeSession( // Load prior messages from JSONL with compact-aware chain building. // Matches CLI's loadTranscriptFile → buildConversationChain → removeExtraFields. const resolved = await resolveSessionFilePath(sessionId, options.cwd) - let initialMessages: any[] + let initialMessages: Message[] = [] if (resolved) { const { size: fileSize } = await stat(resolved.filePath) @@ -764,7 +765,7 @@ export async function unstable_v2_resumeSession( } if (leaf) { const chain = buildChain(byUuid, leaf) - initialMessages = stripChainFields(chain) + initialMessages = stripChainFields(chain) as Message[] } else { initialMessages = [] } @@ -779,7 +780,7 @@ export async function unstable_v2_resumeSession( const { engine, appStateStore, abortController } = createEngineFromOptions( options, session, - initialMessages as any[], + initialMessages, sessionId, ) session.setEngine(engine) diff --git a/src/grpc/server.ts b/src/grpc/server.ts index 6d8f3fae47..fe57207f46 100644 --- a/src/grpc/server.ts +++ b/src/grpc/server.ts @@ -20,9 +20,32 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { oneofs: true, }) -const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any +// proto-loader returns a deeply-nested untyped shape; we only need the AgentService +// definition from the OpenClaude v1 package, so narrow to that. The implementation +// map declares the method names; ServiceDefinition requires T to be a record +// keyed by method name. +const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as unknown as { + openclaude: { + v1: { + AgentService: { service: grpc.ServiceDefinition<{ Chat: never }> } + } + } +} const openclaudeProto = protoDescriptor.openclaude.v1 +// Shape of a single client message on the duplex Chat stream. Matches the +// `oneof` payload in openclaude.proto (ChatRequest | ToolInput | CancelRequest). +type ClientMessage = { + request?: { + session_id?: unknown + message?: unknown + working_directory?: unknown + model?: unknown + } | null + input?: { prompt_id?: unknown; reply?: unknown } | null + cancel?: unknown +} + const MAX_SESSIONS = 1000 type TextDeltaEvent = { @@ -108,7 +131,7 @@ export class GrpcServer { ) } - private handleChat(call: grpc.ServerDuplexStream) { + private handleChat(call: grpc.ServerDuplexStream) { let engine: QueryEngine | null = null let appState: AppState = getDefaultAppState() const fileCache: FileStateCache = new FileStateCache(READ_FILE_STATE_CACHE_SIZE, 25 * 1024 * 1024) @@ -117,7 +140,7 @@ export class GrpcServer { const pendingRequests = new Map void>() // Accumulated messages from previous turns for multi-turn context - let previousMessages: any[] = [] + let previousMessages: Message[] = [] let sessionId = '' let interrupted = false @@ -293,11 +316,11 @@ export class GrpcServer { } call.end() } - } catch (err: any) { + } catch (err) { console.error('Error processing stream') call.write({ error: { - message: err.message || "Internal server error", + message: err instanceof Error ? err.message : "Internal server error", code: "INTERNAL" } }) diff --git a/src/ink/reconciler.ts b/src/ink/reconciler.ts index 3357fcbf30..fa03f192d5 100644 --- a/src/ink/reconciler.ts +++ b/src/ink/reconciler.ts @@ -34,9 +34,8 @@ if (process.env.NODE_ENV === 'development') { try { // eslint-disable-next-line custom-rules/no-top-level-dynamic-import -- dev-only; NODE_ENV check is DCE'd in production void import('./devtools.js') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.code === 'ERR_MODULE_NOT_FOUND') { + } catch (error: unknown) { + if ((error as { code?: unknown }).code === 'ERR_MODULE_NOT_FOUND') { // biome-ignore lint/suspicious/noConsole: intentional warning console.warn( ` diff --git a/src/main.tsx b/src/main.tsx index 70176514eb..287ceaea51 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -261,8 +261,7 @@ function isBeingDebugged() { // Check if inspector is available and active (indicates debugging) try { // Dynamic import would be better but is async - use global object instead - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const inspector = (global as any).require('inspector'); + const inspector = (global as unknown as { require: (s: string) => { url: () => string } }).require('inspector'); const hasInspectorUrl = !!inspector.url(); return hasInspectorUrl || hasInspectArg || hasInspectEnv; } catch { diff --git a/src/query.ts b/src/query.ts index 3c8de1642e..6e04c049df 100644 --- a/src/query.ts +++ b/src/query.ts @@ -974,7 +974,7 @@ async function* queryLoop( messagesForQuery = snipResult.messages snipTokensFreed = snipResult.tokensFreed if (snipResult.boundaryMessage) { - yield snipResult.boundaryMessage + yield snipResult.boundaryMessage as Message } queryCheckpoint('query_snip_end') } diff --git a/src/services/api/withRetry.ts b/src/services/api/withRetry.ts index 3cd4e9a0f1..594000bc03 100644 --- a/src/services/api/withRetry.ts +++ b/src/services/api/withRetry.ts @@ -122,19 +122,21 @@ function isPersistentRetryEnabled(): boolean { : false } -function isQuotaExhausted(error: any): boolean { - const msg = (error?.message || '').toLowerCase() +function isQuotaExhausted(error: unknown): boolean { + const errObj = error as { message?: unknown; status?: unknown } | null | undefined + const msg = String(errObj?.message ?? '').toLowerCase() + const status = errObj?.status // OpenRouter (and other gateways) return 402 with instructions to adjust max_tokens. // If the 402 is retryable via token adjustment, do not treat it as exhausted. - if (error?.status === 402 && error instanceof APIError && parseOpenRouterAffordableMaxTokensError(error) !== undefined) { + if (status === 402 && error instanceof APIError && parseOpenRouterAffordableMaxTokensError(error) !== undefined) { return false } return ( - error?.status === 402 || + status === 402 || msg.includes('quota_exhausted') || - ((error?.status === 429 || error?.status === 403 || error?.status === 400) && + ((status === 429 || status === 403 || status === 400) && (msg.includes('limit: 0') || msg.includes('exceeded your current quota') || msg.includes('credit') || diff --git a/src/services/compact/snipCompact.ts b/src/services/compact/snipCompact.ts index 8e60c72a08..1a065b966c 100644 --- a/src/services/compact/snipCompact.ts +++ b/src/services/compact/snipCompact.ts @@ -1,5 +1,6 @@ import { randomUUID } from 'crypto' import type { UUID } from 'crypto' +import type { Message } from '../../types/message.js' import { deriveShortMessageId } from '../../utils/messages.js' // Module-level registry of message UUIDs queued for removal. We resolve the @@ -35,7 +36,23 @@ function normalizeSnipShortId(shortId: string): string { // and were queued. Unresolvable short IDs (stale or hallucinated) are skipped, // so callers can report the genuinely-queued count rather than the raw request // length. -export function markForSnip(shortIds: string[], messages: any[]): UUID[] { +// Structural message shape for snip operations. Tests construct fake messages +// with `string` uuids; using `Message` directly would reject them since the +// production `uuid` field is a branded `${string}-${string}-...` template type. +type SnipMessage = { + uuid?: string + type?: string + message?: { content?: unknown } + content?: unknown + subtype?: string + attachment?: { type?: string } + snipMetadata?: { removedUuids?: UUID[] } + isMeta?: boolean + timestamp?: string + level?: string +} + +export function markForSnip(shortIds: string[], messages: SnipMessage[]): UUID[] { const shortIdToUuid = new Map() for (const msg of messages) { if (msg?.uuid) { @@ -73,14 +90,14 @@ const DEFAULT_NUDGE_INTERVAL_TOKENS = 10_000 /** * Rough per-message token estimate: content length ÷ 4. */ -function estimateTokens(msg: any): number { +function estimateTokens(msg: SnipMessage): number { const content = msg?.message?.content ?? msg?.content ?? '' const text = typeof content === 'string' ? content : JSON.stringify(content) return Math.ceil(text.length / 4) } export function shouldNudgeForSnips( - messages: any[], + messages: SnipMessage[], intervalTokens = DEFAULT_NUDGE_INTERVAL_TOKENS, ): boolean { let accumulated = 0 @@ -98,19 +115,21 @@ export function shouldNudgeForSnips( return false } -export function snipCompactIfNeeded( - messages: any[], -): { messages: any[]; tokensFreed: number; boundaryMessage?: any } { +export function snipCompactIfNeeded( + messages: T[], +): { messages: T[]; tokensFreed: number; boundaryMessage?: SnipMessage } { if (pendingSnipUuids.size === 0) { return { messages, tokensFreed: 0 } } // Match pending UUIDs against THIS conversation's messages. UUIDs that belong // to another in-process session won't be present here, so they stay pending. - const uuidsToRemove = new Set() + // Use plain `string` keys so the Set also accepts the test fixtures' `uuid: 'u4'` + // shapes; production callers pass branded UUIDs which coerce cleanly. + const uuidsToRemove = new Set() for (const msg of messages) { - const uuid = msg?.uuid as UUID | undefined - if (uuid && pendingSnipUuids.has(uuid)) uuidsToRemove.add(uuid) + const uuid = msg?.uuid + if (uuid && pendingSnipUuids.has(uuid as UUID)) uuidsToRemove.add(uuid as UUID) } if (uuidsToRemove.size === 0) { @@ -118,7 +137,7 @@ export function snipCompactIfNeeded( } // Consume only the matched UUIDs; another session's pending removals survive. - for (const uuid of uuidsToRemove) pendingSnipUuids.delete(uuid) + for (const uuid of uuidsToRemove) pendingSnipUuids.delete(uuid as unknown as UUID) // A tool interaction spans an assistant tool_use and the paired user // tool_result. The model can snip from EITHER side, so we pair in both @@ -164,7 +183,9 @@ export function snipCompactIfNeeded( if (msg?.type !== 'assistant') continue const blocks = msg?.message?.content if (!Array.isArray(blocks)) continue - const toolUses = (blocks as any[]).filter(b => b?.type === 'tool_use') + const toolUses = (blocks as unknown[]).filter((b): b is { type: string; id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_use', + ) if (toolUses.length === 0) continue // Only the paired-drop branch needs the whole turn to be tool blocks: an // explicitly-snipped message (uuidsToRemove) is the model's deliberate choice @@ -174,14 +195,14 @@ export function snipCompactIfNeeded( const droppable = uuidsToRemove.has(msg?.uuid) || (isPureToolUseTurn && - toolUses.every((t: any) => snippedResultToolUseIds.has(t?.id))) + toolUses.every((t) => typeof t.id === 'string' && snippedResultToolUseIds.has(t.id))) if (droppable) { - for (const t of toolUses) if (t?.id) safeToolUseIds.add(t.id as string) + for (const t of toolUses) if (t?.id) safeToolUseIds.add(t.id) } } let tokensFreed = 0 - const surviving: any[] = [] + const surviving: T[] = [] // UUIDs actually removed from the live context: explicitly-snipped messages // that were cleanly removable, plus the paired half of each snipped tool // interaction (paired tool-result users, or paired assistant tool-uses). They @@ -200,10 +221,13 @@ export function snipCompactIfNeeded( // tool_use, which the next API-prep pass repairs with a synthetic // placeholder, so the snip would not actually take effect. Keep it. if (msg?.type === 'user' && Array.isArray(msg?.message?.content)) { - const results = (msg.message.content as any[]).filter(b => b?.type === 'tool_result') + const userBlocks = msg.message.content as unknown[] + const results = userBlocks.filter((b): b is { type: string; tool_use_id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_result', + ) if ( results.length > 0 && - !results.every((r: any) => safeToolUseIds.has(r?.tool_use_id)) + !results.every((r) => typeof r.tool_use_id === 'string' && safeToolUseIds.has(r.tool_use_id)) ) { surviving.push(msg) continue @@ -215,12 +239,14 @@ export function snipCompactIfNeeded( } // Drop user messages whose content is entirely tool results for snipped tool calls if (msg?.type === 'user' && Array.isArray(msg?.message?.content)) { - const blocks = msg.message.content as any[] - const results = blocks.filter(b => b?.type === 'tool_result') + const userBlocks = msg.message.content as unknown[] + const results = userBlocks.filter((b): b is { type: string; tool_use_id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_result', + ) if ( results.length > 0 && - results.length === blocks.length && - results.every((r: any) => snippedToolUseIds.has(r?.tool_use_id)) + results.length === userBlocks.length && + results.every((r) => typeof r.tool_use_id === 'string' && snippedToolUseIds.has(r.tool_use_id)) ) { tokensFreed += estimateTokens(msg) if (msg?.uuid) removedUuids.add(msg.uuid as UUID) @@ -231,12 +257,14 @@ export function snipCompactIfNeeded( // side. Mirrors the user-message .every() guard: if any tool_use in this // turn still has a surviving result, keep the message to avoid orphaning it. if (msg?.type === 'assistant' && Array.isArray(msg?.message?.content)) { - const blocks = msg.message.content as any[] - const toolUses = blocks.filter(b => b?.type === 'tool_use') + const assistantBlocks = msg.message.content as unknown[] + const toolUses = assistantBlocks.filter((b): b is { type: string; id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_use', + ) if ( toolUses.length > 0 && - toolUses.length === blocks.length && - toolUses.every((t: any) => snippedResultToolUseIds.has(t?.id)) + toolUses.length === assistantBlocks.length && + toolUses.every((t) => typeof t.id === 'string' && snippedResultToolUseIds.has(t.id)) ) { tokensFreed += estimateTokens(msg) if (msg?.uuid) removedUuids.add(msg.uuid as UUID) @@ -252,9 +280,9 @@ export function snipCompactIfNeeded( return { messages, tokensFreed: 0 } } - const boundaryMessage = { + const boundaryMessage: SnipMessage = { type: 'system' as const, - subtype: 'snip_boundary', + subtype: 'snip_boundary' as const, content: 'Conversation history snipped', isMeta: false as const, timestamp: new Date().toISOString(), @@ -272,7 +300,7 @@ export function snipCompactIfNeeded( } export function isSnipMarkerMessage(message: unknown): boolean { - return (message as any)?.subtype === 'snip_boundary' + return (message as { subtype?: unknown } | null | undefined)?.subtype === 'snip_boundary' } /** Exposed for test isolation only — do not call in production code. */ diff --git a/src/services/compact/snipProjection.ts b/src/services/compact/snipProjection.ts index b766052e9e..39f9dae446 100644 --- a/src/services/compact/snipProjection.ts +++ b/src/services/compact/snipProjection.ts @@ -1,5 +1,5 @@ export function isSnipBoundaryMessage(message: unknown): boolean { - return Boolean((message as any)?.snipMetadata) + return Boolean((message as { snipMetadata?: unknown } | null | undefined)?.snipMetadata) } /** @@ -10,13 +10,15 @@ export function isSnipBoundaryMessage(message: unknown): boolean { export function projectSnippedView(messages: T[]): T[] { const removedUuids = new Set() for (const msg of messages) { - const uuids = (msg as any)?.snipMetadata?.removedUuids + const uuids = (msg as { snipMetadata?: { removedUuids?: unknown } } | null | undefined)?.snipMetadata?.removedUuids if (!Array.isArray(uuids)) continue - for (const uuid of uuids) removedUuids.add(uuid as string) + for (const uuid of uuids) { + if (typeof uuid === 'string') removedUuids.add(uuid) + } } if (removedUuids.size === 0) return messages return messages.filter(msg => { - const uuid = (msg as any)?.uuid - return !uuid || !removedUuids.has(uuid as string) + const uuid = (msg as { uuid?: string } | null | undefined)?.uuid + return !uuid || !removedUuids.has(uuid) }) } diff --git a/src/tools/WebSearchTool/WebSearchTool.ts b/src/tools/WebSearchTool/WebSearchTool.ts index a748e345f7..4e68b69543 100644 --- a/src/tools/WebSearchTool/WebSearchTool.ts +++ b/src/tools/WebSearchTool/WebSearchTool.ts @@ -254,20 +254,16 @@ function addCodexSource( }) } -function getCodexSources(item: Record): unknown[] { - if (Array.isArray(item.action?.sources)) { - return item.action.sources - } - if (Array.isArray(item.sources)) { - return item.sources - } - if (Array.isArray(item.result?.sources)) { - return item.result.sources - } +function getCodexSources(item: Record): unknown[] { + const action = (item.action ?? {}) as Record + const result = (item.result ?? {}) as Record + if (Array.isArray(action.sources)) return action.sources + if (Array.isArray(item.sources)) return item.sources as unknown[] + if (Array.isArray(result.sources)) return result.sources return [] } -function extractCodexWebSearchFailure(item: Record): string | undefined { +function extractCodexWebSearchFailure(item: Record): string | undefined { // Codex web_search_call items can carry a status field. When the tool // call fails (rate limit, upstream error, model-side guardrail), the // parser should surface a meaningful error rather than the generic @@ -275,10 +271,12 @@ function extractCodexWebSearchFailure(item: Record): string | undef // { type: 'web_search_call', status: 'failed', error: { message?: string } } // { type: 'web_search_call', status: 'failed', action: { error?: { message?: string } } } if (item?.status !== 'failed') return undefined + const error = (item.error ?? {}) as Record + const action = (item.action ?? {}) as Record + const actionError = (action.error ?? {}) as Record const reason = - (typeof item.error?.message === 'string' && item.error.message) || - (typeof item.action?.error?.message === 'string' && - item.action.error.message) || + (typeof error.message === 'string' && error.message) || + (typeof actionError.message === 'string' && actionError.message) || (typeof item.error === 'string' && item.error) || undefined return reason ? `Web search failed: ${reason}` : 'Web search failed.' diff --git a/src/tools/WebSearchTool/providers/bing.ts b/src/tools/WebSearchTool/providers/bing.ts index 4e9b4be9ce..331102b29f 100644 --- a/src/tools/WebSearchTool/providers/bing.ts +++ b/src/tools/WebSearchTool/providers/bing.ts @@ -22,21 +22,26 @@ export const bingProvider: SearchProvider = { url.searchParams.set('q', input.query) url.searchParams.set('count', '15') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { 'Ocp-Apim-Subscription-Key': process.env.BING_API_KEY! }, }, signal, { providerName: 'Bing' }, - ) - - const hits = (data.webPages?.value ?? []).map((r: any) => ({ - title: r.name ?? '', - url: r.url ?? '', - description: r.snippet, - source: r.displayUrl, - })) + )) as { webPages?: { value?: unknown } } | undefined + + const rawResults = data?.webPages?.value + const results = Array.isArray(rawResults) ? rawResults : [] + const hits = (results as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + return { + title: typeof rec.name === 'string' ? rec.name : '', + url: typeof rec.url === 'string' ? rec.url : '', + description: typeof rec.snippet === 'string' ? rec.snippet : undefined, + source: typeof rec.displayUrl === 'string' ? rec.displayUrl : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/brave.ts b/src/tools/WebSearchTool/providers/brave.ts index 7beda80f56..b2f1ce1aa9 100644 --- a/src/tools/WebSearchTool/providers/brave.ts +++ b/src/tools/WebSearchTool/providers/brave.ts @@ -25,7 +25,7 @@ export const braveProvider: SearchProvider = { url.searchParams.set('q', input.query) url.searchParams.set('count', '15') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { @@ -35,14 +35,20 @@ export const braveProvider: SearchProvider = { }, signal, { providerName: 'Brave' }, - ) - - const hits = (data.web?.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.description, - source: r.url ? safeHostname(r.url) : undefined, - })) + )) as { web?: { results?: unknown } } | undefined + + const rawResults = data?.web?.results + const results = Array.isArray(rawResults) ? rawResults : [] + const hits = (results as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: typeof rec.description === 'string' ? rec.description : undefined, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/custom.ts b/src/tools/WebSearchTool/providers/custom.ts index 8d92afabd2..db3622ddbb 100644 --- a/src/tools/WebSearchTool/providers/custom.ts +++ b/src/tools/WebSearchTool/providers/custom.ts @@ -47,7 +47,7 @@ interface ProviderPreset { authHeader?: string authScheme?: string jsonPath?: string - responseAdapter?: (data: any) => SearchHit[] + responseAdapter?: (data: unknown) => SearchHit[] /** * When set, the API key (WEB_KEY) is sent as this query parameter instead * of in an HTTP header. Auth headers are suppressed automatically unless @@ -70,13 +70,24 @@ const BUILT_IN_PROVIDERS: Record = { urlTemplate: 'https://localhost:8080/search', queryParam: 'q', jsonPath: 'results', - responseAdapter(data: any) { - return (data.results ?? []).map((r: any) => ({ - title: r.title ?? r.url, - url: r.url, - description: r.content, - source: r.engine ?? r.source, - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const rawResults = Array.isArray(rec.results) ? rec.results : [] + return (rawResults as unknown[]).map((r) => { + const item = (r ?? {}) as Record + const url = typeof item.url === 'string' ? item.url : '' + return { + title: typeof item.title === 'string' ? item.title : url, + url, + description: typeof item.content === 'string' ? item.content : undefined, + source: + typeof item.engine === 'string' + ? item.engine + : typeof item.source === 'string' + ? item.source + : undefined, + } + }) }, }, google: { @@ -91,13 +102,18 @@ const BUILT_IN_PROVIDERS: Record = { queryParam: 'q', authQueryParam: 'key', envQueryParams: { cx: 'GOOGLE_CSE_ID' }, - responseAdapter(data: any) { - return (data.items ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.link ?? '', - description: r.snippet, - source: r.displayLink, - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const rawItems = Array.isArray(rec.items) ? rec.items : [] + return (rawItems as unknown[]).map((r) => { + const item = (r ?? {}) as Record + return { + title: typeof item.title === 'string' ? item.title : '', + url: typeof item.link === 'string' ? item.link : '', + description: typeof item.snippet === 'string' ? item.snippet : undefined, + source: typeof item.displayLink === 'string' ? item.displayLink : undefined, + } + }) }, }, brave: { @@ -107,13 +123,20 @@ const BUILT_IN_PROVIDERS: Record = { // Brave wants the bare token in X-Subscription-Token, NOT "Bearer ". // Empty scheme overrides the default 'Bearer' applied by buildAuthHeadersForPreset. authScheme: '', - responseAdapter(data: any) { - return (data.web?.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.description, - source: safeHostname(r.url), - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const web = (rec.web ?? {}) as Record + const rawResults = Array.isArray(web.results) ? web.results : [] + return (rawResults as unknown[]).map((r) => { + const item = (r ?? {}) as Record + const url = typeof item.url === 'string' ? item.url : '' + return { + title: typeof item.title === 'string' ? item.title : '', + url, + description: typeof item.description === 'string' ? item.description : undefined, + source: url ? safeHostname(url) : undefined, + } + }) }, }, serpapi: { @@ -121,13 +144,18 @@ const BUILT_IN_PROVIDERS: Record = { queryParam: 'q', authHeader: 'Authorization', authScheme: 'Bearer', - responseAdapter(data: any) { - return (data.organic_results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.link ?? '', - description: r.snippet, - source: r.displayed_link, - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const rawResults = Array.isArray(rec.organic_results) ? rec.organic_results : [] + return (rawResults as unknown[]).map((r) => { + const item = (r ?? {}) as Record + return { + title: typeof item.title === 'string' ? item.title : '', + url: typeof item.link === 'string' ? item.link : '', + description: typeof item.snippet === 'string' ? item.snippet : undefined, + source: typeof item.displayed_link === 'string' ? item.displayed_link : undefined, + } + }) }, }, } @@ -416,7 +444,7 @@ function resolveConfig(): { queryParam: string method: string jsonPath?: string - responseAdapter?: (data: any) => SearchHit[] + responseAdapter?: (data: unknown) => SearchHit[] preset?: ProviderPreset } { const providerName = process.env.WEB_PROVIDER @@ -553,18 +581,18 @@ function buildRequest(query: string) { // Response parsing — flexible, handles many shapes // --------------------------------------------------------------------------- -function walkJsonPath(obj: any, path: string): any { - let current = obj +function walkJsonPath(obj: unknown, path: string): unknown { + let current: unknown = obj for (const seg of path.split('.')) { if (current == null || typeof current !== 'object') return undefined - current = current[seg] + current = (current as Record)[seg] } return current } -function extractFromNode(node: any): SearchHit[] { +function extractFromNode(node: unknown): SearchHit[] { if (!node) return [] - if (Array.isArray(node)) return node.map(normalizeHit).filter(Boolean) as SearchHit[] + if (Array.isArray(node)) return (node as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[] if (typeof node === 'object') { const all: SearchHit[] = [] for (const sub of Object.values(node)) all.push(...extractFromNode(sub)) @@ -574,19 +602,20 @@ function extractFromNode(node: any): SearchHit[] { return [] } -export function extractHits(raw: any, jsonPath?: string): SearchHit[] { +export function extractHits(raw: unknown, jsonPath?: string): SearchHit[] { if (jsonPath) return extractFromNode(walkJsonPath(raw, jsonPath)) - if (Array.isArray(raw)) return raw.map(normalizeHit).filter(Boolean) as SearchHit[] + if (Array.isArray(raw)) return (raw as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[] if (!raw || typeof raw !== 'object') return [] const arrayKeys = ['results', 'items', 'data', 'web', 'organic_results', 'hits', 'entries'] + const obj = raw as Record for (const key of arrayKeys) { - const val = raw[key] - if (Array.isArray(val)) return val.map(normalizeHit).filter(Boolean) as SearchHit[] + const val = obj[key] + if (Array.isArray(val)) return (val as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[] if (val && typeof val === 'object' && !Array.isArray(val)) { const all: SearchHit[] = [] - for (const sub of Object.values(val)) { - if (Array.isArray(sub)) all.push(...(sub.map(normalizeHit).filter(Boolean) as SearchHit[])) + for (const sub of Object.values(val as Record)) { + if (Array.isArray(sub)) all.push(...((sub as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[])) } if (all.length > 0) return all } @@ -599,7 +628,7 @@ export function extractHits(raw: any, jsonPath?: string): SearchHit[] { // Fetch with one retry + timeout // --------------------------------------------------------------------------- -async function fetchWithRetry(url: string, init: RequestInit, signal?: AbortSignal): Promise { +async function fetchWithRetry(url: string, init: RequestInit, signal?: AbortSignal): Promise { const timeoutSec = readPositiveEnvNumber(process.env.WEB_CUSTOM_TIMEOUT_SEC, DEFAULT_TIMEOUT_SECONDS) const timeoutMs = timeoutSec * 1000 let lastErr: Error | undefined diff --git a/src/tools/WebSearchTool/providers/exa.ts b/src/tools/WebSearchTool/providers/exa.ts index 24966f4926..1a104a6ac9 100644 --- a/src/tools/WebSearchTool/providers/exa.ts +++ b/src/tools/WebSearchTool/providers/exa.ts @@ -24,12 +24,14 @@ import { applyDomainFilters, safeHostname, type ProviderOutput } from './types.j import { fetchJsonWithWebSearchTimeout } from './timeout.js' /** Join up to 3 highlight excerpts with an ellipsis separator. */ -function describeFromHighlights(r: any): string | undefined { - const highlights = Array.isArray(r?.highlights) ? r.highlights : null +function describeFromHighlights(r: unknown): string | undefined { + if (!r || typeof r !== 'object') return undefined + const rec = r as Record + const highlights = Array.isArray(rec.highlights) ? rec.highlights : null if (highlights && highlights.length > 0) { - return highlights.slice(0, 3).join(' … ') + return (highlights as unknown[]).slice(0, 3).filter((s): s is string => typeof s === 'string').join(' … ') } - if (typeof r?.text === 'string' && r.text) return r.text + if (typeof rec.text === 'string' && rec.text) return rec.text return undefined } @@ -43,7 +45,7 @@ export const exaProvider: SearchProvider = { async search(input: SearchInput, signal?: AbortSignal): Promise { const start = performance.now() - const body: Record = { + const body: Record = { query: input.query, numResults: 15, type: 'auto', @@ -53,7 +55,7 @@ export const exaProvider: SearchProvider = { if (input.allowed_domains?.length) body.includeDomains = input.allowed_domains if (input.blocked_domains?.length) body.excludeDomains = input.blocked_domains - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( 'https://api.exa.ai/search', { method: 'POST', @@ -65,14 +67,24 @@ export const exaProvider: SearchProvider = { }, signal, { providerName: 'Exa' }, - ) + )) as { results?: unknown } - const hits = (data.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: describeFromHighlights(r), - source: r.url ? safeHostname(r.url) : undefined, - })) + const results = Array.isArray(data.results) ? data.results : [] + const hits = (results as unknown[]).map((r): { + title: string + url: string + description: string | undefined + source: string | undefined + } => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: describeFromHighlights(r), + source: url ? safeHostname(url) : undefined, + } + }) return { // Exa handles domain filtering server-side via includeDomains/excludeDomains diff --git a/src/tools/WebSearchTool/providers/jina.ts b/src/tools/WebSearchTool/providers/jina.ts index 9ff06b7ac0..ea9be7cf1a 100644 --- a/src/tools/WebSearchTool/providers/jina.ts +++ b/src/tools/WebSearchTool/providers/jina.ts @@ -22,7 +22,7 @@ export const jinaProvider: SearchProvider = { url.searchParams.set('q', input.query) url.searchParams.set('count', '10') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { @@ -32,14 +32,27 @@ export const jinaProvider: SearchProvider = { }, signal, { providerName: 'Jina' }, - ) - - const hits = (data.data ?? data.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.description ?? r.snippet ?? r.content, - source: r.url ? safeHostname(r.url) : undefined, - })) + )) as { data?: unknown; results?: unknown } | undefined + + const rawResults = Array.isArray(data?.data) ? data.data : (Array.isArray(data?.results) ? data.results : []) + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + const desc = + typeof rec.description === 'string' + ? rec.description + : typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.content === 'string' + ? rec.content + : undefined + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: desc, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/linkup.ts b/src/tools/WebSearchTool/providers/linkup.ts index 9ccfa4bbfe..c5d73c24c3 100644 --- a/src/tools/WebSearchTool/providers/linkup.ts +++ b/src/tools/WebSearchTool/providers/linkup.ts @@ -18,7 +18,7 @@ export const linkupProvider: SearchProvider = { async search(input: SearchInput, signal?: AbortSignal): Promise { const start = performance.now() - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( 'https://api.linkup.so/v1/search', { method: 'POST', @@ -34,14 +34,27 @@ export const linkupProvider: SearchProvider = { }, signal, { providerName: 'Linkup' }, - ) + )) as { results?: unknown } | undefined - const hits = (data.results ?? []).map((r: any) => ({ - title: r.name ?? r.title ?? '', - url: r.url ?? '', - description: r.snippet ?? r.description ?? r.content, - source: r.url ? safeHostname(r.url) : undefined, - })) + const rawResults = Array.isArray(data?.results) ? data.results : [] + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + const desc = + typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.description === 'string' + ? rec.description + : typeof rec.content === 'string' + ? rec.content + : undefined + return { + title: typeof rec.name === 'string' ? rec.name : (typeof rec.title === 'string' ? rec.title : ''), + url, + description: desc, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/mojeek.ts b/src/tools/WebSearchTool/providers/mojeek.ts index f6157dd41d..1411e2cc93 100644 --- a/src/tools/WebSearchTool/providers/mojeek.ts +++ b/src/tools/WebSearchTool/providers/mojeek.ts @@ -30,23 +30,37 @@ export const mojeekProvider: SearchProvider = { headers['Authorization'] = `Bearer ${process.env.MOJEEK_API_KEY}` } - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers, }, signal, { providerName: 'Mojeek' }, - ) + )) as { response?: { results?: unknown }; results?: unknown } | undefined - const rawResults = data?.response?.results ?? data?.results ?? [] + const nestedResults = data?.response?.results + const rawResults = Array.isArray(nestedResults) + ? nestedResults + : Array.isArray(data?.results) + ? data.results + : [] - const hits = rawResults.map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.snippet ?? r.desc, - source: r.url ? safeHostname(r.url) : undefined, - })) + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: + typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.desc === 'string' + ? rec.desc + : undefined, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/tavily.ts b/src/tools/WebSearchTool/providers/tavily.ts index 9585d36821..7ce5c5ff03 100644 --- a/src/tools/WebSearchTool/providers/tavily.ts +++ b/src/tools/WebSearchTool/providers/tavily.ts @@ -18,7 +18,7 @@ export const tavilyProvider: SearchProvider = { async search(input: SearchInput, signal?: AbortSignal): Promise { const start = performance.now() - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( 'https://api.tavily.com/search', { method: 'POST', @@ -34,14 +34,24 @@ export const tavilyProvider: SearchProvider = { }, signal, { providerName: 'Tavily' }, - ) + )) as { results?: unknown } | undefined - const hits = (data.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.content ?? r.snippet, - source: r.url ? safeHostname(r.url) : undefined, - })) + const rawResults = Array.isArray(data?.results) ? data.results : [] + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: + typeof rec.content === 'string' + ? rec.content + : typeof rec.snippet === 'string' + ? rec.snippet + : undefined, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/timeout.ts b/src/tools/WebSearchTool/providers/timeout.ts index 33c92e89af..df637d4640 100644 --- a/src/tools/WebSearchTool/providers/timeout.ts +++ b/src/tools/WebSearchTool/providers/timeout.ts @@ -133,7 +133,7 @@ export async function fetchJsonWithWebSearchTimeout( init: Parameters[1], signal: AbortSignal | undefined, options: WebSearchTimeoutOptions = {}, -): Promise { +): Promise { const providerName = options.providerName ?? 'Web search provider' return withWebSearchTimeout( diff --git a/src/tools/WebSearchTool/providers/types.ts b/src/tools/WebSearchTool/providers/types.ts index 2b29d537bf..f29eabaeae 100644 --- a/src/tools/WebSearchTool/providers/types.ts +++ b/src/tools/WebSearchTool/providers/types.ts @@ -53,15 +53,18 @@ const SOURCE_KEYS = [ 'source', 'domain', 'displayLink', 'displayed_link', 'engine', ] as const -function firstMatch(obj: any, keys: readonly string[]): string | undefined { +function firstMatch(obj: unknown, keys: readonly string[]): string | undefined { + if (!obj || typeof obj !== 'object') return undefined + const record = obj as Record for (const k of keys) { - if (typeof obj?.[k] === 'string' && obj[k]) return obj[k] + const v = record[k] + if (typeof v === 'string' && v) return v } return undefined } /** Extract a SearchHit from any object shape using well-known field aliases. */ -export function normalizeHit(raw: any): SearchHit | null { +export function normalizeHit(raw: unknown): SearchHit | null { if (!raw || typeof raw !== 'object') return null const title = firstMatch(raw, TITLE_KEYS) const url = firstMatch(raw, URL_KEYS) diff --git a/src/tools/WebSearchTool/providers/you.ts b/src/tools/WebSearchTool/providers/you.ts index 2523b6157c..755c1e2cc2 100644 --- a/src/tools/WebSearchTool/providers/you.ts +++ b/src/tools/WebSearchTool/providers/you.ts @@ -22,24 +22,42 @@ export const youProvider: SearchProvider = { url.searchParams.set('query', input.query) url.searchParams.set('num_web_results', '10') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { 'X-API-Key': process.env.YOU_API_KEY! }, }, signal, { providerName: 'You.com' }, - ) + )) as { results?: { web?: unknown } | unknown } | undefined - const webResults = data?.results?.web ?? data?.results ?? [] + const resultsField = data?.results + const nestedWeb = + resultsField && typeof resultsField === 'object' && 'web' in resultsField + ? (resultsField as { web?: unknown }).web + : undefined + const rawResults = Array.isArray(nestedWeb) + ? nestedWeb + : Array.isArray(resultsField) + ? resultsField + : [] - const hits = webResults.map((r: any) => { - const snippet = Array.isArray(r.snippets) ? r.snippets[0] : r.snippet + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + const snippets = Array.isArray(rec.snippets) ? rec.snippets : [] + const snippet = snippets.length > 0 && typeof snippets[0] === 'string' ? snippets[0] : undefined return { - title: r.title ?? '', - url: r.url ?? '', - description: snippet ?? r.description, - source: r.url ? safeHostname(r.url) : undefined, + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: + snippet ?? + (typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.description === 'string' + ? rec.description + : undefined), + source: url ? safeHostname(url) : undefined, } }) diff --git a/src/utils/conversationArc.ts b/src/utils/conversationArc.ts index 05da1aad0f..63dc3f161b 100644 --- a/src/utils/conversationArc.ts +++ b/src/utils/conversationArc.ts @@ -275,8 +275,13 @@ function extractTextFromContent(content: unknown): string { if (typeof content === 'string') return content if (Array.isArray(content)) { return content - .filter((block: any) => block.type === 'text' && typeof block.text === 'string') - .map((block: any) => block.text) + .filter((block: unknown): block is { type: string; text: string } => + typeof block === 'object' && + block !== null && + (block as { type?: unknown }).type === 'text' && + typeof (block as { text?: unknown }).text === 'string', + ) + .map((block) => block.text) .join('\n') } return '' diff --git a/src/utils/knowledgeGraph.ts b/src/utils/knowledgeGraph.ts index 2839ed01ed..b1c2adef31 100644 --- a/src/utils/knowledgeGraph.ts +++ b/src/utils/knowledgeGraph.ts @@ -141,10 +141,43 @@ interface LegacySource { path: string kind: 'json' | 'sqlite' mtimeMs: number - data: any + data: LegacyData artifactBytes: Map } +// Legacy knowledge-graph shapes (JSON stores written by older OpenClaude +// versions and the SQLite store read at line ~400). All field types are +// `unknown` because the on-disk format predates our schema; helpers in this +// file sanitize as they read. +type LegacyEntity = { + id?: unknown + name?: unknown + type?: unknown + attributes?: Record +} +type LegacyRelation = { + sourceId?: unknown + targetId?: unknown + type?: unknown +} +type LegacySummary = { + id?: unknown + content?: unknown + keywords?: unknown + timestamp?: unknown +} +// Some older JSON dumps stored entities as an array; the SQLite reader +// produces a record keyed by id. Both shapes are normalized to the +// `Record` form before `mergeLegacySources` consumes them. +type LegacyEntityBag = Record | LegacyEntity[] +type LegacyData = { + entities: LegacyEntityBag + relations: LegacyRelation[] + summaries: LegacySummary[] + rules: string[] + _droppedEntityAliases?: Map +} + function getLegacySourceMtime(path: string, kind: LegacySource['kind']): number { let mtimeMs = statSync(path).mtimeMs if (kind === 'sqlite') { @@ -219,12 +252,34 @@ function sqliteDataArtifactsMatch( }) } -function normalizeLegacyData(value: any): any { +function normalizeLegacyData(value: unknown): LegacyData { + const v = value as Partial | null | undefined + const rawEntities = v?.entities + let entities: LegacyEntityBag = {} + if (Array.isArray(rawEntities)) { + // Array form: mergeLegacySources falls back to `String(id ?? entryKey)`, + // so key the array form by the index to preserve the same fallback + // contract. An entry with an explicit id overrides any previous entry + // with the same id (last-wins) so concurrent stores converge to the + // newest copy; entryKey collisions across arrays are not possible + // because the outer `mergeLegacySources` loop never sees two arrays at + // once. + const out: Record = {} + for (let i = 0; i < rawEntities.length; i++) { + const e = rawEntities[i] + if (e && typeof e === 'object') { + out[String(i)] = e as LegacyEntity + } + } + entities = out + } else if (rawEntities && typeof rawEntities === 'object') { + entities = rawEntities as Record + } return { - entities: value?.entities && typeof value.entities === 'object' ? value.entities : {}, - relations: Array.isArray(value?.relations) ? value.relations : [], - summaries: Array.isArray(value?.summaries) ? value.summaries : [], - rules: Array.isArray(value?.rules) ? value.rules : [], + entities, + relations: Array.isArray(v?.relations) ? (v.relations as LegacyRelation[]) : [], + summaries: Array.isArray(v?.summaries) ? (v.summaries as LegacySummary[]) : [], + rules: Array.isArray(v?.rules) ? (v.rules as string[]) : [], } } @@ -241,7 +296,7 @@ function isLegacyDataShape(value: unknown): boolean { } /** Merge every recoverable legacy location, preferring the newest copy on conflicts. */ -function mergeLegacySources(sources: LegacySource[]): any { +function mergeLegacySources(sources: LegacySource[]): LegacyData { const merged = normalizeLegacyData(null) const droppedAliases = new Map() const entityNames = new Map() @@ -251,7 +306,7 @@ function mergeLegacySources(sources: LegacySource[]): any { for (const source of [...sources].sort((a, b) => b.mtimeMs - a.mtimeMs)) { const data = normalizeLegacyData(source.data) - for (const [entryKey, rawEntity] of Object.entries(data.entities) as [string, any][]) { + for (const [entryKey, rawEntity] of Object.entries(data.entities)) { if (!rawEntity || typeof rawEntity !== 'object') continue const id = String(rawEntity.id ?? entryKey) const nameKey = String(rawEntity.name ?? '').trim().toLowerCase() @@ -394,18 +449,23 @@ function migrateLegacyKnowledgeGraph(): void { } type SqliteReadResult = - | { ok: true; data: any } + | { ok: true; data: LegacyData } | { ok: false; reason: 'not_found' | 'unavailable' | 'error' } +interface SqliteDb { + close?: () => void +} +type SqliteRow = Record + function readLegacySqliteStore(dbPath: string): SqliteReadResult { if (!existsSync(dbPath)) return { ok: false, reason: 'not_found' } - let openDatabase: () => { db: any; queryAll: (sql: string) => any[] } + let openDatabase: () => { db: SqliteDb; queryAll: (sql: string) => SqliteRow[] } try { const Database = _require('bun:sqlite').Database openDatabase = () => { - const db = new Database(dbPath, { readonly: true }) - return { db, queryAll: sql => db.query(sql).all() as any[] } + const db: { query: (sql: string) => { all: () => SqliteRow[] }; close?: () => void } = new Database(dbPath, { readonly: true }) + return { db, queryAll: sql => db.query(sql).all() } } } catch { try { @@ -414,8 +474,8 @@ function readLegacySqliteStore(dbPath: string): SqliteReadResult { // OpenClaude install can still migrate after the user changes runtimes. const DatabaseSync = _require('node:sqlite').DatabaseSync openDatabase = () => { - const db = new DatabaseSync(dbPath, { readOnly: true }) - return { db, queryAll: sql => db.prepare(sql).all() as any[] } + const db: { prepare: (sql: string) => { all: () => SqliteRow[] }; close?: () => void } = new DatabaseSync(dbPath, { readOnly: true }) + return { db, queryAll: sql => db.prepare(sql).all() } } } catch { console.error( @@ -425,43 +485,58 @@ function readLegacySqliteStore(dbPath: string): SqliteReadResult { } } - let db: any + let db: SqliteDb | null = null try { const opened = openDatabase() db = opened.db const queryAll = opened.queryAll - const data: any = { entities: {}, relations: [], summaries: [], rules: [] } + const data: LegacyData = { entities: {}, relations: [], summaries: [], rules: [] } const entityRows = queryAll('SELECT id, type, name, attributes FROM entities') for (const row of entityRows) { - data.entities[row.id] = { + const id = String(row.id ?? '') + const rawAttrs = row.attributes + data.entities[id] = { id: row.id, type: row.type ?? '', name: row.name ?? '', - attributes: row.attributes ? JSON.parse(row.attributes) : {}, + attributes: + typeof rawAttrs === 'string' && rawAttrs.length > 0 + ? (JSON.parse(rawAttrs) as Record) + : {}, } } - data.relations = queryAll('SELECT source_id, target_id, type FROM relations').map( - (r: any) => ({ sourceId: r.source_id, targetId: r.target_id, type: r.type }), - ) + data.relations = queryAll('SELECT source_id, target_id, type FROM relations').map(r => ({ + sourceId: r.source_id, + targetId: r.target_id, + type: r.type, + })) const summaryRows = queryAll('SELECT id, content, keywords, timestamp FROM summaries') - data.summaries = summaryRows.map((r: any) => ({ - id: r.id, - content: r.content ?? '', - keywords: r.keywords ? JSON.parse(r.keywords) : [], - timestamp: r.timestamp ?? 0, - })) + data.summaries = summaryRows.map(r => { + const rawKeywords = r.keywords + return { + id: r.id, + content: r.content ?? '', + keywords: + typeof rawKeywords === 'string' && rawKeywords.length > 0 + ? (JSON.parse(rawKeywords) as unknown) + : [], + timestamp: r.timestamp ?? 0, + } + }) - data.rules = queryAll('SELECT content FROM rules').map((r: any) => r.content) + data.rules = queryAll('SELECT content FROM rules') + .map(r => r.content) + .filter((c): c is string => typeof c === 'string') return { ok: true, data } } catch (e) { console.error('[knowledgeGraph] Failed to read SQLite store:', e) return { ok: false, reason: 'error' } } finally { - try { db?.close() } catch { /* ignore close failures after read */ } + try { db?.close?.() } catch { /* ignore close failures after read */ } } } @@ -547,7 +622,7 @@ function archiveLegacySources(sources: LegacySource[]): boolean { return true } -function doMigration(data: any, sources: LegacySource[], projectKey: string): void { +function doMigration(data: LegacyData, sources: LegacySource[], projectKey: string): void { // Archive and byte-verify every discovered source before writing facts. If // any root/cwd store cannot be preserved, leave all live stores in place and // retry later rather than completing a partial migration. @@ -590,7 +665,7 @@ function doMigration(data: any, sources: LegacySource[], projectKey: string): vo // into the YAML title and body (P1). The old fact extractor stored raw env // values in both attributes and names. const legacyEntities = Object.entries(data.entities ?? {}) - for (const [legacyId, entity] of legacyEntities as [string, any][]) { + for (const [legacyId, entity] of legacyEntities) { const safeName = safeEntityName(entity) if (!safeName) { continue @@ -681,7 +756,7 @@ ${safeRule} } // Preserve legacy relations as a single relation-set fact (remapped using legacyToNewId, H4) - const relations: Relation[] = (data.relations ?? []).flatMap((r: any) => { + const relations: Relation[] = (data.relations ?? []).flatMap((r: LegacyRelation) => { const rawSourceId = String(r.sourceId ?? '') const rawTargetId = String(r.targetId ?? '') const sourceId = legacyToNewId.get(rawSourceId) || sanitizeLegacyReference(rawSourceId) diff --git a/src/utils/messages.ts b/src/utils/messages.ts index a4afc17bb1..a2cf59888e 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1745,25 +1745,27 @@ export function normalizeMessagesForAPI( // When tool search is enabled, preserve all fields including 'caller' if (toolSearchEnabled) { - const { extra_content, ...restBlock } = block as any + const blockWithExtra = block as unknown as { extra_content?: unknown } & Record + const { extra_content, ...restBlock } = blockWithExtra return { ...restBlock, name: canonicalName, input: normalizedInput, - ...(extra_content ? { extra_content } : {}) - } + ...(extra_content ? { extra_content } : {}), + } as BetaContentBlock } // When tool search is NOT enabled, explicitly construct tool_use // block with only standard API fields to avoid sending fields like // 'caller' that may be stored in sessions from tool search runs - return { + const blockExtra = (block as unknown as { extra_content?: unknown }).extra_content + return { type: 'tool_use' as const, id: block.id, name: canonicalName, input: normalizedInput, - ...((block as any).extra_content ? { extra_content: (block as any).extra_content } : {}) - } + ...(blockExtra ? { extra_content: blockExtra } : {}), + } as BetaContentBlock } return block }), diff --git a/src/utils/optionalRuntimeModule.ts b/src/utils/optionalRuntimeModule.ts index 5b771ef486..cc61378259 100644 --- a/src/utils/optionalRuntimeModule.ts +++ b/src/utils/optionalRuntimeModule.ts @@ -15,7 +15,7 @@ const runtimeImport = new Function( 'specifier', 'return import(specifier)', -) as (specifier: string) => Promise +) as (specifier: string) => Promise export class OptionalRuntimeModuleUnavailableError extends Error { constructor( @@ -37,7 +37,7 @@ export function isOptionalRuntimeModuleUnavailableError( } /** Raw runtime import — rejects with the underlying error if the module is missing. */ -export function importRuntimeModule(specifier: string): Promise { +export function importRuntimeModule(specifier: string): Promise { return runtimeImport(specifier) } diff --git a/src/utils/settings/types.ts b/src/utils/settings/types.ts index 121a17c3c0..c2e81c43c1 100644 --- a/src/utils/settings/types.ts +++ b/src/utils/settings/types.ts @@ -575,11 +575,11 @@ export const SettingsSchema = lazySchema(() => 'and feed errors back for self-repair.', ), worktree: z - .preprocess((val: any) => { + .preprocess((val: unknown) => { if (val && typeof val === 'object') { - const copy = { ...val } + const copy: Record = { ...(val as Record) } if ('enableGitLongPaths' in val && !('autoConfigureLongPaths' in val)) { - copy.autoConfigureLongPaths = val.enableGitLongPaths + copy.autoConfigureLongPaths = (val as Record).enableGitLongPaths } return copy } diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 784da306f8..f40f8528b5 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -47,7 +47,7 @@ export function assertObject(value: unknown, field: string): asserts value is Re /** * Assert that a value is a function. */ -export function assertFunction(value: unknown, field: string): asserts value is (...args: any[]) => any { +export function assertFunction(value: unknown, field: string): asserts value is (...args: unknown[]) => unknown { if (typeof value !== 'function') { throw new TypeError(`missing or invalid '${field}' (expected function)`) }