semantic bufo search find-bufo.com
bufo

initial commit: hybrid bufo search with normalized scoring

- multimodal image embeddings via voyage-multimodal-3
- hybrid search combining vector (ANN) + text (BM25) with RRF
- normalized similarity scores (0-95%) for intuitive UI display
- ingestion pipeline for bufo.zone images
- clean web interface with image grid and relevance scores

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+3730
+10
.env.example
··· 1 + # server configuration 2 + HOST=0.0.0.0 3 + PORT=8080 4 + 5 + # turbopuffer configuration 6 + TURBOPUFFER_API_KEY=your_turbopuffer_api_key_here 7 + TURBOPUFFER_NAMESPACE=bufos 8 + 9 + # voyage ai configuration (for multimodal embeddings) 10 + VOYAGE_API_TOKEN=your_voyage_api_token_here
+6
.gitignore
··· 1 + /target 2 + /sandbox 3 + /data 4 + .env 5 + server.log 6 + test_*.py
+2529
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "actix-codec" 7 + version = "0.5.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" 10 + dependencies = [ 11 + "bitflags", 12 + "bytes", 13 + "futures-core", 14 + "futures-sink", 15 + "memchr", 16 + "pin-project-lite", 17 + "tokio", 18 + "tokio-util", 19 + "tracing", 20 + ] 21 + 22 + [[package]] 23 + name = "actix-cors" 24 + version = "0.7.1" 25 + source = "registry+https://github.com/rust-lang/crates.io-index" 26 + checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d" 27 + dependencies = [ 28 + "actix-utils", 29 + "actix-web", 30 + "derive_more", 31 + "futures-util", 32 + "log", 33 + "once_cell", 34 + "smallvec", 35 + ] 36 + 37 + [[package]] 38 + name = "actix-files" 39 + version = "0.6.8" 40 + source = "registry+https://github.com/rust-lang/crates.io-index" 41 + checksum = "6c0d87f10d70e2948ad40e8edea79c8e77c6c66e0250a4c1f09b690465199576" 42 + dependencies = [ 43 + "actix-http", 44 + "actix-service", 45 + "actix-utils", 46 + "actix-web", 47 + "bitflags", 48 + "bytes", 49 + "derive_more", 50 + "futures-core", 51 + "http-range", 52 + "log", 53 + "mime", 54 + "mime_guess", 55 + "percent-encoding", 56 + "pin-project-lite", 57 + "v_htmlescape", 58 + ] 59 + 60 + [[package]] 61 + name = "actix-http" 62 + version = "3.11.2" 63 + source = "registry+https://github.com/rust-lang/crates.io-index" 64 + checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" 65 + dependencies = [ 66 + "actix-codec", 67 + "actix-rt", 68 + "actix-service", 69 + "actix-utils", 70 + "base64", 71 + "bitflags", 72 + "brotli", 73 + "bytes", 74 + "bytestring", 75 + "derive_more", 76 + "encoding_rs", 77 + "flate2", 78 + "foldhash", 79 + "futures-core", 80 + "h2 0.3.27", 81 + "http 0.2.12", 82 + "httparse", 83 + "httpdate", 84 + "itoa", 85 + "language-tags", 86 + "local-channel", 87 + "mime", 88 + "percent-encoding", 89 + "pin-project-lite", 90 + "rand", 91 + "sha1", 92 + "smallvec", 93 + "tokio", 94 + "tokio-util", 95 + "tracing", 96 + "zstd", 97 + ] 98 + 99 + [[package]] 100 + name = "actix-macros" 101 + version = "0.2.4" 102 + source = "registry+https://github.com/rust-lang/crates.io-index" 103 + checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" 104 + dependencies = [ 105 + "quote", 106 + "syn", 107 + ] 108 + 109 + [[package]] 110 + name = "actix-router" 111 + version = "0.5.3" 112 + source = "registry+https://github.com/rust-lang/crates.io-index" 113 + checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" 114 + dependencies = [ 115 + "bytestring", 116 + "cfg-if", 117 + "http 0.2.12", 118 + "regex", 119 + "regex-lite", 120 + "serde", 121 + "tracing", 122 + ] 123 + 124 + [[package]] 125 + name = "actix-rt" 126 + version = "2.11.0" 127 + source = "registry+https://github.com/rust-lang/crates.io-index" 128 + checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" 129 + dependencies = [ 130 + "futures-core", 131 + "tokio", 132 + ] 133 + 134 + [[package]] 135 + name = "actix-server" 136 + version = "2.6.0" 137 + source = "registry+https://github.com/rust-lang/crates.io-index" 138 + checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" 139 + dependencies = [ 140 + "actix-rt", 141 + "actix-service", 142 + "actix-utils", 143 + "futures-core", 144 + "futures-util", 145 + "mio", 146 + "socket2 0.5.10", 147 + "tokio", 148 + "tracing", 149 + ] 150 + 151 + [[package]] 152 + name = "actix-service" 153 + version = "2.0.3" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" 156 + dependencies = [ 157 + "futures-core", 158 + "pin-project-lite", 159 + ] 160 + 161 + [[package]] 162 + name = "actix-utils" 163 + version = "3.0.1" 164 + source = "registry+https://github.com/rust-lang/crates.io-index" 165 + checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" 166 + dependencies = [ 167 + "local-waker", 168 + "pin-project-lite", 169 + ] 170 + 171 + [[package]] 172 + name = "actix-web" 173 + version = "4.11.0" 174 + source = "registry+https://github.com/rust-lang/crates.io-index" 175 + checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" 176 + dependencies = [ 177 + "actix-codec", 178 + "actix-http", 179 + "actix-macros", 180 + "actix-router", 181 + "actix-rt", 182 + "actix-server", 183 + "actix-service", 184 + "actix-utils", 185 + "actix-web-codegen", 186 + "bytes", 187 + "bytestring", 188 + "cfg-if", 189 + "cookie", 190 + "derive_more", 191 + "encoding_rs", 192 + "foldhash", 193 + "futures-core", 194 + "futures-util", 195 + "impl-more", 196 + "itoa", 197 + "language-tags", 198 + "log", 199 + "mime", 200 + "once_cell", 201 + "pin-project-lite", 202 + "regex", 203 + "regex-lite", 204 + "serde", 205 + "serde_json", 206 + "serde_urlencoded", 207 + "smallvec", 208 + "socket2 0.5.10", 209 + "time", 210 + "tracing", 211 + "url", 212 + ] 213 + 214 + [[package]] 215 + name = "actix-web-codegen" 216 + version = "4.3.0" 217 + source = "registry+https://github.com/rust-lang/crates.io-index" 218 + checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" 219 + dependencies = [ 220 + "actix-router", 221 + "proc-macro2", 222 + "quote", 223 + "syn", 224 + ] 225 + 226 + [[package]] 227 + name = "adler2" 228 + version = "2.0.1" 229 + source = "registry+https://github.com/rust-lang/crates.io-index" 230 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 231 + 232 + [[package]] 233 + name = "aho-corasick" 234 + version = "1.1.3" 235 + source = "registry+https://github.com/rust-lang/crates.io-index" 236 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 237 + dependencies = [ 238 + "memchr", 239 + ] 240 + 241 + [[package]] 242 + name = "alloc-no-stdlib" 243 + version = "2.0.4" 244 + source = "registry+https://github.com/rust-lang/crates.io-index" 245 + checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 246 + 247 + [[package]] 248 + name = "alloc-stdlib" 249 + version = "0.2.2" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 252 + dependencies = [ 253 + "alloc-no-stdlib", 254 + ] 255 + 256 + [[package]] 257 + name = "anstream" 258 + version = "0.6.21" 259 + source = "registry+https://github.com/rust-lang/crates.io-index" 260 + checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 261 + dependencies = [ 262 + "anstyle", 263 + "anstyle-parse", 264 + "anstyle-query", 265 + "anstyle-wincon", 266 + "colorchoice", 267 + "is_terminal_polyfill", 268 + "utf8parse", 269 + ] 270 + 271 + [[package]] 272 + name = "anstyle" 273 + version = "1.0.13" 274 + source = "registry+https://github.com/rust-lang/crates.io-index" 275 + checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 276 + 277 + [[package]] 278 + name = "anstyle-parse" 279 + version = "0.2.7" 280 + source = "registry+https://github.com/rust-lang/crates.io-index" 281 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 282 + dependencies = [ 283 + "utf8parse", 284 + ] 285 + 286 + [[package]] 287 + name = "anstyle-query" 288 + version = "1.1.4" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 291 + dependencies = [ 292 + "windows-sys 0.60.2", 293 + ] 294 + 295 + [[package]] 296 + name = "anstyle-wincon" 297 + version = "3.0.10" 298 + source = "registry+https://github.com/rust-lang/crates.io-index" 299 + checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 300 + dependencies = [ 301 + "anstyle", 302 + "once_cell_polyfill", 303 + "windows-sys 0.60.2", 304 + ] 305 + 306 + [[package]] 307 + name = "anyhow" 308 + version = "1.0.100" 309 + source = "registry+https://github.com/rust-lang/crates.io-index" 310 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 311 + 312 + [[package]] 313 + name = "atomic-waker" 314 + version = "1.1.2" 315 + source = "registry+https://github.com/rust-lang/crates.io-index" 316 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 317 + 318 + [[package]] 319 + name = "base64" 320 + version = "0.22.1" 321 + source = "registry+https://github.com/rust-lang/crates.io-index" 322 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 323 + 324 + [[package]] 325 + name = "bitflags" 326 + version = "2.10.0" 327 + source = "registry+https://github.com/rust-lang/crates.io-index" 328 + checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 329 + 330 + [[package]] 331 + name = "block-buffer" 332 + version = "0.10.4" 333 + source = "registry+https://github.com/rust-lang/crates.io-index" 334 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 335 + dependencies = [ 336 + "generic-array", 337 + ] 338 + 339 + [[package]] 340 + name = "brotli" 341 + version = "8.0.2" 342 + source = "registry+https://github.com/rust-lang/crates.io-index" 343 + checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" 344 + dependencies = [ 345 + "alloc-no-stdlib", 346 + "alloc-stdlib", 347 + "brotli-decompressor", 348 + ] 349 + 350 + [[package]] 351 + name = "brotli-decompressor" 352 + version = "5.0.0" 353 + source = "registry+https://github.com/rust-lang/crates.io-index" 354 + checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" 355 + dependencies = [ 356 + "alloc-no-stdlib", 357 + "alloc-stdlib", 358 + ] 359 + 360 + [[package]] 361 + name = "bumpalo" 362 + version = "3.19.0" 363 + source = "registry+https://github.com/rust-lang/crates.io-index" 364 + checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 365 + 366 + [[package]] 367 + name = "bytes" 368 + version = "1.10.1" 369 + source = "registry+https://github.com/rust-lang/crates.io-index" 370 + checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 371 + 372 + [[package]] 373 + name = "bytestring" 374 + version = "1.5.0" 375 + source = "registry+https://github.com/rust-lang/crates.io-index" 376 + checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" 377 + dependencies = [ 378 + "bytes", 379 + ] 380 + 381 + [[package]] 382 + name = "cc" 383 + version = "1.2.41" 384 + source = "registry+https://github.com/rust-lang/crates.io-index" 385 + checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" 386 + dependencies = [ 387 + "find-msvc-tools", 388 + "jobserver", 389 + "libc", 390 + "shlex", 391 + ] 392 + 393 + [[package]] 394 + name = "cfg-if" 395 + version = "1.0.4" 396 + source = "registry+https://github.com/rust-lang/crates.io-index" 397 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 398 + 399 + [[package]] 400 + name = "colorchoice" 401 + version = "1.0.4" 402 + source = "registry+https://github.com/rust-lang/crates.io-index" 403 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 404 + 405 + [[package]] 406 + name = "cookie" 407 + version = "0.16.2" 408 + source = "registry+https://github.com/rust-lang/crates.io-index" 409 + checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" 410 + dependencies = [ 411 + "percent-encoding", 412 + "time", 413 + "version_check", 414 + ] 415 + 416 + [[package]] 417 + name = "core-foundation" 418 + version = "0.9.4" 419 + source = "registry+https://github.com/rust-lang/crates.io-index" 420 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 421 + dependencies = [ 422 + "core-foundation-sys", 423 + "libc", 424 + ] 425 + 426 + [[package]] 427 + name = "core-foundation-sys" 428 + version = "0.8.7" 429 + source = "registry+https://github.com/rust-lang/crates.io-index" 430 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 431 + 432 + [[package]] 433 + name = "cpufeatures" 434 + version = "0.2.17" 435 + source = "registry+https://github.com/rust-lang/crates.io-index" 436 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 437 + dependencies = [ 438 + "libc", 439 + ] 440 + 441 + [[package]] 442 + name = "crc32fast" 443 + version = "1.5.0" 444 + source = "registry+https://github.com/rust-lang/crates.io-index" 445 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 446 + dependencies = [ 447 + "cfg-if", 448 + ] 449 + 450 + [[package]] 451 + name = "crypto-common" 452 + version = "0.1.6" 453 + source = "registry+https://github.com/rust-lang/crates.io-index" 454 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 455 + dependencies = [ 456 + "generic-array", 457 + "typenum", 458 + ] 459 + 460 + [[package]] 461 + name = "deranged" 462 + version = "0.5.4" 463 + source = "registry+https://github.com/rust-lang/crates.io-index" 464 + checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 465 + dependencies = [ 466 + "powerfmt", 467 + ] 468 + 469 + [[package]] 470 + name = "derive_more" 471 + version = "2.0.1" 472 + source = "registry+https://github.com/rust-lang/crates.io-index" 473 + checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 474 + dependencies = [ 475 + "derive_more-impl", 476 + ] 477 + 478 + [[package]] 479 + name = "derive_more-impl" 480 + version = "2.0.1" 481 + source = "registry+https://github.com/rust-lang/crates.io-index" 482 + checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 483 + dependencies = [ 484 + "proc-macro2", 485 + "quote", 486 + "syn", 487 + "unicode-xid", 488 + ] 489 + 490 + [[package]] 491 + name = "digest" 492 + version = "0.10.7" 493 + source = "registry+https://github.com/rust-lang/crates.io-index" 494 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 495 + dependencies = [ 496 + "block-buffer", 497 + "crypto-common", 498 + ] 499 + 500 + [[package]] 501 + name = "displaydoc" 502 + version = "0.2.5" 503 + source = "registry+https://github.com/rust-lang/crates.io-index" 504 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 505 + dependencies = [ 506 + "proc-macro2", 507 + "quote", 508 + "syn", 509 + ] 510 + 511 + [[package]] 512 + name = "dotenv" 513 + version = "0.15.0" 514 + source = "registry+https://github.com/rust-lang/crates.io-index" 515 + checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 516 + 517 + [[package]] 518 + name = "encoding_rs" 519 + version = "0.8.35" 520 + source = "registry+https://github.com/rust-lang/crates.io-index" 521 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 522 + dependencies = [ 523 + "cfg-if", 524 + ] 525 + 526 + [[package]] 527 + name = "env_filter" 528 + version = "0.1.4" 529 + source = "registry+https://github.com/rust-lang/crates.io-index" 530 + checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" 531 + dependencies = [ 532 + "log", 533 + "regex", 534 + ] 535 + 536 + [[package]] 537 + name = "env_logger" 538 + version = "0.11.8" 539 + source = "registry+https://github.com/rust-lang/crates.io-index" 540 + checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 541 + dependencies = [ 542 + "anstream", 543 + "anstyle", 544 + "env_filter", 545 + "jiff", 546 + "log", 547 + ] 548 + 549 + [[package]] 550 + name = "equivalent" 551 + version = "1.0.2" 552 + source = "registry+https://github.com/rust-lang/crates.io-index" 553 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 554 + 555 + [[package]] 556 + name = "errno" 557 + version = "0.3.14" 558 + source = "registry+https://github.com/rust-lang/crates.io-index" 559 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 560 + dependencies = [ 561 + "libc", 562 + "windows-sys 0.61.2", 563 + ] 564 + 565 + [[package]] 566 + name = "fastrand" 567 + version = "2.3.0" 568 + source = "registry+https://github.com/rust-lang/crates.io-index" 569 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 570 + 571 + [[package]] 572 + name = "find-bufo" 573 + version = "0.1.0" 574 + dependencies = [ 575 + "actix-cors", 576 + "actix-files", 577 + "actix-web", 578 + "anyhow", 579 + "base64", 580 + "dotenv", 581 + "env_logger", 582 + "log", 583 + "reqwest", 584 + "serde", 585 + "serde_json", 586 + "thiserror", 587 + "tokio", 588 + ] 589 + 590 + [[package]] 591 + name = "find-msvc-tools" 592 + version = "0.1.4" 593 + source = "registry+https://github.com/rust-lang/crates.io-index" 594 + checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 595 + 596 + [[package]] 597 + name = "flate2" 598 + version = "1.1.4" 599 + source = "registry+https://github.com/rust-lang/crates.io-index" 600 + checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" 601 + dependencies = [ 602 + "crc32fast", 603 + "miniz_oxide", 604 + ] 605 + 606 + [[package]] 607 + name = "fnv" 608 + version = "1.0.7" 609 + source = "registry+https://github.com/rust-lang/crates.io-index" 610 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 611 + 612 + [[package]] 613 + name = "foldhash" 614 + version = "0.1.5" 615 + source = "registry+https://github.com/rust-lang/crates.io-index" 616 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 617 + 618 + [[package]] 619 + name = "foreign-types" 620 + version = "0.3.2" 621 + source = "registry+https://github.com/rust-lang/crates.io-index" 622 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 623 + dependencies = [ 624 + "foreign-types-shared", 625 + ] 626 + 627 + [[package]] 628 + name = "foreign-types-shared" 629 + version = "0.1.1" 630 + source = "registry+https://github.com/rust-lang/crates.io-index" 631 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 632 + 633 + [[package]] 634 + name = "form_urlencoded" 635 + version = "1.2.2" 636 + source = "registry+https://github.com/rust-lang/crates.io-index" 637 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 638 + dependencies = [ 639 + "percent-encoding", 640 + ] 641 + 642 + [[package]] 643 + name = "futures-channel" 644 + version = "0.3.31" 645 + source = "registry+https://github.com/rust-lang/crates.io-index" 646 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 647 + dependencies = [ 648 + "futures-core", 649 + ] 650 + 651 + [[package]] 652 + name = "futures-core" 653 + version = "0.3.31" 654 + source = "registry+https://github.com/rust-lang/crates.io-index" 655 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 656 + 657 + [[package]] 658 + name = "futures-sink" 659 + version = "0.3.31" 660 + source = "registry+https://github.com/rust-lang/crates.io-index" 661 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 662 + 663 + [[package]] 664 + name = "futures-task" 665 + version = "0.3.31" 666 + source = "registry+https://github.com/rust-lang/crates.io-index" 667 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 668 + 669 + [[package]] 670 + name = "futures-util" 671 + version = "0.3.31" 672 + source = "registry+https://github.com/rust-lang/crates.io-index" 673 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 674 + dependencies = [ 675 + "futures-core", 676 + "futures-task", 677 + "pin-project-lite", 678 + "pin-utils", 679 + "slab", 680 + ] 681 + 682 + [[package]] 683 + name = "generic-array" 684 + version = "0.14.9" 685 + source = "registry+https://github.com/rust-lang/crates.io-index" 686 + checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 687 + dependencies = [ 688 + "typenum", 689 + "version_check", 690 + ] 691 + 692 + [[package]] 693 + name = "getrandom" 694 + version = "0.2.16" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 697 + dependencies = [ 698 + "cfg-if", 699 + "libc", 700 + "wasi", 701 + ] 702 + 703 + [[package]] 704 + name = "getrandom" 705 + version = "0.3.4" 706 + source = "registry+https://github.com/rust-lang/crates.io-index" 707 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 708 + dependencies = [ 709 + "cfg-if", 710 + "libc", 711 + "r-efi", 712 + "wasip2", 713 + ] 714 + 715 + [[package]] 716 + name = "h2" 717 + version = "0.3.27" 718 + source = "registry+https://github.com/rust-lang/crates.io-index" 719 + checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" 720 + dependencies = [ 721 + "bytes", 722 + "fnv", 723 + "futures-core", 724 + "futures-sink", 725 + "futures-util", 726 + "http 0.2.12", 727 + "indexmap", 728 + "slab", 729 + "tokio", 730 + "tokio-util", 731 + "tracing", 732 + ] 733 + 734 + [[package]] 735 + name = "h2" 736 + version = "0.4.12" 737 + source = "registry+https://github.com/rust-lang/crates.io-index" 738 + checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 739 + dependencies = [ 740 + "atomic-waker", 741 + "bytes", 742 + "fnv", 743 + "futures-core", 744 + "futures-sink", 745 + "http 1.3.1", 746 + "indexmap", 747 + "slab", 748 + "tokio", 749 + "tokio-util", 750 + "tracing", 751 + ] 752 + 753 + [[package]] 754 + name = "hashbrown" 755 + version = "0.16.0" 756 + source = "registry+https://github.com/rust-lang/crates.io-index" 757 + checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 758 + 759 + [[package]] 760 + name = "http" 761 + version = "0.2.12" 762 + source = "registry+https://github.com/rust-lang/crates.io-index" 763 + checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 764 + dependencies = [ 765 + "bytes", 766 + "fnv", 767 + "itoa", 768 + ] 769 + 770 + [[package]] 771 + name = "http" 772 + version = "1.3.1" 773 + source = "registry+https://github.com/rust-lang/crates.io-index" 774 + checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 775 + dependencies = [ 776 + "bytes", 777 + "fnv", 778 + "itoa", 779 + ] 780 + 781 + [[package]] 782 + name = "http-body" 783 + version = "1.0.1" 784 + source = "registry+https://github.com/rust-lang/crates.io-index" 785 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 786 + dependencies = [ 787 + "bytes", 788 + "http 1.3.1", 789 + ] 790 + 791 + [[package]] 792 + name = "http-body-util" 793 + version = "0.1.3" 794 + source = "registry+https://github.com/rust-lang/crates.io-index" 795 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 796 + dependencies = [ 797 + "bytes", 798 + "futures-core", 799 + "http 1.3.1", 800 + "http-body", 801 + "pin-project-lite", 802 + ] 803 + 804 + [[package]] 805 + name = "http-range" 806 + version = "0.1.5" 807 + source = "registry+https://github.com/rust-lang/crates.io-index" 808 + checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" 809 + 810 + [[package]] 811 + name = "httparse" 812 + version = "1.10.1" 813 + source = "registry+https://github.com/rust-lang/crates.io-index" 814 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 815 + 816 + [[package]] 817 + name = "httpdate" 818 + version = "1.0.3" 819 + source = "registry+https://github.com/rust-lang/crates.io-index" 820 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 821 + 822 + [[package]] 823 + name = "hyper" 824 + version = "1.7.0" 825 + source = "registry+https://github.com/rust-lang/crates.io-index" 826 + checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" 827 + dependencies = [ 828 + "atomic-waker", 829 + "bytes", 830 + "futures-channel", 831 + "futures-core", 832 + "h2 0.4.12", 833 + "http 1.3.1", 834 + "http-body", 835 + "httparse", 836 + "itoa", 837 + "pin-project-lite", 838 + "pin-utils", 839 + "smallvec", 840 + "tokio", 841 + "want", 842 + ] 843 + 844 + [[package]] 845 + name = "hyper-rustls" 846 + version = "0.27.7" 847 + source = "registry+https://github.com/rust-lang/crates.io-index" 848 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 849 + dependencies = [ 850 + "http 1.3.1", 851 + "hyper", 852 + "hyper-util", 853 + "rustls", 854 + "rustls-pki-types", 855 + "tokio", 856 + "tokio-rustls", 857 + "tower-service", 858 + ] 859 + 860 + [[package]] 861 + name = "hyper-tls" 862 + version = "0.6.0" 863 + source = "registry+https://github.com/rust-lang/crates.io-index" 864 + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 865 + dependencies = [ 866 + "bytes", 867 + "http-body-util", 868 + "hyper", 869 + "hyper-util", 870 + "native-tls", 871 + "tokio", 872 + "tokio-native-tls", 873 + "tower-service", 874 + ] 875 + 876 + [[package]] 877 + name = "hyper-util" 878 + version = "0.1.17" 879 + source = "registry+https://github.com/rust-lang/crates.io-index" 880 + checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" 881 + dependencies = [ 882 + "base64", 883 + "bytes", 884 + "futures-channel", 885 + "futures-core", 886 + "futures-util", 887 + "http 1.3.1", 888 + "http-body", 889 + "hyper", 890 + "ipnet", 891 + "libc", 892 + "percent-encoding", 893 + "pin-project-lite", 894 + "socket2 0.6.1", 895 + "system-configuration", 896 + "tokio", 897 + "tower-service", 898 + "tracing", 899 + "windows-registry", 900 + ] 901 + 902 + [[package]] 903 + name = "icu_collections" 904 + version = "2.0.0" 905 + source = "registry+https://github.com/rust-lang/crates.io-index" 906 + checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 907 + dependencies = [ 908 + "displaydoc", 909 + "potential_utf", 910 + "yoke", 911 + "zerofrom", 912 + "zerovec", 913 + ] 914 + 915 + [[package]] 916 + name = "icu_locale_core" 917 + version = "2.0.0" 918 + source = "registry+https://github.com/rust-lang/crates.io-index" 919 + checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 920 + dependencies = [ 921 + "displaydoc", 922 + "litemap", 923 + "tinystr", 924 + "writeable", 925 + "zerovec", 926 + ] 927 + 928 + [[package]] 929 + name = "icu_normalizer" 930 + version = "2.0.0" 931 + source = "registry+https://github.com/rust-lang/crates.io-index" 932 + checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 933 + dependencies = [ 934 + "displaydoc", 935 + "icu_collections", 936 + "icu_normalizer_data", 937 + "icu_properties", 938 + "icu_provider", 939 + "smallvec", 940 + "zerovec", 941 + ] 942 + 943 + [[package]] 944 + name = "icu_normalizer_data" 945 + version = "2.0.0" 946 + source = "registry+https://github.com/rust-lang/crates.io-index" 947 + checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 948 + 949 + [[package]] 950 + name = "icu_properties" 951 + version = "2.0.1" 952 + source = "registry+https://github.com/rust-lang/crates.io-index" 953 + checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 954 + dependencies = [ 955 + "displaydoc", 956 + "icu_collections", 957 + "icu_locale_core", 958 + "icu_properties_data", 959 + "icu_provider", 960 + "potential_utf", 961 + "zerotrie", 962 + "zerovec", 963 + ] 964 + 965 + [[package]] 966 + name = "icu_properties_data" 967 + version = "2.0.1" 968 + source = "registry+https://github.com/rust-lang/crates.io-index" 969 + checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 970 + 971 + [[package]] 972 + name = "icu_provider" 973 + version = "2.0.0" 974 + source = "registry+https://github.com/rust-lang/crates.io-index" 975 + checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 976 + dependencies = [ 977 + "displaydoc", 978 + "icu_locale_core", 979 + "stable_deref_trait", 980 + "tinystr", 981 + "writeable", 982 + "yoke", 983 + "zerofrom", 984 + "zerotrie", 985 + "zerovec", 986 + ] 987 + 988 + [[package]] 989 + name = "idna" 990 + version = "1.1.0" 991 + source = "registry+https://github.com/rust-lang/crates.io-index" 992 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 993 + dependencies = [ 994 + "idna_adapter", 995 + "smallvec", 996 + "utf8_iter", 997 + ] 998 + 999 + [[package]] 1000 + name = "idna_adapter" 1001 + version = "1.2.1" 1002 + source = "registry+https://github.com/rust-lang/crates.io-index" 1003 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1004 + dependencies = [ 1005 + "icu_normalizer", 1006 + "icu_properties", 1007 + ] 1008 + 1009 + [[package]] 1010 + name = "impl-more" 1011 + version = "0.1.9" 1012 + source = "registry+https://github.com/rust-lang/crates.io-index" 1013 + checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" 1014 + 1015 + [[package]] 1016 + name = "indexmap" 1017 + version = "2.12.0" 1018 + source = "registry+https://github.com/rust-lang/crates.io-index" 1019 + checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 1020 + dependencies = [ 1021 + "equivalent", 1022 + "hashbrown", 1023 + ] 1024 + 1025 + [[package]] 1026 + name = "ipnet" 1027 + version = "2.11.0" 1028 + source = "registry+https://github.com/rust-lang/crates.io-index" 1029 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1030 + 1031 + [[package]] 1032 + name = "iri-string" 1033 + version = "0.7.8" 1034 + source = "registry+https://github.com/rust-lang/crates.io-index" 1035 + checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" 1036 + dependencies = [ 1037 + "memchr", 1038 + "serde", 1039 + ] 1040 + 1041 + [[package]] 1042 + name = "is_terminal_polyfill" 1043 + version = "1.70.2" 1044 + source = "registry+https://github.com/rust-lang/crates.io-index" 1045 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 1046 + 1047 + [[package]] 1048 + name = "itoa" 1049 + version = "1.0.15" 1050 + source = "registry+https://github.com/rust-lang/crates.io-index" 1051 + checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1052 + 1053 + [[package]] 1054 + name = "jiff" 1055 + version = "0.2.15" 1056 + source = "registry+https://github.com/rust-lang/crates.io-index" 1057 + checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" 1058 + dependencies = [ 1059 + "jiff-static", 1060 + "log", 1061 + "portable-atomic", 1062 + "portable-atomic-util", 1063 + "serde", 1064 + ] 1065 + 1066 + [[package]] 1067 + name = "jiff-static" 1068 + version = "0.2.15" 1069 + source = "registry+https://github.com/rust-lang/crates.io-index" 1070 + checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" 1071 + dependencies = [ 1072 + "proc-macro2", 1073 + "quote", 1074 + "syn", 1075 + ] 1076 + 1077 + [[package]] 1078 + name = "jobserver" 1079 + version = "0.1.34" 1080 + source = "registry+https://github.com/rust-lang/crates.io-index" 1081 + checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 1082 + dependencies = [ 1083 + "getrandom 0.3.4", 1084 + "libc", 1085 + ] 1086 + 1087 + [[package]] 1088 + name = "js-sys" 1089 + version = "0.3.81" 1090 + source = "registry+https://github.com/rust-lang/crates.io-index" 1091 + checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" 1092 + dependencies = [ 1093 + "once_cell", 1094 + "wasm-bindgen", 1095 + ] 1096 + 1097 + [[package]] 1098 + name = "language-tags" 1099 + version = "0.3.2" 1100 + source = "registry+https://github.com/rust-lang/crates.io-index" 1101 + checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 1102 + 1103 + [[package]] 1104 + name = "libc" 1105 + version = "0.2.177" 1106 + source = "registry+https://github.com/rust-lang/crates.io-index" 1107 + checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 1108 + 1109 + [[package]] 1110 + name = "linux-raw-sys" 1111 + version = "0.11.0" 1112 + source = "registry+https://github.com/rust-lang/crates.io-index" 1113 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1114 + 1115 + [[package]] 1116 + name = "litemap" 1117 + version = "0.8.0" 1118 + source = "registry+https://github.com/rust-lang/crates.io-index" 1119 + checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 1120 + 1121 + [[package]] 1122 + name = "local-channel" 1123 + version = "0.1.5" 1124 + source = "registry+https://github.com/rust-lang/crates.io-index" 1125 + checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" 1126 + dependencies = [ 1127 + "futures-core", 1128 + "futures-sink", 1129 + "local-waker", 1130 + ] 1131 + 1132 + [[package]] 1133 + name = "local-waker" 1134 + version = "0.1.4" 1135 + source = "registry+https://github.com/rust-lang/crates.io-index" 1136 + checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" 1137 + 1138 + [[package]] 1139 + name = "lock_api" 1140 + version = "0.4.14" 1141 + source = "registry+https://github.com/rust-lang/crates.io-index" 1142 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1143 + dependencies = [ 1144 + "scopeguard", 1145 + ] 1146 + 1147 + [[package]] 1148 + name = "log" 1149 + version = "0.4.28" 1150 + source = "registry+https://github.com/rust-lang/crates.io-index" 1151 + checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 1152 + 1153 + [[package]] 1154 + name = "memchr" 1155 + version = "2.7.6" 1156 + source = "registry+https://github.com/rust-lang/crates.io-index" 1157 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1158 + 1159 + [[package]] 1160 + name = "mime" 1161 + version = "0.3.17" 1162 + source = "registry+https://github.com/rust-lang/crates.io-index" 1163 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1164 + 1165 + [[package]] 1166 + name = "mime_guess" 1167 + version = "2.0.5" 1168 + source = "registry+https://github.com/rust-lang/crates.io-index" 1169 + checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1170 + dependencies = [ 1171 + "mime", 1172 + "unicase", 1173 + ] 1174 + 1175 + [[package]] 1176 + name = "miniz_oxide" 1177 + version = "0.8.9" 1178 + source = "registry+https://github.com/rust-lang/crates.io-index" 1179 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1180 + dependencies = [ 1181 + "adler2", 1182 + "simd-adler32", 1183 + ] 1184 + 1185 + [[package]] 1186 + name = "mio" 1187 + version = "1.1.0" 1188 + source = "registry+https://github.com/rust-lang/crates.io-index" 1189 + checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 1190 + dependencies = [ 1191 + "libc", 1192 + "log", 1193 + "wasi", 1194 + "windows-sys 0.61.2", 1195 + ] 1196 + 1197 + [[package]] 1198 + name = "native-tls" 1199 + version = "0.2.14" 1200 + source = "registry+https://github.com/rust-lang/crates.io-index" 1201 + checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1202 + dependencies = [ 1203 + "libc", 1204 + "log", 1205 + "openssl", 1206 + "openssl-probe", 1207 + "openssl-sys", 1208 + "schannel", 1209 + "security-framework", 1210 + "security-framework-sys", 1211 + "tempfile", 1212 + ] 1213 + 1214 + [[package]] 1215 + name = "num-conv" 1216 + version = "0.1.0" 1217 + source = "registry+https://github.com/rust-lang/crates.io-index" 1218 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1219 + 1220 + [[package]] 1221 + name = "once_cell" 1222 + version = "1.21.3" 1223 + source = "registry+https://github.com/rust-lang/crates.io-index" 1224 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1225 + 1226 + [[package]] 1227 + name = "once_cell_polyfill" 1228 + version = "1.70.2" 1229 + source = "registry+https://github.com/rust-lang/crates.io-index" 1230 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 1231 + 1232 + [[package]] 1233 + name = "openssl" 1234 + version = "0.10.74" 1235 + source = "registry+https://github.com/rust-lang/crates.io-index" 1236 + checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" 1237 + dependencies = [ 1238 + "bitflags", 1239 + "cfg-if", 1240 + "foreign-types", 1241 + "libc", 1242 + "once_cell", 1243 + "openssl-macros", 1244 + "openssl-sys", 1245 + ] 1246 + 1247 + [[package]] 1248 + name = "openssl-macros" 1249 + version = "0.1.1" 1250 + source = "registry+https://github.com/rust-lang/crates.io-index" 1251 + checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1252 + dependencies = [ 1253 + "proc-macro2", 1254 + "quote", 1255 + "syn", 1256 + ] 1257 + 1258 + [[package]] 1259 + name = "openssl-probe" 1260 + version = "0.1.6" 1261 + source = "registry+https://github.com/rust-lang/crates.io-index" 1262 + checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1263 + 1264 + [[package]] 1265 + name = "openssl-sys" 1266 + version = "0.9.110" 1267 + source = "registry+https://github.com/rust-lang/crates.io-index" 1268 + checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" 1269 + dependencies = [ 1270 + "cc", 1271 + "libc", 1272 + "pkg-config", 1273 + "vcpkg", 1274 + ] 1275 + 1276 + [[package]] 1277 + name = "parking_lot" 1278 + version = "0.12.5" 1279 + source = "registry+https://github.com/rust-lang/crates.io-index" 1280 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 1281 + dependencies = [ 1282 + "lock_api", 1283 + "parking_lot_core", 1284 + ] 1285 + 1286 + [[package]] 1287 + name = "parking_lot_core" 1288 + version = "0.9.12" 1289 + source = "registry+https://github.com/rust-lang/crates.io-index" 1290 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 1291 + dependencies = [ 1292 + "cfg-if", 1293 + "libc", 1294 + "redox_syscall", 1295 + "smallvec", 1296 + "windows-link 0.2.1", 1297 + ] 1298 + 1299 + [[package]] 1300 + name = "percent-encoding" 1301 + version = "2.3.2" 1302 + source = "registry+https://github.com/rust-lang/crates.io-index" 1303 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1304 + 1305 + [[package]] 1306 + name = "pin-project-lite" 1307 + version = "0.2.16" 1308 + source = "registry+https://github.com/rust-lang/crates.io-index" 1309 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1310 + 1311 + [[package]] 1312 + name = "pin-utils" 1313 + version = "0.1.0" 1314 + source = "registry+https://github.com/rust-lang/crates.io-index" 1315 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1316 + 1317 + [[package]] 1318 + name = "pkg-config" 1319 + version = "0.3.32" 1320 + source = "registry+https://github.com/rust-lang/crates.io-index" 1321 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1322 + 1323 + [[package]] 1324 + name = "portable-atomic" 1325 + version = "1.11.1" 1326 + source = "registry+https://github.com/rust-lang/crates.io-index" 1327 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 1328 + 1329 + [[package]] 1330 + name = "portable-atomic-util" 1331 + version = "0.2.4" 1332 + source = "registry+https://github.com/rust-lang/crates.io-index" 1333 + checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 1334 + dependencies = [ 1335 + "portable-atomic", 1336 + ] 1337 + 1338 + [[package]] 1339 + name = "potential_utf" 1340 + version = "0.1.3" 1341 + source = "registry+https://github.com/rust-lang/crates.io-index" 1342 + checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" 1343 + dependencies = [ 1344 + "zerovec", 1345 + ] 1346 + 1347 + [[package]] 1348 + name = "powerfmt" 1349 + version = "0.2.0" 1350 + source = "registry+https://github.com/rust-lang/crates.io-index" 1351 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1352 + 1353 + [[package]] 1354 + name = "ppv-lite86" 1355 + version = "0.2.21" 1356 + source = "registry+https://github.com/rust-lang/crates.io-index" 1357 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1358 + dependencies = [ 1359 + "zerocopy", 1360 + ] 1361 + 1362 + [[package]] 1363 + name = "proc-macro2" 1364 + version = "1.0.101" 1365 + source = "registry+https://github.com/rust-lang/crates.io-index" 1366 + checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 1367 + dependencies = [ 1368 + "unicode-ident", 1369 + ] 1370 + 1371 + [[package]] 1372 + name = "quote" 1373 + version = "1.0.41" 1374 + source = "registry+https://github.com/rust-lang/crates.io-index" 1375 + checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 1376 + dependencies = [ 1377 + "proc-macro2", 1378 + ] 1379 + 1380 + [[package]] 1381 + name = "r-efi" 1382 + version = "5.3.0" 1383 + source = "registry+https://github.com/rust-lang/crates.io-index" 1384 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1385 + 1386 + [[package]] 1387 + name = "rand" 1388 + version = "0.9.2" 1389 + source = "registry+https://github.com/rust-lang/crates.io-index" 1390 + checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 1391 + dependencies = [ 1392 + "rand_chacha", 1393 + "rand_core", 1394 + ] 1395 + 1396 + [[package]] 1397 + name = "rand_chacha" 1398 + version = "0.9.0" 1399 + source = "registry+https://github.com/rust-lang/crates.io-index" 1400 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1401 + dependencies = [ 1402 + "ppv-lite86", 1403 + "rand_core", 1404 + ] 1405 + 1406 + [[package]] 1407 + name = "rand_core" 1408 + version = "0.9.3" 1409 + source = "registry+https://github.com/rust-lang/crates.io-index" 1410 + checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1411 + dependencies = [ 1412 + "getrandom 0.3.4", 1413 + ] 1414 + 1415 + [[package]] 1416 + name = "redox_syscall" 1417 + version = "0.5.18" 1418 + source = "registry+https://github.com/rust-lang/crates.io-index" 1419 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1420 + dependencies = [ 1421 + "bitflags", 1422 + ] 1423 + 1424 + [[package]] 1425 + name = "regex" 1426 + version = "1.12.2" 1427 + source = "registry+https://github.com/rust-lang/crates.io-index" 1428 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1429 + dependencies = [ 1430 + "aho-corasick", 1431 + "memchr", 1432 + "regex-automata", 1433 + "regex-syntax", 1434 + ] 1435 + 1436 + [[package]] 1437 + name = "regex-automata" 1438 + version = "0.4.13" 1439 + source = "registry+https://github.com/rust-lang/crates.io-index" 1440 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1441 + dependencies = [ 1442 + "aho-corasick", 1443 + "memchr", 1444 + "regex-syntax", 1445 + ] 1446 + 1447 + [[package]] 1448 + name = "regex-lite" 1449 + version = "0.1.8" 1450 + source = "registry+https://github.com/rust-lang/crates.io-index" 1451 + checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 1452 + 1453 + [[package]] 1454 + name = "regex-syntax" 1455 + version = "0.8.8" 1456 + source = "registry+https://github.com/rust-lang/crates.io-index" 1457 + checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1458 + 1459 + [[package]] 1460 + name = "reqwest" 1461 + version = "0.12.24" 1462 + source = "registry+https://github.com/rust-lang/crates.io-index" 1463 + checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 1464 + dependencies = [ 1465 + "base64", 1466 + "bytes", 1467 + "encoding_rs", 1468 + "futures-core", 1469 + "futures-util", 1470 + "h2 0.4.12", 1471 + "http 1.3.1", 1472 + "http-body", 1473 + "http-body-util", 1474 + "hyper", 1475 + "hyper-rustls", 1476 + "hyper-tls", 1477 + "hyper-util", 1478 + "js-sys", 1479 + "log", 1480 + "mime", 1481 + "mime_guess", 1482 + "native-tls", 1483 + "percent-encoding", 1484 + "pin-project-lite", 1485 + "rustls-pki-types", 1486 + "serde", 1487 + "serde_json", 1488 + "serde_urlencoded", 1489 + "sync_wrapper", 1490 + "tokio", 1491 + "tokio-native-tls", 1492 + "tower", 1493 + "tower-http", 1494 + "tower-service", 1495 + "url", 1496 + "wasm-bindgen", 1497 + "wasm-bindgen-futures", 1498 + "web-sys", 1499 + ] 1500 + 1501 + [[package]] 1502 + name = "ring" 1503 + version = "0.17.14" 1504 + source = "registry+https://github.com/rust-lang/crates.io-index" 1505 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1506 + dependencies = [ 1507 + "cc", 1508 + "cfg-if", 1509 + "getrandom 0.2.16", 1510 + "libc", 1511 + "untrusted", 1512 + "windows-sys 0.52.0", 1513 + ] 1514 + 1515 + [[package]] 1516 + name = "rustix" 1517 + version = "1.1.2" 1518 + source = "registry+https://github.com/rust-lang/crates.io-index" 1519 + checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 1520 + dependencies = [ 1521 + "bitflags", 1522 + "errno", 1523 + "libc", 1524 + "linux-raw-sys", 1525 + "windows-sys 0.61.2", 1526 + ] 1527 + 1528 + [[package]] 1529 + name = "rustls" 1530 + version = "0.23.34" 1531 + source = "registry+https://github.com/rust-lang/crates.io-index" 1532 + checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" 1533 + dependencies = [ 1534 + "once_cell", 1535 + "rustls-pki-types", 1536 + "rustls-webpki", 1537 + "subtle", 1538 + "zeroize", 1539 + ] 1540 + 1541 + [[package]] 1542 + name = "rustls-pki-types" 1543 + version = "1.12.0" 1544 + source = "registry+https://github.com/rust-lang/crates.io-index" 1545 + checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 1546 + dependencies = [ 1547 + "zeroize", 1548 + ] 1549 + 1550 + [[package]] 1551 + name = "rustls-webpki" 1552 + version = "0.103.7" 1553 + source = "registry+https://github.com/rust-lang/crates.io-index" 1554 + checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" 1555 + dependencies = [ 1556 + "ring", 1557 + "rustls-pki-types", 1558 + "untrusted", 1559 + ] 1560 + 1561 + [[package]] 1562 + name = "rustversion" 1563 + version = "1.0.22" 1564 + source = "registry+https://github.com/rust-lang/crates.io-index" 1565 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1566 + 1567 + [[package]] 1568 + name = "ryu" 1569 + version = "1.0.20" 1570 + source = "registry+https://github.com/rust-lang/crates.io-index" 1571 + checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1572 + 1573 + [[package]] 1574 + name = "schannel" 1575 + version = "0.1.28" 1576 + source = "registry+https://github.com/rust-lang/crates.io-index" 1577 + checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 1578 + dependencies = [ 1579 + "windows-sys 0.61.2", 1580 + ] 1581 + 1582 + [[package]] 1583 + name = "scopeguard" 1584 + version = "1.2.0" 1585 + source = "registry+https://github.com/rust-lang/crates.io-index" 1586 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1587 + 1588 + [[package]] 1589 + name = "security-framework" 1590 + version = "2.11.1" 1591 + source = "registry+https://github.com/rust-lang/crates.io-index" 1592 + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1593 + dependencies = [ 1594 + "bitflags", 1595 + "core-foundation", 1596 + "core-foundation-sys", 1597 + "libc", 1598 + "security-framework-sys", 1599 + ] 1600 + 1601 + [[package]] 1602 + name = "security-framework-sys" 1603 + version = "2.15.0" 1604 + source = "registry+https://github.com/rust-lang/crates.io-index" 1605 + checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 1606 + dependencies = [ 1607 + "core-foundation-sys", 1608 + "libc", 1609 + ] 1610 + 1611 + [[package]] 1612 + name = "serde" 1613 + version = "1.0.228" 1614 + source = "registry+https://github.com/rust-lang/crates.io-index" 1615 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1616 + dependencies = [ 1617 + "serde_core", 1618 + "serde_derive", 1619 + ] 1620 + 1621 + [[package]] 1622 + name = "serde_core" 1623 + version = "1.0.228" 1624 + source = "registry+https://github.com/rust-lang/crates.io-index" 1625 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1626 + dependencies = [ 1627 + "serde_derive", 1628 + ] 1629 + 1630 + [[package]] 1631 + name = "serde_derive" 1632 + version = "1.0.228" 1633 + source = "registry+https://github.com/rust-lang/crates.io-index" 1634 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1635 + dependencies = [ 1636 + "proc-macro2", 1637 + "quote", 1638 + "syn", 1639 + ] 1640 + 1641 + [[package]] 1642 + name = "serde_json" 1643 + version = "1.0.145" 1644 + source = "registry+https://github.com/rust-lang/crates.io-index" 1645 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 1646 + dependencies = [ 1647 + "itoa", 1648 + "memchr", 1649 + "ryu", 1650 + "serde", 1651 + "serde_core", 1652 + ] 1653 + 1654 + [[package]] 1655 + name = "serde_urlencoded" 1656 + version = "0.7.1" 1657 + source = "registry+https://github.com/rust-lang/crates.io-index" 1658 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1659 + dependencies = [ 1660 + "form_urlencoded", 1661 + "itoa", 1662 + "ryu", 1663 + "serde", 1664 + ] 1665 + 1666 + [[package]] 1667 + name = "sha1" 1668 + version = "0.10.6" 1669 + source = "registry+https://github.com/rust-lang/crates.io-index" 1670 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1671 + dependencies = [ 1672 + "cfg-if", 1673 + "cpufeatures", 1674 + "digest", 1675 + ] 1676 + 1677 + [[package]] 1678 + name = "shlex" 1679 + version = "1.3.0" 1680 + source = "registry+https://github.com/rust-lang/crates.io-index" 1681 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1682 + 1683 + [[package]] 1684 + name = "signal-hook-registry" 1685 + version = "1.4.6" 1686 + source = "registry+https://github.com/rust-lang/crates.io-index" 1687 + checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 1688 + dependencies = [ 1689 + "libc", 1690 + ] 1691 + 1692 + [[package]] 1693 + name = "simd-adler32" 1694 + version = "0.3.7" 1695 + source = "registry+https://github.com/rust-lang/crates.io-index" 1696 + checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1697 + 1698 + [[package]] 1699 + name = "slab" 1700 + version = "0.4.11" 1701 + source = "registry+https://github.com/rust-lang/crates.io-index" 1702 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 1703 + 1704 + [[package]] 1705 + name = "smallvec" 1706 + version = "1.15.1" 1707 + source = "registry+https://github.com/rust-lang/crates.io-index" 1708 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1709 + 1710 + [[package]] 1711 + name = "socket2" 1712 + version = "0.5.10" 1713 + source = "registry+https://github.com/rust-lang/crates.io-index" 1714 + checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 1715 + dependencies = [ 1716 + "libc", 1717 + "windows-sys 0.52.0", 1718 + ] 1719 + 1720 + [[package]] 1721 + name = "socket2" 1722 + version = "0.6.1" 1723 + source = "registry+https://github.com/rust-lang/crates.io-index" 1724 + checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 1725 + dependencies = [ 1726 + "libc", 1727 + "windows-sys 0.60.2", 1728 + ] 1729 + 1730 + [[package]] 1731 + name = "stable_deref_trait" 1732 + version = "1.2.1" 1733 + source = "registry+https://github.com/rust-lang/crates.io-index" 1734 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1735 + 1736 + [[package]] 1737 + name = "subtle" 1738 + version = "2.6.1" 1739 + source = "registry+https://github.com/rust-lang/crates.io-index" 1740 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1741 + 1742 + [[package]] 1743 + name = "syn" 1744 + version = "2.0.107" 1745 + source = "registry+https://github.com/rust-lang/crates.io-index" 1746 + checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" 1747 + dependencies = [ 1748 + "proc-macro2", 1749 + "quote", 1750 + "unicode-ident", 1751 + ] 1752 + 1753 + [[package]] 1754 + name = "sync_wrapper" 1755 + version = "1.0.2" 1756 + source = "registry+https://github.com/rust-lang/crates.io-index" 1757 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1758 + dependencies = [ 1759 + "futures-core", 1760 + ] 1761 + 1762 + [[package]] 1763 + name = "synstructure" 1764 + version = "0.13.2" 1765 + source = "registry+https://github.com/rust-lang/crates.io-index" 1766 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1767 + dependencies = [ 1768 + "proc-macro2", 1769 + "quote", 1770 + "syn", 1771 + ] 1772 + 1773 + [[package]] 1774 + name = "system-configuration" 1775 + version = "0.6.1" 1776 + source = "registry+https://github.com/rust-lang/crates.io-index" 1777 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1778 + dependencies = [ 1779 + "bitflags", 1780 + "core-foundation", 1781 + "system-configuration-sys", 1782 + ] 1783 + 1784 + [[package]] 1785 + name = "system-configuration-sys" 1786 + version = "0.6.0" 1787 + source = "registry+https://github.com/rust-lang/crates.io-index" 1788 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1789 + dependencies = [ 1790 + "core-foundation-sys", 1791 + "libc", 1792 + ] 1793 + 1794 + [[package]] 1795 + name = "tempfile" 1796 + version = "3.23.0" 1797 + source = "registry+https://github.com/rust-lang/crates.io-index" 1798 + checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 1799 + dependencies = [ 1800 + "fastrand", 1801 + "getrandom 0.3.4", 1802 + "once_cell", 1803 + "rustix", 1804 + "windows-sys 0.61.2", 1805 + ] 1806 + 1807 + [[package]] 1808 + name = "thiserror" 1809 + version = "1.0.69" 1810 + source = "registry+https://github.com/rust-lang/crates.io-index" 1811 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1812 + dependencies = [ 1813 + "thiserror-impl", 1814 + ] 1815 + 1816 + [[package]] 1817 + name = "thiserror-impl" 1818 + version = "1.0.69" 1819 + source = "registry+https://github.com/rust-lang/crates.io-index" 1820 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1821 + dependencies = [ 1822 + "proc-macro2", 1823 + "quote", 1824 + "syn", 1825 + ] 1826 + 1827 + [[package]] 1828 + name = "time" 1829 + version = "0.3.44" 1830 + source = "registry+https://github.com/rust-lang/crates.io-index" 1831 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 1832 + dependencies = [ 1833 + "deranged", 1834 + "itoa", 1835 + "num-conv", 1836 + "powerfmt", 1837 + "serde", 1838 + "time-core", 1839 + "time-macros", 1840 + ] 1841 + 1842 + [[package]] 1843 + name = "time-core" 1844 + version = "0.1.6" 1845 + source = "registry+https://github.com/rust-lang/crates.io-index" 1846 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 1847 + 1848 + [[package]] 1849 + name = "time-macros" 1850 + version = "0.2.24" 1851 + source = "registry+https://github.com/rust-lang/crates.io-index" 1852 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 1853 + dependencies = [ 1854 + "num-conv", 1855 + "time-core", 1856 + ] 1857 + 1858 + [[package]] 1859 + name = "tinystr" 1860 + version = "0.8.1" 1861 + source = "registry+https://github.com/rust-lang/crates.io-index" 1862 + checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1863 + dependencies = [ 1864 + "displaydoc", 1865 + "zerovec", 1866 + ] 1867 + 1868 + [[package]] 1869 + name = "tokio" 1870 + version = "1.48.0" 1871 + source = "registry+https://github.com/rust-lang/crates.io-index" 1872 + checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 1873 + dependencies = [ 1874 + "bytes", 1875 + "libc", 1876 + "mio", 1877 + "parking_lot", 1878 + "pin-project-lite", 1879 + "signal-hook-registry", 1880 + "socket2 0.6.1", 1881 + "tokio-macros", 1882 + "windows-sys 0.61.2", 1883 + ] 1884 + 1885 + [[package]] 1886 + name = "tokio-macros" 1887 + version = "2.6.0" 1888 + source = "registry+https://github.com/rust-lang/crates.io-index" 1889 + checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 1890 + dependencies = [ 1891 + "proc-macro2", 1892 + "quote", 1893 + "syn", 1894 + ] 1895 + 1896 + [[package]] 1897 + name = "tokio-native-tls" 1898 + version = "0.3.1" 1899 + source = "registry+https://github.com/rust-lang/crates.io-index" 1900 + checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1901 + dependencies = [ 1902 + "native-tls", 1903 + "tokio", 1904 + ] 1905 + 1906 + [[package]] 1907 + name = "tokio-rustls" 1908 + version = "0.26.4" 1909 + source = "registry+https://github.com/rust-lang/crates.io-index" 1910 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 1911 + dependencies = [ 1912 + "rustls", 1913 + "tokio", 1914 + ] 1915 + 1916 + [[package]] 1917 + name = "tokio-util" 1918 + version = "0.7.16" 1919 + source = "registry+https://github.com/rust-lang/crates.io-index" 1920 + checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 1921 + dependencies = [ 1922 + "bytes", 1923 + "futures-core", 1924 + "futures-sink", 1925 + "pin-project-lite", 1926 + "tokio", 1927 + ] 1928 + 1929 + [[package]] 1930 + name = "tower" 1931 + version = "0.5.2" 1932 + source = "registry+https://github.com/rust-lang/crates.io-index" 1933 + checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1934 + dependencies = [ 1935 + "futures-core", 1936 + "futures-util", 1937 + "pin-project-lite", 1938 + "sync_wrapper", 1939 + "tokio", 1940 + "tower-layer", 1941 + "tower-service", 1942 + ] 1943 + 1944 + [[package]] 1945 + name = "tower-http" 1946 + version = "0.6.6" 1947 + source = "registry+https://github.com/rust-lang/crates.io-index" 1948 + checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 1949 + dependencies = [ 1950 + "bitflags", 1951 + "bytes", 1952 + "futures-util", 1953 + "http 1.3.1", 1954 + "http-body", 1955 + "iri-string", 1956 + "pin-project-lite", 1957 + "tower", 1958 + "tower-layer", 1959 + "tower-service", 1960 + ] 1961 + 1962 + [[package]] 1963 + name = "tower-layer" 1964 + version = "0.3.3" 1965 + source = "registry+https://github.com/rust-lang/crates.io-index" 1966 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1967 + 1968 + [[package]] 1969 + name = "tower-service" 1970 + version = "0.3.3" 1971 + source = "registry+https://github.com/rust-lang/crates.io-index" 1972 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1973 + 1974 + [[package]] 1975 + name = "tracing" 1976 + version = "0.1.41" 1977 + source = "registry+https://github.com/rust-lang/crates.io-index" 1978 + checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1979 + dependencies = [ 1980 + "log", 1981 + "pin-project-lite", 1982 + "tracing-attributes", 1983 + "tracing-core", 1984 + ] 1985 + 1986 + [[package]] 1987 + name = "tracing-attributes" 1988 + version = "0.1.30" 1989 + source = "registry+https://github.com/rust-lang/crates.io-index" 1990 + checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 1991 + dependencies = [ 1992 + "proc-macro2", 1993 + "quote", 1994 + "syn", 1995 + ] 1996 + 1997 + [[package]] 1998 + name = "tracing-core" 1999 + version = "0.1.34" 2000 + source = "registry+https://github.com/rust-lang/crates.io-index" 2001 + checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 2002 + dependencies = [ 2003 + "once_cell", 2004 + ] 2005 + 2006 + [[package]] 2007 + name = "try-lock" 2008 + version = "0.2.5" 2009 + source = "registry+https://github.com/rust-lang/crates.io-index" 2010 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2011 + 2012 + [[package]] 2013 + name = "typenum" 2014 + version = "1.19.0" 2015 + source = "registry+https://github.com/rust-lang/crates.io-index" 2016 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 2017 + 2018 + [[package]] 2019 + name = "unicase" 2020 + version = "2.8.1" 2021 + source = "registry+https://github.com/rust-lang/crates.io-index" 2022 + checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 2023 + 2024 + [[package]] 2025 + name = "unicode-ident" 2026 + version = "1.0.20" 2027 + source = "registry+https://github.com/rust-lang/crates.io-index" 2028 + checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" 2029 + 2030 + [[package]] 2031 + name = "unicode-xid" 2032 + version = "0.2.6" 2033 + source = "registry+https://github.com/rust-lang/crates.io-index" 2034 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 2035 + 2036 + [[package]] 2037 + name = "untrusted" 2038 + version = "0.9.0" 2039 + source = "registry+https://github.com/rust-lang/crates.io-index" 2040 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2041 + 2042 + [[package]] 2043 + name = "url" 2044 + version = "2.5.7" 2045 + source = "registry+https://github.com/rust-lang/crates.io-index" 2046 + checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 2047 + dependencies = [ 2048 + "form_urlencoded", 2049 + "idna", 2050 + "percent-encoding", 2051 + "serde", 2052 + ] 2053 + 2054 + [[package]] 2055 + name = "utf8_iter" 2056 + version = "1.0.4" 2057 + source = "registry+https://github.com/rust-lang/crates.io-index" 2058 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2059 + 2060 + [[package]] 2061 + name = "utf8parse" 2062 + version = "0.2.2" 2063 + source = "registry+https://github.com/rust-lang/crates.io-index" 2064 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2065 + 2066 + [[package]] 2067 + name = "v_htmlescape" 2068 + version = "0.15.8" 2069 + source = "registry+https://github.com/rust-lang/crates.io-index" 2070 + checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" 2071 + 2072 + [[package]] 2073 + name = "vcpkg" 2074 + version = "0.2.15" 2075 + source = "registry+https://github.com/rust-lang/crates.io-index" 2076 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2077 + 2078 + [[package]] 2079 + name = "version_check" 2080 + version = "0.9.5" 2081 + source = "registry+https://github.com/rust-lang/crates.io-index" 2082 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2083 + 2084 + [[package]] 2085 + name = "want" 2086 + version = "0.3.1" 2087 + source = "registry+https://github.com/rust-lang/crates.io-index" 2088 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2089 + dependencies = [ 2090 + "try-lock", 2091 + ] 2092 + 2093 + [[package]] 2094 + name = "wasi" 2095 + version = "0.11.1+wasi-snapshot-preview1" 2096 + source = "registry+https://github.com/rust-lang/crates.io-index" 2097 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 2098 + 2099 + [[package]] 2100 + name = "wasip2" 2101 + version = "1.0.1+wasi-0.2.4" 2102 + source = "registry+https://github.com/rust-lang/crates.io-index" 2103 + checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 2104 + dependencies = [ 2105 + "wit-bindgen", 2106 + ] 2107 + 2108 + [[package]] 2109 + name = "wasm-bindgen" 2110 + version = "0.2.104" 2111 + source = "registry+https://github.com/rust-lang/crates.io-index" 2112 + checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" 2113 + dependencies = [ 2114 + "cfg-if", 2115 + "once_cell", 2116 + "rustversion", 2117 + "wasm-bindgen-macro", 2118 + "wasm-bindgen-shared", 2119 + ] 2120 + 2121 + [[package]] 2122 + name = "wasm-bindgen-backend" 2123 + version = "0.2.104" 2124 + source = "registry+https://github.com/rust-lang/crates.io-index" 2125 + checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" 2126 + dependencies = [ 2127 + "bumpalo", 2128 + "log", 2129 + "proc-macro2", 2130 + "quote", 2131 + "syn", 2132 + "wasm-bindgen-shared", 2133 + ] 2134 + 2135 + [[package]] 2136 + name = "wasm-bindgen-futures" 2137 + version = "0.4.54" 2138 + source = "registry+https://github.com/rust-lang/crates.io-index" 2139 + checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" 2140 + dependencies = [ 2141 + "cfg-if", 2142 + "js-sys", 2143 + "once_cell", 2144 + "wasm-bindgen", 2145 + "web-sys", 2146 + ] 2147 + 2148 + [[package]] 2149 + name = "wasm-bindgen-macro" 2150 + version = "0.2.104" 2151 + source = "registry+https://github.com/rust-lang/crates.io-index" 2152 + checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" 2153 + dependencies = [ 2154 + "quote", 2155 + "wasm-bindgen-macro-support", 2156 + ] 2157 + 2158 + [[package]] 2159 + name = "wasm-bindgen-macro-support" 2160 + version = "0.2.104" 2161 + source = "registry+https://github.com/rust-lang/crates.io-index" 2162 + checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" 2163 + dependencies = [ 2164 + "proc-macro2", 2165 + "quote", 2166 + "syn", 2167 + "wasm-bindgen-backend", 2168 + "wasm-bindgen-shared", 2169 + ] 2170 + 2171 + [[package]] 2172 + name = "wasm-bindgen-shared" 2173 + version = "0.2.104" 2174 + source = "registry+https://github.com/rust-lang/crates.io-index" 2175 + checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" 2176 + dependencies = [ 2177 + "unicode-ident", 2178 + ] 2179 + 2180 + [[package]] 2181 + name = "web-sys" 2182 + version = "0.3.81" 2183 + source = "registry+https://github.com/rust-lang/crates.io-index" 2184 + checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" 2185 + dependencies = [ 2186 + "js-sys", 2187 + "wasm-bindgen", 2188 + ] 2189 + 2190 + [[package]] 2191 + name = "windows-link" 2192 + version = "0.1.3" 2193 + source = "registry+https://github.com/rust-lang/crates.io-index" 2194 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 2195 + 2196 + [[package]] 2197 + name = "windows-link" 2198 + version = "0.2.1" 2199 + source = "registry+https://github.com/rust-lang/crates.io-index" 2200 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2201 + 2202 + [[package]] 2203 + name = "windows-registry" 2204 + version = "0.5.3" 2205 + source = "registry+https://github.com/rust-lang/crates.io-index" 2206 + checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" 2207 + dependencies = [ 2208 + "windows-link 0.1.3", 2209 + "windows-result", 2210 + "windows-strings", 2211 + ] 2212 + 2213 + [[package]] 2214 + name = "windows-result" 2215 + version = "0.3.4" 2216 + source = "registry+https://github.com/rust-lang/crates.io-index" 2217 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 2218 + dependencies = [ 2219 + "windows-link 0.1.3", 2220 + ] 2221 + 2222 + [[package]] 2223 + name = "windows-strings" 2224 + version = "0.4.2" 2225 + source = "registry+https://github.com/rust-lang/crates.io-index" 2226 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 2227 + dependencies = [ 2228 + "windows-link 0.1.3", 2229 + ] 2230 + 2231 + [[package]] 2232 + name = "windows-sys" 2233 + version = "0.52.0" 2234 + source = "registry+https://github.com/rust-lang/crates.io-index" 2235 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2236 + dependencies = [ 2237 + "windows-targets 0.52.6", 2238 + ] 2239 + 2240 + [[package]] 2241 + name = "windows-sys" 2242 + version = "0.60.2" 2243 + source = "registry+https://github.com/rust-lang/crates.io-index" 2244 + checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 2245 + dependencies = [ 2246 + "windows-targets 0.53.5", 2247 + ] 2248 + 2249 + [[package]] 2250 + name = "windows-sys" 2251 + version = "0.61.2" 2252 + source = "registry+https://github.com/rust-lang/crates.io-index" 2253 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 2254 + dependencies = [ 2255 + "windows-link 0.2.1", 2256 + ] 2257 + 2258 + [[package]] 2259 + name = "windows-targets" 2260 + version = "0.52.6" 2261 + source = "registry+https://github.com/rust-lang/crates.io-index" 2262 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2263 + dependencies = [ 2264 + "windows_aarch64_gnullvm 0.52.6", 2265 + "windows_aarch64_msvc 0.52.6", 2266 + "windows_i686_gnu 0.52.6", 2267 + "windows_i686_gnullvm 0.52.6", 2268 + "windows_i686_msvc 0.52.6", 2269 + "windows_x86_64_gnu 0.52.6", 2270 + "windows_x86_64_gnullvm 0.52.6", 2271 + "windows_x86_64_msvc 0.52.6", 2272 + ] 2273 + 2274 + [[package]] 2275 + name = "windows-targets" 2276 + version = "0.53.5" 2277 + source = "registry+https://github.com/rust-lang/crates.io-index" 2278 + checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 2279 + dependencies = [ 2280 + "windows-link 0.2.1", 2281 + "windows_aarch64_gnullvm 0.53.1", 2282 + "windows_aarch64_msvc 0.53.1", 2283 + "windows_i686_gnu 0.53.1", 2284 + "windows_i686_gnullvm 0.53.1", 2285 + "windows_i686_msvc 0.53.1", 2286 + "windows_x86_64_gnu 0.53.1", 2287 + "windows_x86_64_gnullvm 0.53.1", 2288 + "windows_x86_64_msvc 0.53.1", 2289 + ] 2290 + 2291 + [[package]] 2292 + name = "windows_aarch64_gnullvm" 2293 + version = "0.52.6" 2294 + source = "registry+https://github.com/rust-lang/crates.io-index" 2295 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2296 + 2297 + [[package]] 2298 + name = "windows_aarch64_gnullvm" 2299 + version = "0.53.1" 2300 + source = "registry+https://github.com/rust-lang/crates.io-index" 2301 + checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 2302 + 2303 + [[package]] 2304 + name = "windows_aarch64_msvc" 2305 + version = "0.52.6" 2306 + source = "registry+https://github.com/rust-lang/crates.io-index" 2307 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2308 + 2309 + [[package]] 2310 + name = "windows_aarch64_msvc" 2311 + version = "0.53.1" 2312 + source = "registry+https://github.com/rust-lang/crates.io-index" 2313 + checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 2314 + 2315 + [[package]] 2316 + name = "windows_i686_gnu" 2317 + version = "0.52.6" 2318 + source = "registry+https://github.com/rust-lang/crates.io-index" 2319 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2320 + 2321 + [[package]] 2322 + name = "windows_i686_gnu" 2323 + version = "0.53.1" 2324 + source = "registry+https://github.com/rust-lang/crates.io-index" 2325 + checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 2326 + 2327 + [[package]] 2328 + name = "windows_i686_gnullvm" 2329 + version = "0.52.6" 2330 + source = "registry+https://github.com/rust-lang/crates.io-index" 2331 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2332 + 2333 + [[package]] 2334 + name = "windows_i686_gnullvm" 2335 + version = "0.53.1" 2336 + source = "registry+https://github.com/rust-lang/crates.io-index" 2337 + checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 2338 + 2339 + [[package]] 2340 + name = "windows_i686_msvc" 2341 + version = "0.52.6" 2342 + source = "registry+https://github.com/rust-lang/crates.io-index" 2343 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2344 + 2345 + [[package]] 2346 + name = "windows_i686_msvc" 2347 + version = "0.53.1" 2348 + source = "registry+https://github.com/rust-lang/crates.io-index" 2349 + checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 2350 + 2351 + [[package]] 2352 + name = "windows_x86_64_gnu" 2353 + version = "0.52.6" 2354 + source = "registry+https://github.com/rust-lang/crates.io-index" 2355 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2356 + 2357 + [[package]] 2358 + name = "windows_x86_64_gnu" 2359 + version = "0.53.1" 2360 + source = "registry+https://github.com/rust-lang/crates.io-index" 2361 + checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 2362 + 2363 + [[package]] 2364 + name = "windows_x86_64_gnullvm" 2365 + version = "0.52.6" 2366 + source = "registry+https://github.com/rust-lang/crates.io-index" 2367 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2368 + 2369 + [[package]] 2370 + name = "windows_x86_64_gnullvm" 2371 + version = "0.53.1" 2372 + source = "registry+https://github.com/rust-lang/crates.io-index" 2373 + checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 2374 + 2375 + [[package]] 2376 + name = "windows_x86_64_msvc" 2377 + version = "0.52.6" 2378 + source = "registry+https://github.com/rust-lang/crates.io-index" 2379 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2380 + 2381 + [[package]] 2382 + name = "windows_x86_64_msvc" 2383 + version = "0.53.1" 2384 + source = "registry+https://github.com/rust-lang/crates.io-index" 2385 + checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 2386 + 2387 + [[package]] 2388 + name = "wit-bindgen" 2389 + version = "0.46.0" 2390 + source = "registry+https://github.com/rust-lang/crates.io-index" 2391 + checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 2392 + 2393 + [[package]] 2394 + name = "writeable" 2395 + version = "0.6.1" 2396 + source = "registry+https://github.com/rust-lang/crates.io-index" 2397 + checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 2398 + 2399 + [[package]] 2400 + name = "yoke" 2401 + version = "0.8.0" 2402 + source = "registry+https://github.com/rust-lang/crates.io-index" 2403 + checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2404 + dependencies = [ 2405 + "serde", 2406 + "stable_deref_trait", 2407 + "yoke-derive", 2408 + "zerofrom", 2409 + ] 2410 + 2411 + [[package]] 2412 + name = "yoke-derive" 2413 + version = "0.8.0" 2414 + source = "registry+https://github.com/rust-lang/crates.io-index" 2415 + checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2416 + dependencies = [ 2417 + "proc-macro2", 2418 + "quote", 2419 + "syn", 2420 + "synstructure", 2421 + ] 2422 + 2423 + [[package]] 2424 + name = "zerocopy" 2425 + version = "0.8.27" 2426 + source = "registry+https://github.com/rust-lang/crates.io-index" 2427 + checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 2428 + dependencies = [ 2429 + "zerocopy-derive", 2430 + ] 2431 + 2432 + [[package]] 2433 + name = "zerocopy-derive" 2434 + version = "0.8.27" 2435 + source = "registry+https://github.com/rust-lang/crates.io-index" 2436 + checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 2437 + dependencies = [ 2438 + "proc-macro2", 2439 + "quote", 2440 + "syn", 2441 + ] 2442 + 2443 + [[package]] 2444 + name = "zerofrom" 2445 + version = "0.1.6" 2446 + source = "registry+https://github.com/rust-lang/crates.io-index" 2447 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2448 + dependencies = [ 2449 + "zerofrom-derive", 2450 + ] 2451 + 2452 + [[package]] 2453 + name = "zerofrom-derive" 2454 + version = "0.1.6" 2455 + source = "registry+https://github.com/rust-lang/crates.io-index" 2456 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2457 + dependencies = [ 2458 + "proc-macro2", 2459 + "quote", 2460 + "syn", 2461 + "synstructure", 2462 + ] 2463 + 2464 + [[package]] 2465 + name = "zeroize" 2466 + version = "1.8.2" 2467 + source = "registry+https://github.com/rust-lang/crates.io-index" 2468 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 2469 + 2470 + [[package]] 2471 + name = "zerotrie" 2472 + version = "0.2.2" 2473 + source = "registry+https://github.com/rust-lang/crates.io-index" 2474 + checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2475 + dependencies = [ 2476 + "displaydoc", 2477 + "yoke", 2478 + "zerofrom", 2479 + ] 2480 + 2481 + [[package]] 2482 + name = "zerovec" 2483 + version = "0.11.4" 2484 + source = "registry+https://github.com/rust-lang/crates.io-index" 2485 + checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 2486 + dependencies = [ 2487 + "yoke", 2488 + "zerofrom", 2489 + "zerovec-derive", 2490 + ] 2491 + 2492 + [[package]] 2493 + name = "zerovec-derive" 2494 + version = "0.11.1" 2495 + source = "registry+https://github.com/rust-lang/crates.io-index" 2496 + checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2497 + dependencies = [ 2498 + "proc-macro2", 2499 + "quote", 2500 + "syn", 2501 + ] 2502 + 2503 + [[package]] 2504 + name = "zstd" 2505 + version = "0.13.3" 2506 + source = "registry+https://github.com/rust-lang/crates.io-index" 2507 + checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 2508 + dependencies = [ 2509 + "zstd-safe", 2510 + ] 2511 + 2512 + [[package]] 2513 + name = "zstd-safe" 2514 + version = "7.2.4" 2515 + source = "registry+https://github.com/rust-lang/crates.io-index" 2516 + checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 2517 + dependencies = [ 2518 + "zstd-sys", 2519 + ] 2520 + 2521 + [[package]] 2522 + name = "zstd-sys" 2523 + version = "2.0.16+zstd.1.5.7" 2524 + source = "registry+https://github.com/rust-lang/crates.io-index" 2525 + checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" 2526 + dependencies = [ 2527 + "cc", 2528 + "pkg-config", 2529 + ]
+19
Cargo.toml
··· 1 + [package] 2 + name = "find-bufo" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + actix-web = "4" 8 + actix-files = "0.6" 9 + actix-cors = "0.7" 10 + serde = { version = "1.0", features = ["derive"] } 11 + serde_json = "1.0" 12 + tokio = { version = "1", features = ["full"] } 13 + reqwest = { version = "0.12", features = ["json", "multipart"] } 14 + anyhow = "1.0" 15 + thiserror = "1.0" 16 + dotenv = "0.15" 17 + env_logger = "0.11" 18 + log = "0.4" 19 + base64 = "0.22"
+30
Dockerfile
··· 1 + FROM rust:1.82-slim as builder 2 + 3 + WORKDIR /app 4 + 5 + RUN apt-get update && apt-get install -y \ 6 + pkg-config \ 7 + libssl-dev \ 8 + && rm -rf /var/lib/apt/lists/* 9 + 10 + COPY Cargo.toml Cargo.lock ./ 11 + COPY src ./src 12 + COPY static ./static 13 + 14 + RUN cargo build --release 15 + 16 + FROM debian:bookworm-slim 17 + 18 + RUN apt-get update && apt-get install -y \ 19 + ca-certificates \ 20 + libssl3 \ 21 + && rm -rf /var/lib/apt/lists/* 22 + 23 + WORKDIR /app 24 + 25 + COPY --from=builder /app/target/release/find-bufo /app/ 26 + COPY static ./static 27 + 28 + ENV RUST_LOG=info 29 + 30 + CMD ["./find-bufo"]
+79
README.md
··· 1 + # find-bufo 2 + 3 + semantic search for the bufo zone 4 + 5 + ## overview 6 + 7 + a one-page application for searching through all the bufos from [bufo.zone](https://bufo.zone/) using multi-modal embeddings and vector search. 8 + 9 + ## architecture 10 + 11 + - **backend**: rust (actix-web) 12 + - **frontend**: vanilla html/css/js 13 + - **embeddings**: voyage ai voyage-multimodal-3 14 + - **vector store**: turbopuffer 15 + - **deployment**: fly.io 16 + 17 + ## setup 18 + 19 + 1. install dependencies: 20 + - rust toolchain 21 + - python 3.11+ with uv 22 + 23 + 2. copy environment variables: 24 + ```bash 25 + cp .env.example .env 26 + ``` 27 + 28 + 3. set your api keys in `.env`: 29 + - `VOYAGE_API_TOKEN` - for generating embeddings 30 + - `TURBOPUFFER_API_KEY` - for vector storage 31 + 32 + ## ingestion 33 + 34 + to populate the vector store with bufos: 35 + 36 + ```bash 37 + uvx scripts/ingest_bufos.py 38 + ``` 39 + 40 + this will: 41 + 1. scrape all bufos from bufo.zone 42 + 2. download them to `data/bufos/` 43 + 3. generate embeddings for each image 44 + 4. upload to turbopuffer 45 + 46 + ## development 47 + 48 + run the server locally: 49 + 50 + ```bash 51 + cargo run 52 + ``` 53 + 54 + the app will be available at `http://localhost:8080` 55 + 56 + ## deployment 57 + 58 + deploy to fly.io: 59 + 60 + ```bash 61 + fly launch # first time 62 + fly secrets set VOYAGE_API_TOKEN=your_token 63 + fly secrets set TURBOPUFFER_API_KEY=your_key 64 + fly deploy 65 + ``` 66 + 67 + ## usage 68 + 69 + 1. open the app 70 + 2. enter a search query describing the bufo you want 71 + 3. see the top matching bufos with similarity scores 72 + 4. click any bufo to open it in a new tab 73 + 74 + ## how it works 75 + 76 + 1. **ingestion**: all bufo images are embedded using voyage ai's multimodal model 77 + 2. **search**: user queries are embedded with the same model 78 + 3. **retrieval**: turbopuffer finds the most similar bufos using cosine distance 79 + 4. **display**: results are shown with similarity scores
+27
fly.toml
··· 1 + app = "find-bufo" 2 + primary_region = "ewr" 3 + 4 + [build] 5 + dockerfile = "Dockerfile" 6 + 7 + [env] 8 + HOST = "0.0.0.0" 9 + PORT = "8080" 10 + TURBOPUFFER_NAMESPACE = "bufos" 11 + 12 + [http_service] 13 + internal_port = 8080 14 + force_https = true 15 + auto_stop_machines = true 16 + auto_start_machines = true 17 + min_machines_running = 0 18 + 19 + [http_service.concurrency] 20 + type = "requests" 21 + hard_limit = 250 22 + soft_limit = 200 23 + 24 + [[vm]] 25 + cpu_kind = "shared" 26 + cpus = 1 27 + memory_mb = 512
+322
scripts/ingest_bufos.py
··· 1 + #!/usr/bin/env python3 2 + # /// script 3 + # requires-python = ">=3.11" 4 + # dependencies = [ 5 + # "httpx", 6 + # "beautifulsoup4", 7 + # "rich", 8 + # "python-dotenv", 9 + # "pillow", 10 + # ] 11 + # /// 12 + """ 13 + Scrape all bufos from bufo.zone, generate embeddings, and upload to turbopuffer. 14 + """ 15 + 16 + import asyncio 17 + import base64 18 + import hashlib 19 + import os 20 + import re 21 + from io import BytesIO 22 + from pathlib import Path 23 + from typing import List 24 + 25 + import httpx 26 + from bs4 import BeautifulSoup 27 + from PIL import Image 28 + from rich.console import Console 29 + from rich.progress import Progress, SpinnerColumn, TextColumn 30 + from dotenv import load_dotenv 31 + 32 + console = Console() 33 + 34 + # Load .env from project root 35 + load_dotenv(Path(__file__).parent.parent / ".env") 36 + 37 + 38 + async def fetch_bufo_urls() -> set[str]: 39 + """Fetch all unique bufo URLs from bufo.zone""" 40 + console.print("[cyan]fetching bufo list from bufo.zone...[/cyan]") 41 + 42 + async with httpx.AsyncClient() as client: 43 + response = await client.get("https://bufo.zone", timeout=30.0) 44 + response.raise_for_status() 45 + 46 + soup = BeautifulSoup(response.text, "html.parser") 47 + 48 + urls = set() 49 + for img in soup.find_all("img"): 50 + src = img.get("src", "") 51 + if "all-the.bufo.zone" in src: 52 + urls.add(src) 53 + 54 + pattern = re.compile( 55 + r"https://all-the\.bufo\.zone/[^\"'>\s]+\.(png|gif|jpg|jpeg|webp)" 56 + ) 57 + for match in pattern.finditer(response.text): 58 + urls.add(match.group(0)) 59 + 60 + console.print(f"[green]found {len(urls)} unique bufo images[/green]") 61 + return urls 62 + 63 + 64 + async def download_bufo(client: httpx.AsyncClient, url: str, output_dir: Path) -> str | None: 65 + """Download a single bufo and return filename""" 66 + filename = url.split("/")[-1] 67 + output_path = output_dir / filename 68 + 69 + if output_path.exists() and output_path.stat().st_size > 0: 70 + return filename 71 + 72 + try: 73 + response = await client.get(url, timeout=30.0) 74 + response.raise_for_status() 75 + output_path.write_bytes(response.content) 76 + return filename 77 + except Exception as e: 78 + console.print(f"[red]error downloading {url}: {e}[/red]") 79 + return None 80 + 81 + 82 + async def download_all_bufos(urls: set[str], output_dir: Path) -> List[Path]: 83 + """Download all bufos concurrently""" 84 + output_dir.mkdir(parents=True, exist_ok=True) 85 + 86 + downloaded_files = [] 87 + 88 + async with httpx.AsyncClient() as client: 89 + with Progress( 90 + SpinnerColumn(), 91 + TextColumn("[progress.description]{task.description}"), 92 + console=console, 93 + ) as progress: 94 + task = progress.add_task( 95 + f"[cyan]downloading {len(urls)} bufos...", total=len(urls) 96 + ) 97 + 98 + batch_size = 10 99 + urls_list = list(urls) 100 + 101 + for i in range(0, len(urls_list), batch_size): 102 + batch = urls_list[i : i + batch_size] 103 + tasks = [download_bufo(client, url, output_dir) for url in batch] 104 + results = await asyncio.gather(*tasks, return_exceptions=True) 105 + 106 + for filename in results: 107 + if filename and not isinstance(filename, Exception): 108 + downloaded_files.append(output_dir / filename) 109 + 110 + progress.update(task, advance=len(batch)) 111 + 112 + if i + batch_size < len(urls_list): 113 + await asyncio.sleep(0.5) 114 + 115 + console.print(f"[green]downloaded {len(downloaded_files)} bufos[/green]") 116 + return downloaded_files 117 + 118 + 119 + async def embed_image(client: httpx.AsyncClient, image_path: Path, api_key: str, max_retries: int = 3) -> List[float] | None: 120 + """Generate embedding for an image using Voyage AI with retry logic""" 121 + for attempt in range(max_retries): 122 + try: 123 + image = Image.open(image_path) 124 + 125 + # check if this is an animated image 126 + is_animated = hasattr(image, 'n_frames') and image.n_frames > 1 127 + 128 + if is_animated: 129 + # for animated GIFs, extract multiple keyframes for temporal representation 130 + num_frames = image.n_frames 131 + # extract up to 5 evenly distributed frames 132 + max_frames = min(5, num_frames) 133 + frame_indices = [int(i * (num_frames - 1) / (max_frames - 1)) for i in range(max_frames)] 134 + 135 + # extract each frame as base64 image 136 + content = [] 137 + for frame_idx in frame_indices: 138 + image.seek(frame_idx) 139 + buffered = BytesIO() 140 + image.convert("RGB").save(buffered, format="WEBP", lossless=True) 141 + img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8") 142 + content.append({ 143 + "type": "image_base64", 144 + "image_base64": f"data:image/webp;base64,{img_base64}", 145 + }) 146 + else: 147 + # for static images, just send the single image 148 + buffered = BytesIO() 149 + image.convert("RGB").save(buffered, format="WEBP", lossless=True) 150 + img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8") 151 + content = [{ 152 + "type": "image_base64", 153 + "image_base64": f"data:image/webp;base64,{img_base64}", 154 + }] 155 + 156 + response = await client.post( 157 + "https://api.voyageai.com/v1/multimodalembeddings", 158 + headers={ 159 + "Authorization": f"Bearer {api_key}", 160 + "Content-Type": "application/json", 161 + }, 162 + json={ 163 + "inputs": [{"content": content}], 164 + "model": "voyage-multimodal-3", 165 + }, 166 + timeout=60.0, 167 + ) 168 + response.raise_for_status() 169 + result = response.json() 170 + return result["data"][0]["embedding"] 171 + except httpx.HTTPStatusError as e: 172 + if e.response.status_code == 429: 173 + # rate limited - exponential backoff 174 + wait_time = (2 ** attempt) * 2 # 2s, 4s, 8s 175 + if attempt < max_retries - 1: 176 + await asyncio.sleep(wait_time) 177 + continue 178 + # show actual error response for 400s 179 + error_detail = e.response.text if e.response.status_code == 400 else str(e) 180 + console.print(f"[red]error embedding {image_path.name} ({e.response.status_code}): {error_detail}[/red]") 181 + return None 182 + except Exception as e: 183 + console.print(f"[red]error embedding {image_path.name}: {e}[/red]") 184 + return None 185 + return None 186 + 187 + 188 + async def generate_embeddings( 189 + image_paths: List[Path], api_key: str 190 + ) -> dict[str, List[float]]: 191 + """Generate embeddings for all images with controlled concurrency""" 192 + embeddings = {} 193 + 194 + # limit to 50 concurrent requests to stay well under 2000/min rate limit 195 + semaphore = asyncio.Semaphore(50) 196 + 197 + async def embed_with_semaphore(client, image_path): 198 + async with semaphore: 199 + embedding = await embed_image(client, image_path, api_key) 200 + return (image_path.name, embedding) 201 + 202 + async with httpx.AsyncClient() as client: 203 + with Progress( 204 + SpinnerColumn(), 205 + TextColumn("[progress.description]{task.description}"), 206 + console=console, 207 + ) as progress: 208 + task = progress.add_task( 209 + f"[cyan]generating embeddings for {len(image_paths)} images...", 210 + total=len(image_paths), 211 + ) 212 + 213 + # process all images concurrently with semaphore 214 + tasks = [embed_with_semaphore(client, img) for img in image_paths] 215 + results = await asyncio.gather(*tasks) 216 + 217 + for name, embedding in results: 218 + if embedding: 219 + embeddings[name] = embedding 220 + progress.update(task, advance=1) 221 + 222 + console.print(f"[green]generated {len(embeddings)} embeddings[/green]") 223 + return embeddings 224 + 225 + 226 + async def upload_to_turbopuffer( 227 + embeddings: dict[str, List[float]], 228 + bufo_urls: dict[str, str], 229 + api_key: str, 230 + namespace: str, 231 + ): 232 + """Upload embeddings to turbopuffer""" 233 + console.print("[cyan]uploading to turbopuffer...[/cyan]") 234 + 235 + ids = [] 236 + vectors = [] 237 + urls = [] 238 + names = [] 239 + filenames = [] 240 + 241 + for filename, embedding in embeddings.items(): 242 + # use hash as ID to stay under 64 byte limit 243 + file_hash = hashlib.sha256(filename.encode()).hexdigest()[:16] 244 + ids.append(file_hash) 245 + vectors.append(embedding) 246 + urls.append(bufo_urls.get(filename, "")) 247 + names.append(filename.rsplit(".", 1)[0]) 248 + filenames.append(filename) 249 + 250 + async with httpx.AsyncClient() as client: 251 + response = await client.post( 252 + f"https://api.turbopuffer.com/v1/vectors/{namespace}", 253 + headers={ 254 + "Authorization": f"Bearer {api_key}", 255 + "Content-Type": "application/json", 256 + }, 257 + json={ 258 + "ids": ids, 259 + "vectors": vectors, 260 + "distance_metric": "cosine_distance", 261 + "attributes": { 262 + "url": urls, 263 + "name": names, 264 + "filename": filenames, 265 + }, 266 + "schema": { 267 + "name": { 268 + "type": "string", 269 + "full_text_search": True, 270 + }, 271 + "filename": { 272 + "type": "string", 273 + "full_text_search": True, 274 + }, 275 + }, 276 + }, 277 + timeout=120.0, 278 + ) 279 + if response.status_code != 200: 280 + console.print(f"[red]turbopuffer error: {response.text}[/red]") 281 + response.raise_for_status() 282 + 283 + console.print( 284 + f"[green]uploaded {len(ids)} bufos to turbopuffer namespace '{namespace}'[/green]" 285 + ) 286 + 287 + 288 + async def main(): 289 + """Main function""" 290 + console.print("[bold cyan]bufo ingestion pipeline[/bold cyan]\n") 291 + 292 + voyage_api_key = os.getenv("VOYAGE_API_TOKEN") 293 + if not voyage_api_key: 294 + console.print("[red]VOYAGE_API_TOKEN not set[/red]") 295 + return 296 + 297 + tpuf_api_key = os.getenv("TURBOPUFFER_API_KEY") 298 + if not tpuf_api_key: 299 + console.print("[red]TURBOPUFFER_API_KEY not set[/red]") 300 + return 301 + 302 + tpuf_namespace = os.getenv("TURBOPUFFER_NAMESPACE", "bufos") 303 + 304 + script_dir = Path(__file__).parent 305 + project_root = script_dir.parent 306 + output_dir = project_root / "data" / "bufos" 307 + 308 + bufo_urls_raw = await fetch_bufo_urls() 309 + 310 + bufo_urls_map = {url.split("/")[-1]: url for url in bufo_urls_raw} 311 + 312 + image_paths = await download_all_bufos(bufo_urls_raw, output_dir) 313 + 314 + embeddings = await generate_embeddings(image_paths, voyage_api_key) 315 + 316 + await upload_to_turbopuffer(embeddings, bufo_urls_map, tpuf_api_key, tpuf_namespace) 317 + 318 + console.print("\n[bold green]ingestion complete![/bold green]") 319 + 320 + 321 + if __name__ == "__main__": 322 + asyncio.run(main())
+29
src/config.rs
··· 1 + use anyhow::{Context, Result}; 2 + use std::env; 3 + 4 + #[derive(Clone, Debug)] 5 + pub struct Config { 6 + pub host: String, 7 + pub port: u16, 8 + pub turbopuffer_api_key: String, 9 + pub turbopuffer_namespace: String, 10 + pub voyage_api_key: String, 11 + } 12 + 13 + impl Config { 14 + pub fn from_env() -> Result<Self> { 15 + Ok(Config { 16 + host: env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string()), 17 + port: env::var("PORT") 18 + .unwrap_or_else(|_| "8080".to_string()) 19 + .parse() 20 + .context("failed to parse PORT")?, 21 + turbopuffer_api_key: env::var("TURBOPUFFER_API_KEY") 22 + .context("TURBOPUFFER_API_KEY must be set")?, 23 + turbopuffer_namespace: env::var("TURBOPUFFER_NAMESPACE") 24 + .unwrap_or_else(|_| "bufos".to_string()), 25 + voyage_api_key: env::var("VOYAGE_API_TOKEN") 26 + .context("VOYAGE_API_TOKEN must be set")?, 27 + }) 28 + } 29 + }
+98
src/embedding.rs
··· 1 + use anyhow::{Context, Result}; 2 + use reqwest::Client; 3 + use serde::{Deserialize, Serialize}; 4 + 5 + #[derive(Debug, Serialize)] 6 + struct VoyageEmbeddingRequest { 7 + inputs: Vec<MultimodalInput>, 8 + model: String, 9 + } 10 + 11 + #[derive(Debug, Serialize)] 12 + struct MultimodalInput { 13 + content: Vec<ContentSegment>, 14 + } 15 + 16 + #[derive(Debug, Serialize)] 17 + #[serde(tag = "type", rename_all = "snake_case")] 18 + enum ContentSegment { 19 + Text { text: String }, 20 + } 21 + 22 + #[derive(Debug, Deserialize)] 23 + struct VoyageEmbeddingResponse { 24 + data: Vec<VoyageEmbeddingData>, 25 + } 26 + 27 + #[derive(Debug, Deserialize)] 28 + struct VoyageEmbeddingData { 29 + embedding: Vec<f32>, 30 + } 31 + 32 + pub struct EmbeddingClient { 33 + client: Client, 34 + api_key: String, 35 + } 36 + 37 + impl EmbeddingClient { 38 + pub fn new(api_key: String) -> Self { 39 + Self { 40 + client: Client::new(), 41 + api_key, 42 + } 43 + } 44 + 45 + pub async fn embed_text(&self, text: &str) -> Result<Vec<f32>> { 46 + let request = VoyageEmbeddingRequest { 47 + inputs: vec![MultimodalInput { 48 + content: vec![ContentSegment::Text { 49 + text: text.to_string(), 50 + }], 51 + }], 52 + model: "voyage-multimodal-3".to_string(), 53 + }; 54 + 55 + let json_body = serde_json::to_string(&request)?; 56 + log::debug!("Sending request body: {}", json_body); 57 + 58 + let response = self 59 + .client 60 + .post("https://api.voyageai.com/v1/multimodalembeddings") 61 + .header("Authorization", format!("Bearer {}", self.api_key)) 62 + .json(&request) 63 + .send() 64 + .await 65 + .context("failed to send embedding request")?; 66 + 67 + if !response.status().is_success() { 68 + let status = response.status(); 69 + let body = response.text().await.unwrap_or_default(); 70 + anyhow::bail!("voyage api error ({}): {}", status, body); 71 + } 72 + 73 + let embedding_response: VoyageEmbeddingResponse = response 74 + .json() 75 + .await 76 + .context("failed to parse embedding response")?; 77 + 78 + let embedding = embedding_response 79 + .data 80 + .into_iter() 81 + .next() 82 + .map(|d| d.embedding) 83 + .context("no embedding returned")?; 84 + 85 + log::debug!( 86 + "Generated embedding for '{}': dimension={}, first 5 values=[{:.4}, {:.4}, {:.4}, {:.4}, {:.4}]", 87 + text, 88 + embedding.len(), 89 + embedding.get(0).unwrap_or(&0.0), 90 + embedding.get(1).unwrap_or(&0.0), 91 + embedding.get(2).unwrap_or(&0.0), 92 + embedding.get(3).unwrap_or(&0.0), 93 + embedding.get(4).unwrap_or(&0.0) 94 + ); 95 + 96 + Ok(embedding) 97 + } 98 + }
+49
src/main.rs
··· 1 + mod config; 2 + mod embedding; 3 + mod search; 4 + mod turbopuffer; 5 + 6 + use actix_cors::Cors; 7 + use actix_files as fs; 8 + use actix_web::{middleware, web, App, HttpResponse, HttpServer}; 9 + use anyhow::Result; 10 + use config::Config; 11 + 12 + async fn index() -> HttpResponse { 13 + HttpResponse::Ok() 14 + .content_type("text/html; charset=utf-8") 15 + .body(include_str!("../static/index.html")) 16 + } 17 + 18 + #[actix_web::main] 19 + async fn main() -> Result<()> { 20 + dotenv::dotenv().ok(); 21 + env_logger::init(); 22 + 23 + let config = Config::from_env()?; 24 + let host = config.host.clone(); 25 + let port = config.port; 26 + 27 + log::info!("starting bufo search server on {}:{}", host, port); 28 + 29 + HttpServer::new(move || { 30 + let cors = Cors::permissive(); 31 + 32 + App::new() 33 + .wrap(middleware::Logger::default()) 34 + .wrap(cors) 35 + .app_data(web::Data::new(config.clone())) 36 + .route("/", web::get().to(index)) 37 + .service( 38 + web::scope("/api") 39 + .route("/search", web::post().to(search::search)) 40 + .route("/health", web::get().to(|| async { HttpResponse::Ok().body("ok") })) 41 + ) 42 + .service(fs::Files::new("/static", "./static").show_files_listing()) 43 + }) 44 + .bind((host.as_str(), port))? 45 + .run() 46 + .await?; 47 + 48 + Ok(()) 49 + }
+145
src/search.rs
··· 1 + use crate::config::Config; 2 + use crate::embedding::EmbeddingClient; 3 + use crate::turbopuffer::{QueryRequest, TurbopufferClient}; 4 + use actix_web::{web, HttpResponse, Result as ActixResult}; 5 + use serde::{Deserialize, Serialize}; 6 + 7 + #[derive(Debug, Deserialize)] 8 + pub struct SearchQuery { 9 + pub query: String, 10 + #[serde(default = "default_top_k")] 11 + pub top_k: usize, 12 + } 13 + 14 + fn default_top_k() -> usize { 15 + 10 16 + } 17 + 18 + #[derive(Debug, Serialize)] 19 + pub struct SearchResponse { 20 + pub results: Vec<BufoResult>, 21 + } 22 + 23 + #[derive(Debug, Serialize)] 24 + pub struct BufoResult { 25 + pub id: String, 26 + pub url: String, 27 + pub name: String, 28 + pub score: f32, // normalized 0-1 score for display 29 + } 30 + 31 + pub async fn search( 32 + query: web::Json<SearchQuery>, 33 + config: web::Data<Config>, 34 + ) -> ActixResult<HttpResponse> { 35 + let embedding_client = EmbeddingClient::new(config.voyage_api_key.clone()); 36 + let tpuf_client = TurbopufferClient::new( 37 + config.turbopuffer_api_key.clone(), 38 + config.turbopuffer_namespace.clone(), 39 + ); 40 + 41 + // Run vector search 42 + let query_embedding = embedding_client 43 + .embed_text(&query.query) 44 + .await 45 + .map_err(|e| { 46 + log::error!("failed to generate embedding: {}", e); 47 + actix_web::error::ErrorInternalServerError(format!( 48 + "failed to generate embedding: {}", 49 + e 50 + )) 51 + })?; 52 + 53 + let vector_request = QueryRequest { 54 + rank_by: vec![ 55 + serde_json::json!("vector"), 56 + serde_json::json!("ANN"), 57 + serde_json::json!(query_embedding), 58 + ], 59 + top_k: query.top_k * 2, // get more results for fusion 60 + include_attributes: Some(vec!["url".to_string(), "name".to_string(), "filename".to_string()]), 61 + }; 62 + 63 + let vector_results = tpuf_client.query(vector_request).await.map_err(|e| { 64 + log::error!("failed to query turbopuffer (vector): {}", e); 65 + actix_web::error::ErrorInternalServerError(format!( 66 + "failed to query turbopuffer (vector): {}", 67 + e 68 + )) 69 + })?; 70 + 71 + // Run BM25 text search 72 + let bm25_results = tpuf_client.bm25_query(&query.query, query.top_k * 2).await.map_err(|e| { 73 + log::error!("failed to query turbopuffer (BM25): {}", e); 74 + actix_web::error::ErrorInternalServerError(format!( 75 + "failed to query turbopuffer (BM25): {}", 76 + e 77 + )) 78 + })?; 79 + 80 + // Combine results using Reciprocal Rank Fusion (RRF) 81 + use std::collections::HashMap; 82 + let mut rrf_scores: HashMap<String, f32> = HashMap::new(); 83 + let k = 60.0; // RRF constant 84 + 85 + // Add vector search rankings 86 + for (rank, row) in vector_results.iter().enumerate() { 87 + let score = 1.0 / (k + (rank as f32) + 1.0); 88 + *rrf_scores.entry(row.id.clone()).or_insert(0.0) += score; 89 + } 90 + 91 + // Add BM25 search rankings 92 + for (rank, row) in bm25_results.iter().enumerate() { 93 + let score = 1.0 / (k + (rank as f32) + 1.0); 94 + *rrf_scores.entry(row.id.clone()).or_insert(0.0) += score; 95 + } 96 + 97 + // Collect all unique results 98 + let mut all_results: HashMap<String, crate::turbopuffer::QueryRow> = HashMap::new(); 99 + for row in vector_results.into_iter().chain(bm25_results.into_iter()) { 100 + all_results.entry(row.id.clone()).or_insert(row); 101 + } 102 + 103 + // Sort by RRF score and take top_k 104 + let mut scored_results: Vec<(String, f32)> = rrf_scores.into_iter().collect(); 105 + scored_results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); 106 + scored_results.truncate(query.top_k); 107 + 108 + // Normalize scores to 0-0.95 range 109 + let max_score = scored_results.first().map(|(_, s)| *s).unwrap_or(1.0); 110 + let min_score = scored_results.last().map(|(_, s)| *s).unwrap_or(0.0); 111 + let score_range = (max_score - min_score).max(0.001); // avoid division by zero 112 + 113 + let results: Vec<BufoResult> = scored_results 114 + .into_iter() 115 + .filter_map(|(id, raw_score)| { 116 + all_results.get(&id).map(|row| { 117 + let url = row 118 + .attributes 119 + .get("url") 120 + .and_then(|v| v.as_str()) 121 + .unwrap_or("") 122 + .to_string(); 123 + 124 + let name = row 125 + .attributes 126 + .get("name") 127 + .and_then(|v| v.as_str()) 128 + .unwrap_or(&row.id) 129 + .to_string(); 130 + 131 + // Normalize score to 0-0.95 range 132 + let normalized_score = ((raw_score - min_score) / score_range) * 0.95; 133 + 134 + BufoResult { 135 + id: row.id.clone(), 136 + url, 137 + name, 138 + score: normalized_score, 139 + } 140 + }) 141 + }) 142 + .collect(); 143 + 144 + Ok(HttpResponse::Ok().json(SearchResponse { results })) 145 + }
+138
src/turbopuffer.rs
··· 1 + use anyhow::{Context, Result}; 2 + use reqwest::Client; 3 + use serde::{Deserialize, Serialize}; 4 + 5 + #[derive(Debug, Serialize)] 6 + pub struct UpsertRequest { 7 + pub ids: Vec<String>, 8 + pub vectors: Vec<Vec<f32>>, 9 + pub attributes: serde_json::Value, 10 + } 11 + 12 + #[derive(Debug, Serialize)] 13 + pub struct QueryRequest { 14 + pub rank_by: Vec<serde_json::Value>, 15 + pub top_k: usize, 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + pub include_attributes: Option<Vec<String>>, 18 + } 19 + 20 + pub type QueryResponse = Vec<QueryRow>; 21 + 22 + #[derive(Debug, Deserialize, Serialize, Clone)] 23 + pub struct QueryRow { 24 + pub id: String, 25 + pub dist: Option<f32>, 26 + pub attributes: serde_json::Map<String, serde_json::Value>, 27 + } 28 + 29 + pub struct TurbopufferClient { 30 + client: Client, 31 + api_key: String, 32 + namespace: String, 33 + } 34 + 35 + impl TurbopufferClient { 36 + pub fn new(api_key: String, namespace: String) -> Self { 37 + Self { 38 + client: Client::new(), 39 + api_key, 40 + namespace, 41 + } 42 + } 43 + 44 + pub async fn upsert(&self, request: UpsertRequest) -> Result<()> { 45 + let url = format!( 46 + "https://api.turbopuffer.com/v1/vectors/{}", 47 + self.namespace 48 + ); 49 + 50 + let response = self 51 + .client 52 + .post(&url) 53 + .header("Authorization", format!("Bearer {}", self.api_key)) 54 + .json(&request) 55 + .send() 56 + .await 57 + .context("failed to send upsert request")?; 58 + 59 + if !response.status().is_success() { 60 + let status = response.status(); 61 + let body = response.text().await.unwrap_or_default(); 62 + anyhow::bail!( 63 + "turbopuffer upsert failed with status {}: {}", 64 + status, 65 + body 66 + ); 67 + } 68 + 69 + Ok(()) 70 + } 71 + 72 + pub async fn query(&self, request: QueryRequest) -> Result<QueryResponse> { 73 + let url = format!( 74 + "https://api.turbopuffer.com/v1/vectors/{}/query", 75 + self.namespace 76 + ); 77 + 78 + let request_json = serde_json::to_string_pretty(&request)?; 79 + log::debug!("turbopuffer query request: {}", request_json); 80 + 81 + let response = self 82 + .client 83 + .post(&url) 84 + .header("Authorization", format!("Bearer {}", self.api_key)) 85 + .json(&request) 86 + .send() 87 + .await 88 + .context("failed to send query request")?; 89 + 90 + if !response.status().is_success() { 91 + let status = response.status(); 92 + let body = response.text().await.unwrap_or_default(); 93 + anyhow::bail!("turbopuffer query failed with status {}: {}", status, body); 94 + } 95 + 96 + let body = response.text().await.context("failed to read response body")?; 97 + log::debug!("turbopuffer response: {}", body); 98 + 99 + serde_json::from_str(&body) 100 + .context(format!("failed to parse query response: {}", body)) 101 + } 102 + 103 + pub async fn bm25_query(&self, query_text: &str, top_k: usize) -> Result<QueryResponse> { 104 + let url = format!( 105 + "https://api.turbopuffer.com/v1/vectors/{}/query", 106 + self.namespace 107 + ); 108 + 109 + let request = serde_json::json!({ 110 + "rank_by": ["name", "BM25", query_text], 111 + "top_k": top_k, 112 + "include_attributes": ["url", "name", "filename"], 113 + }); 114 + 115 + log::debug!("turbopuffer BM25 query request: {}", serde_json::to_string_pretty(&request)?); 116 + 117 + let response = self 118 + .client 119 + .post(&url) 120 + .header("Authorization", format!("Bearer {}", self.api_key)) 121 + .json(&request) 122 + .send() 123 + .await 124 + .context("failed to send BM25 query request")?; 125 + 126 + if !response.status().is_success() { 127 + let status = response.status(); 128 + let body = response.text().await.unwrap_or_default(); 129 + anyhow::bail!("turbopuffer BM25 query failed with status {}: {}", status, body); 130 + } 131 + 132 + let body = response.text().await.context("failed to read response body")?; 133 + log::debug!("turbopuffer BM25 response: {}", body); 134 + 135 + serde_json::from_str(&body) 136 + .context(format!("failed to parse BM25 query response: {}", body)) 137 + } 138 + }
+249
static/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>find bufo</title> 7 + <style> 8 + * { 9 + margin: 0; 10 + padding: 0; 11 + box-sizing: border-box; 12 + } 13 + 14 + body { 15 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 16 + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 17 + min-height: 100vh; 18 + display: flex; 19 + justify-content: center; 20 + align-items: center; 21 + padding: 20px; 22 + } 23 + 24 + .container { 25 + max-width: 800px; 26 + width: 100%; 27 + } 28 + 29 + .header { 30 + text-align: center; 31 + margin-bottom: 40px; 32 + } 33 + 34 + h1 { 35 + color: white; 36 + font-size: 3em; 37 + font-weight: 700; 38 + margin-bottom: 10px; 39 + text-shadow: 2px 2px 4px rgba(0,0,0,0.2); 40 + } 41 + 42 + .subtitle { 43 + color: rgba(255, 255, 255, 0.9); 44 + font-size: 1.1em; 45 + } 46 + 47 + .search-box { 48 + background: white; 49 + border-radius: 12px; 50 + padding: 20px; 51 + box-shadow: 0 10px 40px rgba(0,0,0,0.2); 52 + margin-bottom: 30px; 53 + } 54 + 55 + .search-input-wrapper { 56 + display: flex; 57 + gap: 10px; 58 + } 59 + 60 + input[type="text"] { 61 + flex: 1; 62 + padding: 15px; 63 + border: 2px solid #e0e0e0; 64 + border-radius: 8px; 65 + font-size: 16px; 66 + transition: border-color 0.3s; 67 + } 68 + 69 + input[type="text"]:focus { 70 + outline: none; 71 + border-color: #667eea; 72 + } 73 + 74 + button { 75 + padding: 15px 30px; 76 + background: #667eea; 77 + color: white; 78 + border: none; 79 + border-radius: 8px; 80 + font-size: 16px; 81 + font-weight: 600; 82 + cursor: pointer; 83 + transition: background 0.3s; 84 + } 85 + 86 + button:hover { 87 + background: #5568d3; 88 + } 89 + 90 + button:disabled { 91 + background: #ccc; 92 + cursor: not-allowed; 93 + } 94 + 95 + .results { 96 + display: grid; 97 + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); 98 + gap: 20px; 99 + } 100 + 101 + .bufo-card { 102 + background: white; 103 + border-radius: 12px; 104 + padding: 15px; 105 + box-shadow: 0 4px 6px rgba(0,0,0,0.1); 106 + transition: transform 0.2s, box-shadow 0.2s; 107 + cursor: pointer; 108 + text-align: center; 109 + } 110 + 111 + .bufo-card:hover { 112 + transform: translateY(-5px); 113 + box-shadow: 0 8px 12px rgba(0,0,0,0.15); 114 + } 115 + 116 + .bufo-image { 117 + width: 100%; 118 + height: 120px; 119 + object-fit: contain; 120 + margin-bottom: 10px; 121 + } 122 + 123 + .bufo-name { 124 + font-size: 0.9em; 125 + color: #333; 126 + font-weight: 500; 127 + word-break: break-word; 128 + } 129 + 130 + .bufo-score { 131 + font-size: 0.8em; 132 + color: #888; 133 + margin-top: 5px; 134 + } 135 + 136 + .loading { 137 + text-align: center; 138 + color: white; 139 + font-size: 1.2em; 140 + } 141 + 142 + .error { 143 + background: #ff6b6b; 144 + color: white; 145 + padding: 15px; 146 + border-radius: 8px; 147 + margin-bottom: 20px; 148 + } 149 + 150 + .no-results { 151 + text-align: center; 152 + color: white; 153 + font-size: 1.2em; 154 + padding: 40px; 155 + } 156 + </style> 157 + </head> 158 + <body> 159 + <div class="container"> 160 + <div class="header"> 161 + <h1>find bufo</h1> 162 + <p class="subtitle">search the bufo zone</p> 163 + </div> 164 + 165 + <div class="search-box"> 166 + <div class="search-input-wrapper"> 167 + <input 168 + type="text" 169 + id="searchInput" 170 + placeholder="describe the bufo you seek..." 171 + autocomplete="off" 172 + > 173 + <button id="searchButton">search</button> 174 + </div> 175 + </div> 176 + 177 + <div id="error" class="error" style="display: none;"></div> 178 + <div id="loading" class="loading" style="display: none;">searching...</div> 179 + <div id="results" class="results"></div> 180 + </div> 181 + 182 + <script> 183 + const searchInput = document.getElementById('searchInput'); 184 + const searchButton = document.getElementById('searchButton'); 185 + const resultsDiv = document.getElementById('results'); 186 + const loadingDiv = document.getElementById('loading'); 187 + const errorDiv = document.getElementById('error'); 188 + 189 + async function search() { 190 + const query = searchInput.value.trim(); 191 + if (!query) return; 192 + 193 + searchButton.disabled = true; 194 + loadingDiv.style.display = 'block'; 195 + resultsDiv.innerHTML = ''; 196 + errorDiv.style.display = 'none'; 197 + 198 + try { 199 + const response = await fetch('/api/search', { 200 + method: 'POST', 201 + headers: { 202 + 'Content-Type': 'application/json', 203 + }, 204 + body: JSON.stringify({ query, top_k: 20 }), 205 + }); 206 + 207 + if (!response.ok) { 208 + throw new Error(`search failed: ${response.statusText}`); 209 + } 210 + 211 + const data = await response.json(); 212 + displayResults(data.results); 213 + } catch (error) { 214 + errorDiv.textContent = error.message; 215 + errorDiv.style.display = 'block'; 216 + } finally { 217 + searchButton.disabled = false; 218 + loadingDiv.style.display = 'none'; 219 + } 220 + } 221 + 222 + function displayResults(results) { 223 + if (results.length === 0) { 224 + resultsDiv.innerHTML = '<div class="no-results">no bufos found</div>'; 225 + return; 226 + } 227 + 228 + results.forEach(bufo => { 229 + const card = document.createElement('div'); 230 + card.className = 'bufo-card'; 231 + card.onclick = () => window.open(bufo.url, '_blank'); 232 + 233 + card.innerHTML = ` 234 + <img src="${bufo.url}" alt="${bufo.name}" class="bufo-image" loading="lazy"> 235 + <div class="bufo-name">${bufo.name}</div> 236 + <div class="bufo-score">${(bufo.score * 100).toFixed(1)}%</div> 237 + `; 238 + 239 + resultsDiv.appendChild(card); 240 + }); 241 + } 242 + 243 + searchButton.addEventListener('click', search); 244 + searchInput.addEventListener('keypress', (e) => { 245 + if (e.key === 'Enter') search(); 246 + }); 247 + </script> 248 + </body> 249 + </html>