this repo has no description
20
fork

Configure Feed

Select the types of activity you want to include in your feed.

release: 0.14.2

+1406 -198
+3
.github/workflows/release-binaries.yml
··· 39 39 - name: atpxrpc 40 40 package: atpxrpc 41 41 features: "" 42 + - name: atptid 43 + package: atproto-record 44 + features: clap 42 45 43 46 runs-on: ${{ matrix.target.os }} 44 47
+12
CHANGELOG.md
··· 7 7 8 8 ## [Unreleased] 9 9 10 + ## [0.14.2] - 2026-03-09 11 + ### Added 12 + - `atptid` CLI tool for generating and parsing AT Protocol Timestamp Identifiers 13 + - `get_lexicon` tool to atpmcp MCP server for fetching lexicon schema records 14 + - `invoke_xrpc` tool to atpmcp MCP server 15 + - `generate_tid` tool to atpmcp MCP server 16 + - `validate_xrpc` tool to atpmcp MCP server 17 + 18 + ### Changed 19 + - Updated release workflow and README to include atptid and atpcid binaries 20 + 10 21 ## [0.14.1] - 2026-03-08 11 22 ### Added 12 23 - `atpxrpc` CLI tool for persistent XRPC session management with proxy support ··· 194 205 - Core DID document handling 195 206 - Cryptographic key operations for P-256 curves 196 207 208 + [0.14.2]: https://tangled.org/ngerakines.me/atproto-crates/tree/v0.14.2 197 209 [0.14.1]: https://tangled.org/ngerakines.me/atproto-crates/tree/v0.14.1 198 210 [0.14.0]: https://tangled.org/ngerakines.me/atproto-crates/tree/v0.14.0 199 211 [0.13.0]: https://tangled.org/ngerakines.me/atproto-crates/tree/v0.13.0
+2
CLAUDE.md
··· 38 38 39 39 #### Record Operations 40 40 - **Generate CID**: `cat record.json | cargo run --features clap --bin atproto-record-cid` (reads JSON from stdin, outputs CID) 41 + - **Generate TID**: `cargo run --features clap --bin atptid` (generates a TID; use `-n 5` for batch, pass a TID to parse, or pass a microsecond timestamp to generate from) 41 42 42 43 #### Client Tools 43 44 - **App password auth**: `cargo run --features clap --bin atproto-client-app-password -- <subject> <access_token> <xrpc_path>` ··· 175 176 176 177 #### Record Operations (atproto-record) 177 178 - **`src/bin/atproto-record-cid.rs`**: Generate CID (Content Identifier) for AT Protocol records using DAG-CBOR serialization 179 + - **`src/bin/atptid.rs`**: Generate and parse AT Protocol Timestamp Identifiers (TIDs) 178 180 179 181 #### Client Tools (atproto-client) 180 182 - **`src/bin/atproto-client-app-password.rs`**: Make XRPC calls using app password authentication
+322 -146
Cargo.lock
··· 75 75 76 76 [[package]] 77 77 name = "anyhow" 78 - version = "1.0.100" 78 + version = "1.0.102" 79 79 source = "registry+https://github.com/rust-lang/crates.io-index" 80 - checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 80 + checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 81 81 82 82 [[package]] 83 83 name = "arrayref" ··· 110 110 111 111 [[package]] 112 112 name = "atpmcp" 113 - version = "0.14.1" 113 + version = "0.14.2" 114 114 dependencies = [ 115 115 "anyhow", 116 116 "async-trait", ··· 120 120 "atproto-identity", 121 121 "atproto-lexicon", 122 122 "atproto-record", 123 + "dirs", 123 124 "reqwest", 124 125 "serde", 125 126 "serde_json", ··· 131 132 132 133 [[package]] 133 134 name = "atproto-attestation" 134 - version = "0.14.1" 135 + version = "0.14.2" 135 136 dependencies = [ 136 137 "anyhow", 137 138 "async-trait", ··· 157 158 158 159 [[package]] 159 160 name = "atproto-client" 160 - version = "0.14.1" 161 + version = "0.14.2" 161 162 dependencies = [ 162 163 "anyhow", 163 164 "async-trait", ··· 181 182 182 183 [[package]] 183 184 name = "atproto-dasl" 184 - version = "0.14.1" 185 + version = "0.14.2" 185 186 dependencies = [ 186 187 "anyhow", 187 188 "axum", ··· 209 210 210 211 [[package]] 211 212 name = "atproto-extras" 212 - version = "0.14.1" 213 + version = "0.14.2" 213 214 dependencies = [ 214 215 "anyhow", 215 216 "async-trait", ··· 224 225 225 226 [[package]] 226 227 name = "atproto-identity" 227 - version = "0.14.1" 228 + version = "0.14.2" 228 229 dependencies = [ 229 230 "anyhow", 230 231 "async-trait", ··· 255 256 256 257 [[package]] 257 258 name = "atproto-jetstream" 258 - version = "0.14.1" 259 + version = "0.14.2" 259 260 dependencies = [ 260 261 "anyhow", 261 262 "async-trait", ··· 277 278 278 279 [[package]] 279 280 name = "atproto-lexicon" 280 - version = "0.14.1" 281 + version = "0.14.2" 281 282 dependencies = [ 282 283 "anyhow", 283 284 "async-trait", ··· 302 303 303 304 [[package]] 304 305 name = "atproto-oauth" 305 - version = "0.14.1" 306 + version = "0.14.2" 306 307 dependencies = [ 307 308 "anyhow", 308 309 "async-trait", ··· 333 334 334 335 [[package]] 335 336 name = "atproto-oauth-aip" 336 - version = "0.14.1" 337 + version = "0.14.2" 337 338 dependencies = [ 338 339 "anyhow", 339 340 "atproto-identity", ··· 348 349 349 350 [[package]] 350 351 name = "atproto-oauth-axum" 351 - version = "0.14.1" 352 + version = "0.14.2" 352 353 dependencies = [ 353 354 "anyhow", 354 355 "async-trait", ··· 377 378 378 379 [[package]] 379 380 name = "atproto-record" 380 - version = "0.14.1" 381 + version = "0.14.2" 381 382 dependencies = [ 382 383 "anyhow", 383 384 "async-trait", ··· 398 399 399 400 [[package]] 400 401 name = "atproto-repo" 401 - version = "0.14.1" 402 + version = "0.14.2" 402 403 dependencies = [ 403 404 "anyhow", 404 405 "async-trait", ··· 421 422 422 423 [[package]] 423 424 name = "atproto-tap" 424 - version = "0.14.1" 425 + version = "0.14.2" 425 426 dependencies = [ 426 427 "atproto-client", 427 428 "atproto-identity", ··· 444 445 445 446 [[package]] 446 447 name = "atproto-xrpcs" 447 - version = "0.14.1" 448 + version = "0.14.2" 448 449 dependencies = [ 449 450 "anyhow", 450 451 "async-trait", ··· 470 471 471 472 [[package]] 472 473 name = "atproto-xrpcs-helloworld" 473 - version = "0.14.1" 474 + version = "0.14.2" 474 475 dependencies = [ 475 476 "anyhow", 476 477 "async-trait", ··· 497 498 498 499 [[package]] 499 500 name = "atpxrpc" 500 - version = "0.14.1" 501 + version = "0.14.2" 501 502 dependencies = [ 502 503 "anyhow", 503 504 "atproto-client", ··· 635 636 636 637 [[package]] 637 638 name = "bitflags" 638 - version = "2.10.0" 639 + version = "2.11.0" 639 640 source = "registry+https://github.com/rust-lang/crates.io-index" 640 - checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 641 + checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" 641 642 642 643 [[package]] 643 644 name = "blake3" ··· 664 665 665 666 [[package]] 666 667 name = "bumpalo" 667 - version = "3.19.1" 668 + version = "3.20.2" 668 669 source = "registry+https://github.com/rust-lang/crates.io-index" 669 - checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 670 + checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" 670 671 671 672 [[package]] 672 673 name = "bytes" 673 - version = "1.11.0" 674 + version = "1.11.1" 674 675 source = "registry+https://github.com/rust-lang/crates.io-index" 675 - checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 676 + checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 676 677 677 678 [[package]] 678 679 name = "cast" ··· 691 692 692 693 [[package]] 693 694 name = "cc" 694 - version = "1.2.54" 695 + version = "1.2.56" 695 696 source = "registry+https://github.com/rust-lang/crates.io-index" 696 - checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" 697 + checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" 697 698 dependencies = [ 698 699 "find-msvc-tools", 699 700 "jobserver", ··· 715 716 716 717 [[package]] 717 718 name = "chrono" 718 - version = "0.4.43" 719 + version = "0.4.44" 719 720 source = "registry+https://github.com/rust-lang/crates.io-index" 720 - checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" 721 + checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" 721 722 dependencies = [ 722 723 "num-traits", 723 724 "serde", ··· 764 765 765 766 [[package]] 766 767 name = "clap" 767 - version = "4.5.54" 768 + version = "4.5.60" 768 769 source = "registry+https://github.com/rust-lang/crates.io-index" 769 - checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" 770 + checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" 770 771 dependencies = [ 771 772 "clap_builder", 772 773 "clap_derive", ··· 774 775 775 776 [[package]] 776 777 name = "clap_builder" 777 - version = "4.5.54" 778 + version = "4.5.60" 778 779 source = "registry+https://github.com/rust-lang/crates.io-index" 779 - checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" 780 + checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" 780 781 dependencies = [ 781 782 "anstream", 782 783 "anstyle", ··· 786 787 787 788 [[package]] 788 789 name = "clap_derive" 789 - version = "4.5.49" 790 + version = "4.5.55" 790 791 source = "registry+https://github.com/rust-lang/crates.io-index" 791 - checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 792 + checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" 792 793 dependencies = [ 793 794 "heck", 794 795 "proc-macro2", ··· 798 799 799 800 [[package]] 800 801 name = "clap_lex" 801 - version = "0.7.7" 802 + version = "1.0.0" 802 803 source = "registry+https://github.com/rust-lang/crates.io-index" 803 - checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" 804 + checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" 804 805 805 806 [[package]] 806 807 name = "colorchoice" ··· 1170 1171 1171 1172 [[package]] 1172 1173 name = "find-msvc-tools" 1173 - version = "0.1.8" 1174 + version = "0.1.9" 1174 1175 source = "registry+https://github.com/rust-lang/crates.io-index" 1175 - checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" 1176 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 1176 1177 1177 1178 [[package]] 1178 1179 name = "fnv" ··· 1197 1198 1198 1199 [[package]] 1199 1200 name = "futures" 1200 - version = "0.3.31" 1201 + version = "0.3.32" 1201 1202 source = "registry+https://github.com/rust-lang/crates.io-index" 1202 - checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 1203 + checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" 1203 1204 dependencies = [ 1204 1205 "futures-channel", 1205 1206 "futures-core", ··· 1212 1213 1213 1214 [[package]] 1214 1215 name = "futures-channel" 1215 - version = "0.3.31" 1216 + version = "0.3.32" 1216 1217 source = "registry+https://github.com/rust-lang/crates.io-index" 1217 - checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 1218 + checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" 1218 1219 dependencies = [ 1219 1220 "futures-core", 1220 1221 "futures-sink", ··· 1222 1223 1223 1224 [[package]] 1224 1225 name = "futures-core" 1225 - version = "0.3.31" 1226 + version = "0.3.32" 1226 1227 source = "registry+https://github.com/rust-lang/crates.io-index" 1227 - checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 1228 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 1228 1229 1229 1230 [[package]] 1230 1231 name = "futures-executor" 1231 - version = "0.3.31" 1232 + version = "0.3.32" 1232 1233 source = "registry+https://github.com/rust-lang/crates.io-index" 1233 - checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 1234 + checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" 1234 1235 dependencies = [ 1235 1236 "futures-core", 1236 1237 "futures-task", ··· 1239 1240 1240 1241 [[package]] 1241 1242 name = "futures-io" 1242 - version = "0.3.31" 1243 + version = "0.3.32" 1243 1244 source = "registry+https://github.com/rust-lang/crates.io-index" 1244 - checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 1245 + checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" 1245 1246 1246 1247 [[package]] 1247 1248 name = "futures-macro" 1248 - version = "0.3.31" 1249 + version = "0.3.32" 1249 1250 source = "registry+https://github.com/rust-lang/crates.io-index" 1250 - checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1251 + checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" 1251 1252 dependencies = [ 1252 1253 "proc-macro2", 1253 1254 "quote", ··· 1256 1257 1257 1258 [[package]] 1258 1259 name = "futures-sink" 1259 - version = "0.3.31" 1260 + version = "0.3.32" 1260 1261 source = "registry+https://github.com/rust-lang/crates.io-index" 1261 - checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 1262 + checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" 1262 1263 1263 1264 [[package]] 1264 1265 name = "futures-task" 1265 - version = "0.3.31" 1266 + version = "0.3.32" 1266 1267 source = "registry+https://github.com/rust-lang/crates.io-index" 1267 - checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 1268 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 1268 1269 1269 1270 [[package]] 1270 1271 name = "futures-util" 1271 - version = "0.3.31" 1272 + version = "0.3.32" 1272 1273 source = "registry+https://github.com/rust-lang/crates.io-index" 1273 - checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 1274 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 1274 1275 dependencies = [ 1275 1276 "futures-channel", 1276 1277 "futures-core", ··· 1280 1281 "futures-task", 1281 1282 "memchr", 1282 1283 "pin-project-lite", 1283 - "pin-utils", 1284 1284 "slab", 1285 1285 ] 1286 1286 ··· 1317 1317 "cfg-if", 1318 1318 "js-sys", 1319 1319 "libc", 1320 - "r-efi", 1320 + "r-efi 5.3.0", 1321 1321 "wasip2", 1322 1322 "wasm-bindgen", 1323 + ] 1324 + 1325 + [[package]] 1326 + name = "getrandom" 1327 + version = "0.4.2" 1328 + source = "registry+https://github.com/rust-lang/crates.io-index" 1329 + checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" 1330 + dependencies = [ 1331 + "cfg-if", 1332 + "libc", 1333 + "r-efi 6.0.0", 1334 + "wasip2", 1335 + "wasip3", 1323 1336 ] 1324 1337 1325 1338 [[package]] ··· 1549 1562 1550 1563 [[package]] 1551 1564 name = "hyper-util" 1552 - version = "0.1.19" 1565 + version = "0.1.20" 1553 1566 source = "registry+https://github.com/rust-lang/crates.io-index" 1554 - checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 1567 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 1555 1568 dependencies = [ 1556 1569 "base64", 1557 1570 "bytes", 1558 1571 "futures-channel", 1559 - "futures-core", 1560 1572 "futures-util", 1561 1573 "http", 1562 1574 "http-body", ··· 1565 1577 "libc", 1566 1578 "percent-encoding", 1567 1579 "pin-project-lite", 1568 - "socket2 0.6.2", 1580 + "socket2 0.6.3", 1569 1581 "system-configuration", 1570 1582 "tokio", 1571 1583 "tower-service", ··· 1655 1667 ] 1656 1668 1657 1669 [[package]] 1670 + name = "id-arena" 1671 + version = "2.3.0" 1672 + source = "registry+https://github.com/rust-lang/crates.io-index" 1673 + checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" 1674 + 1675 + [[package]] 1658 1676 name = "idna" 1659 1677 version = "1.1.0" 1660 1678 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1701 1719 1702 1720 [[package]] 1703 1721 name = "ipnet" 1704 - version = "2.11.0" 1722 + version = "2.12.0" 1705 1723 source = "registry+https://github.com/rust-lang/crates.io-index" 1706 - checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1724 + checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" 1707 1725 1708 1726 [[package]] 1709 1727 name = "iri-string" ··· 1759 1777 1760 1778 [[package]] 1761 1779 name = "js-sys" 1762 - version = "0.3.85" 1780 + version = "0.3.91" 1763 1781 source = "registry+https://github.com/rust-lang/crates.io-index" 1764 - checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" 1782 + checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" 1765 1783 dependencies = [ 1766 1784 "once_cell", 1767 1785 "wasm-bindgen", ··· 1788 1806 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1789 1807 1790 1808 [[package]] 1809 + name = "leb128fmt" 1810 + version = "0.1.0" 1811 + source = "registry+https://github.com/rust-lang/crates.io-index" 1812 + checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 1813 + 1814 + [[package]] 1791 1815 name = "libc" 1792 - version = "0.2.180" 1816 + version = "0.2.183" 1793 1817 source = "registry+https://github.com/rust-lang/crates.io-index" 1794 - checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 1818 + checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" 1795 1819 1796 1820 [[package]] 1797 1821 name = "libredox" 1798 - version = "0.1.12" 1822 + version = "0.1.14" 1799 1823 source = "registry+https://github.com/rust-lang/crates.io-index" 1800 - checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" 1824 + checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" 1801 1825 dependencies = [ 1802 - "bitflags", 1803 1826 "libc", 1804 1827 ] 1805 1828 1806 1829 [[package]] 1807 1830 name = "linux-raw-sys" 1808 - version = "0.11.0" 1831 + version = "0.12.1" 1809 1832 source = "registry+https://github.com/rust-lang/crates.io-index" 1810 - checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1833 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 1811 1834 1812 1835 [[package]] 1813 1836 name = "litemap" ··· 1873 1896 1874 1897 [[package]] 1875 1898 name = "memchr" 1876 - version = "2.7.6" 1899 + version = "2.8.0" 1877 1900 source = "registry+https://github.com/rust-lang/crates.io-index" 1878 - checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1901 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 1879 1902 1880 1903 [[package]] 1881 1904 name = "mime" ··· 1906 1929 1907 1930 [[package]] 1908 1931 name = "moka" 1909 - version = "0.12.12" 1932 + version = "0.12.14" 1910 1933 source = "registry+https://github.com/rust-lang/crates.io-index" 1911 - checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" 1934 + checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" 1912 1935 dependencies = [ 1913 1936 "crossbeam-channel", 1914 1937 "crossbeam-epoch", ··· 2061 2084 2062 2085 [[package]] 2063 2086 name = "pin-project-lite" 2064 - version = "0.2.16" 2087 + version = "0.2.17" 2065 2088 source = "registry+https://github.com/rust-lang/crates.io-index" 2066 - checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 2089 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 2067 2090 2068 2091 [[package]] 2069 2092 name = "pin-utils" ··· 2117 2140 2118 2141 [[package]] 2119 2142 name = "portable-atomic" 2120 - version = "1.13.0" 2143 + version = "1.13.1" 2121 2144 source = "registry+https://github.com/rust-lang/crates.io-index" 2122 - checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" 2145 + checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" 2123 2146 2124 2147 [[package]] 2125 2148 name = "potential_utf" ··· 2140 2163 ] 2141 2164 2142 2165 [[package]] 2166 + name = "prettyplease" 2167 + version = "0.2.37" 2168 + source = "registry+https://github.com/rust-lang/crates.io-index" 2169 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 2170 + dependencies = [ 2171 + "proc-macro2", 2172 + "syn", 2173 + ] 2174 + 2175 + [[package]] 2143 2176 name = "primeorder" 2144 2177 version = "0.13.6" 2145 2178 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2196 2229 "quinn-udp", 2197 2230 "rustc-hash", 2198 2231 "rustls", 2199 - "socket2 0.6.2", 2232 + "socket2 0.6.3", 2200 2233 "thiserror 2.0.18", 2201 2234 "tokio", 2202 2235 "tracing", ··· 2205 2238 2206 2239 [[package]] 2207 2240 name = "quinn-proto" 2208 - version = "0.11.13" 2241 + version = "0.11.14" 2209 2242 source = "registry+https://github.com/rust-lang/crates.io-index" 2210 - checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 2243 + checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" 2211 2244 dependencies = [ 2212 2245 "bytes", 2213 2246 "getrandom 0.3.4", ··· 2233 2266 "cfg_aliases", 2234 2267 "libc", 2235 2268 "once_cell", 2236 - "socket2 0.6.2", 2269 + "socket2 0.6.3", 2237 2270 "tracing", 2238 2271 "windows-sys 0.60.2", 2239 2272 ] 2240 2273 2241 2274 [[package]] 2242 2275 name = "quote" 2243 - version = "1.0.44" 2276 + version = "1.0.45" 2244 2277 source = "registry+https://github.com/rust-lang/crates.io-index" 2245 - checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 2278 + checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 2246 2279 dependencies = [ 2247 2280 "proc-macro2", 2248 2281 ] ··· 2252 2285 version = "5.3.0" 2253 2286 source = "registry+https://github.com/rust-lang/crates.io-index" 2254 2287 checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 2288 + 2289 + [[package]] 2290 + name = "r-efi" 2291 + version = "6.0.0" 2292 + source = "registry+https://github.com/rust-lang/crates.io-index" 2293 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 2255 2294 2256 2295 [[package]] 2257 2296 name = "rand" ··· 2363 2402 2364 2403 [[package]] 2365 2404 name = "regex" 2366 - version = "1.12.2" 2405 + version = "1.12.3" 2367 2406 source = "registry+https://github.com/rust-lang/crates.io-index" 2368 - checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 2407 + checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" 2369 2408 dependencies = [ 2370 2409 "aho-corasick", 2371 2410 "memchr", ··· 2375 2414 2376 2415 [[package]] 2377 2416 name = "regex-automata" 2378 - version = "0.4.13" 2417 + version = "0.4.14" 2379 2418 source = "registry+https://github.com/rust-lang/crates.io-index" 2380 - checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 2419 + checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" 2381 2420 dependencies = [ 2382 2421 "aho-corasick", 2383 2422 "memchr", ··· 2386 2425 2387 2426 [[package]] 2388 2427 name = "regex-syntax" 2389 - version = "0.8.8" 2428 + version = "0.8.10" 2390 2429 source = "registry+https://github.com/rust-lang/crates.io-index" 2391 - checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 2430 + checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" 2392 2431 2393 2432 [[package]] 2394 2433 name = "reqwest" ··· 2519 2558 2520 2559 [[package]] 2521 2560 name = "rustix" 2522 - version = "1.1.3" 2561 + version = "1.1.4" 2523 2562 source = "registry+https://github.com/rust-lang/crates.io-index" 2524 - checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 2563 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 2525 2564 dependencies = [ 2526 2565 "bitflags", 2527 2566 "errno", ··· 2532 2571 2533 2572 [[package]] 2534 2573 name = "rustls" 2535 - version = "0.23.36" 2574 + version = "0.23.37" 2536 2575 source = "registry+https://github.com/rust-lang/crates.io-index" 2537 - checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" 2576 + checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" 2538 2577 dependencies = [ 2539 2578 "once_cell", 2540 2579 "ring", ··· 2597 2636 2598 2637 [[package]] 2599 2638 name = "ryu" 2600 - version = "1.0.22" 2639 + version = "1.0.23" 2601 2640 source = "registry+https://github.com/rust-lang/crates.io-index" 2602 - checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 2641 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 2603 2642 2604 2643 [[package]] 2605 2644 name = "same-file" ··· 2652 2691 2653 2692 [[package]] 2654 2693 name = "security-framework" 2655 - version = "3.5.1" 2694 + version = "3.7.0" 2656 2695 source = "registry+https://github.com/rust-lang/crates.io-index" 2657 - checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 2696 + checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" 2658 2697 dependencies = [ 2659 2698 "bitflags", 2660 2699 "core-foundation 0.10.1", ··· 2665 2704 2666 2705 [[package]] 2667 2706 name = "security-framework-sys" 2668 - version = "2.15.0" 2707 + version = "2.17.0" 2669 2708 source = "registry+https://github.com/rust-lang/crates.io-index" 2670 - checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 2709 + checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" 2671 2710 dependencies = [ 2672 2711 "core-foundation-sys", 2673 2712 "libc", 2674 2713 ] 2675 2714 2676 2715 [[package]] 2716 + name = "semver" 2717 + version = "1.0.27" 2718 + source = "registry+https://github.com/rust-lang/crates.io-index" 2719 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 2720 + 2721 + [[package]] 2677 2722 name = "serde" 2678 2723 version = "1.0.228" 2679 2724 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2814 2859 2815 2860 [[package]] 2816 2861 name = "slab" 2817 - version = "0.4.11" 2862 + version = "0.4.12" 2818 2863 source = "registry+https://github.com/rust-lang/crates.io-index" 2819 - checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 2864 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 2820 2865 2821 2866 [[package]] 2822 2867 name = "smallvec" ··· 2836 2881 2837 2882 [[package]] 2838 2883 name = "socket2" 2839 - version = "0.6.2" 2884 + version = "0.6.3" 2840 2885 source = "registry+https://github.com/rust-lang/crates.io-index" 2841 - checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" 2886 + checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" 2842 2887 dependencies = [ 2843 2888 "libc", 2844 - "windows-sys 0.60.2", 2889 + "windows-sys 0.61.2", 2845 2890 ] 2846 2891 2847 2892 [[package]] ··· 2880 2925 2881 2926 [[package]] 2882 2927 name = "syn" 2883 - version = "2.0.114" 2928 + version = "2.0.117" 2884 2929 source = "registry+https://github.com/rust-lang/crates.io-index" 2885 - checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 2930 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 2886 2931 dependencies = [ 2887 2932 "proc-macro2", 2888 2933 "quote", ··· 2911 2956 2912 2957 [[package]] 2913 2958 name = "system-configuration" 2914 - version = "0.6.1" 2959 + version = "0.7.0" 2915 2960 source = "registry+https://github.com/rust-lang/crates.io-index" 2916 - checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2961 + checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" 2917 2962 dependencies = [ 2918 2963 "bitflags", 2919 2964 "core-foundation 0.9.4", ··· 2938 2983 2939 2984 [[package]] 2940 2985 name = "tempfile" 2941 - version = "3.24.0" 2986 + version = "3.26.0" 2942 2987 source = "registry+https://github.com/rust-lang/crates.io-index" 2943 - checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 2988 + checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" 2944 2989 dependencies = [ 2945 2990 "fastrand", 2946 - "getrandom 0.3.4", 2991 + "getrandom 0.4.2", 2947 2992 "once_cell", 2948 2993 "rustix", 2949 2994 "windows-sys 0.61.2", ··· 3035 3080 3036 3081 [[package]] 3037 3082 name = "tokio" 3038 - version = "1.49.0" 3083 + version = "1.50.0" 3039 3084 source = "registry+https://github.com/rust-lang/crates.io-index" 3040 - checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 3085 + checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" 3041 3086 dependencies = [ 3042 3087 "bytes", 3043 3088 "libc", 3044 3089 "mio", 3045 3090 "pin-project-lite", 3046 3091 "signal-hook-registry", 3047 - "socket2 0.6.2", 3092 + "socket2 0.6.3", 3048 3093 "tokio-macros", 3049 3094 "windows-sys 0.61.2", 3050 3095 ] 3051 3096 3052 3097 [[package]] 3053 3098 name = "tokio-macros" 3054 - version = "2.6.0" 3099 + version = "2.6.1" 3055 3100 source = "registry+https://github.com/rust-lang/crates.io-index" 3056 - checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 3101 + checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" 3057 3102 dependencies = [ 3058 3103 "proc-macro2", 3059 3104 "quote", ··· 3260 3305 3261 3306 [[package]] 3262 3307 name = "unicode-ident" 3263 - version = "1.0.22" 3308 + version = "1.0.24" 3264 3309 source = "registry+https://github.com/rust-lang/crates.io-index" 3265 - checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 3310 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 3266 3311 3267 3312 [[package]] 3268 3313 name = "unicode-segmentation" ··· 3271 3316 checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 3272 3317 3273 3318 [[package]] 3319 + name = "unicode-xid" 3320 + version = "0.2.6" 3321 + source = "registry+https://github.com/rust-lang/crates.io-index" 3322 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 3323 + 3324 + [[package]] 3274 3325 name = "unsigned-varint" 3275 3326 version = "0.8.0" 3276 3327 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3314 3365 3315 3366 [[package]] 3316 3367 name = "uuid" 3317 - version = "1.20.0" 3368 + version = "1.22.0" 3318 3369 source = "registry+https://github.com/rust-lang/crates.io-index" 3319 - checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" 3370 + checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" 3320 3371 dependencies = [ 3321 - "getrandom 0.3.4", 3372 + "getrandom 0.4.2", 3322 3373 "js-sys", 3323 3374 "wasm-bindgen", 3324 3375 ] ··· 3379 3430 ] 3380 3431 3381 3432 [[package]] 3433 + name = "wasip3" 3434 + version = "0.4.0+wasi-0.3.0-rc-2026-01-06" 3435 + source = "registry+https://github.com/rust-lang/crates.io-index" 3436 + checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" 3437 + dependencies = [ 3438 + "wit-bindgen", 3439 + ] 3440 + 3441 + [[package]] 3382 3442 name = "wasm-bindgen" 3383 - version = "0.2.108" 3443 + version = "0.2.114" 3384 3444 source = "registry+https://github.com/rust-lang/crates.io-index" 3385 - checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" 3445 + checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" 3386 3446 dependencies = [ 3387 3447 "cfg-if", 3388 3448 "once_cell", ··· 3393 3453 3394 3454 [[package]] 3395 3455 name = "wasm-bindgen-futures" 3396 - version = "0.4.58" 3456 + version = "0.4.64" 3397 3457 source = "registry+https://github.com/rust-lang/crates.io-index" 3398 - checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" 3458 + checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" 3399 3459 dependencies = [ 3400 3460 "cfg-if", 3401 3461 "futures-util", ··· 3407 3467 3408 3468 [[package]] 3409 3469 name = "wasm-bindgen-macro" 3410 - version = "0.2.108" 3470 + version = "0.2.114" 3411 3471 source = "registry+https://github.com/rust-lang/crates.io-index" 3412 - checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" 3472 + checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" 3413 3473 dependencies = [ 3414 3474 "quote", 3415 3475 "wasm-bindgen-macro-support", ··· 3417 3477 3418 3478 [[package]] 3419 3479 name = "wasm-bindgen-macro-support" 3420 - version = "0.2.108" 3480 + version = "0.2.114" 3421 3481 source = "registry+https://github.com/rust-lang/crates.io-index" 3422 - checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" 3482 + checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" 3423 3483 dependencies = [ 3424 3484 "bumpalo", 3425 3485 "proc-macro2", ··· 3430 3490 3431 3491 [[package]] 3432 3492 name = "wasm-bindgen-shared" 3433 - version = "0.2.108" 3493 + version = "0.2.114" 3434 3494 source = "registry+https://github.com/rust-lang/crates.io-index" 3435 - checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" 3495 + checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" 3436 3496 dependencies = [ 3437 3497 "unicode-ident", 3438 3498 ] 3439 3499 3440 3500 [[package]] 3501 + name = "wasm-encoder" 3502 + version = "0.244.0" 3503 + source = "registry+https://github.com/rust-lang/crates.io-index" 3504 + checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" 3505 + dependencies = [ 3506 + "leb128fmt", 3507 + "wasmparser", 3508 + ] 3509 + 3510 + [[package]] 3511 + name = "wasm-metadata" 3512 + version = "0.244.0" 3513 + source = "registry+https://github.com/rust-lang/crates.io-index" 3514 + checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" 3515 + dependencies = [ 3516 + "anyhow", 3517 + "indexmap", 3518 + "wasm-encoder", 3519 + "wasmparser", 3520 + ] 3521 + 3522 + [[package]] 3523 + name = "wasmparser" 3524 + version = "0.244.0" 3525 + source = "registry+https://github.com/rust-lang/crates.io-index" 3526 + checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" 3527 + dependencies = [ 3528 + "bitflags", 3529 + "hashbrown 0.15.5", 3530 + "indexmap", 3531 + "semver", 3532 + ] 3533 + 3534 + [[package]] 3441 3535 name = "web-sys" 3442 - version = "0.3.85" 3536 + version = "0.3.91" 3443 3537 source = "registry+https://github.com/rust-lang/crates.io-index" 3444 - checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" 3538 + checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" 3445 3539 dependencies = [ 3446 3540 "js-sys", 3447 3541 "wasm-bindgen", ··· 3459 3553 3460 3554 [[package]] 3461 3555 name = "webpki-roots" 3462 - version = "1.0.5" 3556 + version = "1.0.6" 3463 3557 source = "registry+https://github.com/rust-lang/crates.io-index" 3464 - checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" 3558 + checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" 3465 3559 dependencies = [ 3466 3560 "rustls-pki-types", 3467 3561 ] ··· 3762 3856 version = "0.51.0" 3763 3857 source = "registry+https://github.com/rust-lang/crates.io-index" 3764 3858 checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 3859 + dependencies = [ 3860 + "wit-bindgen-rust-macro", 3861 + ] 3862 + 3863 + [[package]] 3864 + name = "wit-bindgen-core" 3865 + version = "0.51.0" 3866 + source = "registry+https://github.com/rust-lang/crates.io-index" 3867 + checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" 3868 + dependencies = [ 3869 + "anyhow", 3870 + "heck", 3871 + "wit-parser", 3872 + ] 3873 + 3874 + [[package]] 3875 + name = "wit-bindgen-rust" 3876 + version = "0.51.0" 3877 + source = "registry+https://github.com/rust-lang/crates.io-index" 3878 + checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" 3879 + dependencies = [ 3880 + "anyhow", 3881 + "heck", 3882 + "indexmap", 3883 + "prettyplease", 3884 + "syn", 3885 + "wasm-metadata", 3886 + "wit-bindgen-core", 3887 + "wit-component", 3888 + ] 3889 + 3890 + [[package]] 3891 + name = "wit-bindgen-rust-macro" 3892 + version = "0.51.0" 3893 + source = "registry+https://github.com/rust-lang/crates.io-index" 3894 + checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" 3895 + dependencies = [ 3896 + "anyhow", 3897 + "prettyplease", 3898 + "proc-macro2", 3899 + "quote", 3900 + "syn", 3901 + "wit-bindgen-core", 3902 + "wit-bindgen-rust", 3903 + ] 3904 + 3905 + [[package]] 3906 + name = "wit-component" 3907 + version = "0.244.0" 3908 + source = "registry+https://github.com/rust-lang/crates.io-index" 3909 + checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" 3910 + dependencies = [ 3911 + "anyhow", 3912 + "bitflags", 3913 + "indexmap", 3914 + "log", 3915 + "serde", 3916 + "serde_derive", 3917 + "serde_json", 3918 + "wasm-encoder", 3919 + "wasm-metadata", 3920 + "wasmparser", 3921 + "wit-parser", 3922 + ] 3923 + 3924 + [[package]] 3925 + name = "wit-parser" 3926 + version = "0.244.0" 3927 + source = "registry+https://github.com/rust-lang/crates.io-index" 3928 + checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" 3929 + dependencies = [ 3930 + "anyhow", 3931 + "id-arena", 3932 + "indexmap", 3933 + "log", 3934 + "semver", 3935 + "serde", 3936 + "serde_derive", 3937 + "serde_json", 3938 + "unicode-xid", 3939 + "wasmparser", 3940 + ] 3765 3941 3766 3942 [[package]] 3767 3943 name = "writeable" ··· 3794 3970 3795 3971 [[package]] 3796 3972 name = "zerocopy" 3797 - version = "0.8.33" 3973 + version = "0.8.42" 3798 3974 source = "registry+https://github.com/rust-lang/crates.io-index" 3799 - checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" 3975 + checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" 3800 3976 dependencies = [ 3801 3977 "zerocopy-derive", 3802 3978 ] 3803 3979 3804 3980 [[package]] 3805 3981 name = "zerocopy-derive" 3806 - version = "0.8.33" 3982 + version = "0.8.42" 3807 3983 source = "registry+https://github.com/rust-lang/crates.io-index" 3808 - checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" 3984 + checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" 3809 3985 dependencies = [ 3810 3986 "proc-macro2", 3811 3987 "quote", ··· 3888 4064 3889 4065 [[package]] 3890 4066 name = "zmij" 3891 - version = "1.0.17" 4067 + version = "1.0.21" 3892 4068 source = "registry+https://github.com/rust-lang/crates.io-index" 3893 - checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" 4069 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" 3894 4070 3895 4071 [[package]] 3896 4072 name = "zstd"
+15 -15
Cargo.toml
··· 33 33 categories = ["command-line-utilities", "web-programming"] 34 34 35 35 [workspace.dependencies] 36 - atproto-attestation = { version = "0.14.1", path = "crates/atproto-attestation" } 37 - atproto-client = { version = "0.14.1", path = "crates/atproto-client" } 38 - atproto-dasl = { version = "0.14.1", path = "crates/atproto-dasl" } 39 - atproto-extras = { version = "0.14.1", path = "crates/atproto-extras" } 40 - atproto-identity = { version = "0.14.1", path = "crates/atproto-identity" } 41 - atproto-jetstream = { version = "0.14.1", path = "crates/atproto-jetstream" } 42 - atproto-lexicon = { version = "0.14.1", path = "crates/atproto-lexicon" } 43 - atproto-oauth = { version = "0.14.1", path = "crates/atproto-oauth" } 44 - atproto-oauth-aip = { version = "0.14.1", path = "crates/atproto-oauth-aip" } 45 - atproto-oauth-axum = { version = "0.14.1", path = "crates/atproto-oauth-axum" } 46 - atproto-record = { version = "0.14.1", path = "crates/atproto-record" } 47 - atproto-repo = { version = "0.14.1", path = "crates/atproto-repo" } 48 - atproto-tap = { version = "0.14.1", path = "crates/atproto-tap" } 49 - atproto-xrpcs = { version = "0.14.1", path = "crates/atproto-xrpcs" } 36 + atproto-attestation = { version = "0.14.2", path = "crates/atproto-attestation" } 37 + atproto-client = { version = "0.14.2", path = "crates/atproto-client" } 38 + atproto-dasl = { version = "0.14.2", path = "crates/atproto-dasl" } 39 + atproto-extras = { version = "0.14.2", path = "crates/atproto-extras" } 40 + atproto-identity = { version = "0.14.2", path = "crates/atproto-identity" } 41 + atproto-jetstream = { version = "0.14.2", path = "crates/atproto-jetstream" } 42 + atproto-lexicon = { version = "0.14.2", path = "crates/atproto-lexicon" } 43 + atproto-oauth = { version = "0.14.2", path = "crates/atproto-oauth" } 44 + atproto-oauth-aip = { version = "0.14.2", path = "crates/atproto-oauth-aip" } 45 + atproto-oauth-axum = { version = "0.14.2", path = "crates/atproto-oauth-axum" } 46 + atproto-record = { version = "0.14.2", path = "crates/atproto-record" } 47 + atproto-repo = { version = "0.14.2", path = "crates/atproto-repo" } 48 + atproto-tap = { version = "0.14.2", path = "crates/atproto-tap" } 49 + atproto-xrpcs = { version = "0.14.2", path = "crates/atproto-xrpcs" } 50 50 51 - atpxrpc = { version = "0.14.1", path = "crates/atpxrpc" } 51 + atpxrpc = { version = "0.14.2", path = "crates/atpxrpc" } 52 52 53 53 bitflags = "2" 54 54 anyhow = "1.0"
+1 -1
Dockerfile
··· 73 73 LABEL org.opencontainers.image.description="AT Protocol identity management tools" 74 74 LABEL org.opencontainers.image.authors="Nick Gerakines <nick.gerakines@gmail.com>" 75 75 LABEL org.opencontainers.image.source="https://tangled.org/ngerakines.me/atproto-crates" 76 - LABEL org.opencontainers.image.version="0.14.1" 76 + LABEL org.opencontainers.image.version="0.14.2" 77 77 LABEL org.opencontainers.image.licenses="MIT" 78 78 79 79 # Document available binaries
+15 -13
README.md
··· 16 16 17 17 - **[`atproto-identity`](crates/atproto-identity/)** - Core identity management with multi-method DID resolution (plc, web, key), DNS/HTTP handle resolution, PLC directory operations, and P-256/P-384/K-256 cryptographic operations. *Includes 6 CLI tools.* 18 18 - **[`atproto-attestation`](crates/atproto-attestation/)** - CID-first attestation utilities for creating and verifying cryptographic signatures on AT Protocol records, supporting both inline and remote attestation workflows. *Includes 2 CLI tools.* 19 - - **[`atproto-record`](crates/atproto-record/)** - Record utilities including TID generation, AT-URI parsing, datetime formatting, and CID generation using IPLD DAG-CBOR serialization. *Includes 1 CLI tool.* 19 + - **[`atproto-record`](crates/atproto-record/)** - Record utilities including TID generation, AT-URI parsing, datetime formatting, and CID generation using IPLD DAG-CBOR serialization. *Includes 2 CLI tools.* 20 20 - **[`atproto-lexicon`](crates/atproto-lexicon/)** - Lexicon schema resolution and validation for AT Protocol, supporting recursive resolution, NSID validation, and DNS-based lexicon discovery. *Includes 1 CLI tool.* 21 21 22 22 ### Repository ··· 55 55 56 56 ```toml 57 57 [dependencies] 58 - atproto-dasl = "0.14.1" 59 - atproto-identity = "0.14.1" 60 - atproto-attestation = "0.14.1" 61 - atproto-record = "0.14.1" 62 - atproto-repo = "0.14.1" 63 - atproto-lexicon = "0.14.1" 64 - atproto-oauth = "0.14.1" 65 - atproto-oauth-aip = "0.14.1" 66 - atproto-client = "0.14.1" 67 - atproto-extras = "0.14.1" 68 - atproto-tap = "0.14.1" 58 + atproto-dasl = "0.14.2" 59 + atproto-identity = "0.14.2" 60 + atproto-attestation = "0.14.2" 61 + atproto-record = "0.14.2" 62 + atproto-repo = "0.14.2" 63 + atproto-lexicon = "0.14.2" 64 + atproto-oauth = "0.14.2" 65 + atproto-oauth-aip = "0.14.2" 66 + atproto-client = "0.14.2" 67 + atproto-extras = "0.14.2" 68 + atproto-tap = "0.14.2" 69 69 # Add others as needed 70 70 ``` 71 71 ··· 247 247 cargo build --features clap --bins 248 248 249 249 # DASL operations (atproto-dasl crate) 250 - cargo run --package atproto-dasl --features clap --bin atproto-dasl -- --help 250 + cargo run --package atproto-dasl --features clap --bin atpcid -- '{"text":"hello"}' 251 251 252 252 # Identity operations (atproto-identity crate) 253 253 cargo run --features clap,hickory-dns --bin atproto-identity-resolve -- alice.bsky.social ··· 263 263 264 264 # Record operations (atproto-record crate) 265 265 cat record.json | cargo run --features clap --bin atproto-record-cid 266 + cargo run --package atproto-record --features clap --bin atptid 267 + cargo run --package atproto-record --features clap --bin atptid -- -n 5 266 268 267 269 # Repository operations (atproto-repo crate) 268 270 cargo run --package atproto-repo --features clap --bin atproto-repo-car -- ls repo.car
+2 -1
crates/atpmcp/Cargo.toml
··· 1 1 [package] 2 2 name = "atpmcp" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol MCP server for DAG-CBOR CID generation" 5 5 homepage = "https://tangled.org/ngerakines.me/atproto-crates" 6 6 documentation = "https://docs.rs/atpmcp" ··· 27 27 atproto-lexicon.workspace = true 28 28 atproto-record.workspace = true 29 29 30 + dirs.workspace = true 30 31 anyhow.workspace = true 31 32 async-trait.workspace = true 32 33 reqwest.workspace = true
+21
crates/atpmcp/src/errors.rs
··· 49 49 /// Description of the retrieval failure. 50 50 reason: String, 51 51 }, 52 + 53 + /// Lexicon retrieval failed. 54 + #[error("error-atpmcp-tool-7 Lexicon retrieval failed: {reason}")] 55 + LexiconRetrievalFailed { 56 + /// Description of the retrieval failure. 57 + reason: String, 58 + }, 59 + 60 + /// XRPC request failed. 61 + #[error("error-atpmcp-tool-8 XRPC request failed: {reason}")] 62 + XrpcRequestFailed { 63 + /// Description of the request failure. 64 + reason: String, 65 + }, 66 + 67 + /// XRPC parameter validation failed. 68 + #[error("error-atpmcp-tool-9 XRPC validation failed: {reason}")] 69 + XrpcValidationFailed { 70 + /// Description of the validation failure. 71 + reason: String, 72 + }, 52 73 }
+834 -1
crates/atpmcp/src/main.rs
··· 20 20 //! - `resolve_identity` — Resolve a DID to its full DID document 21 21 //! - `parse_facets` — Parse rich text facets from plain text 22 22 //! - `get_record` — Retrieve an AT Protocol record by AT-URI 23 + //! - `get_lexicon` — Fetch a lexicon schema record by NSID 24 + //! - `validate_xrpc` — Validate XRPC parameters against a lexicon schema 25 + //! - `invoke_xrpc` — Make an XRPC request to an AT Protocol service 23 26 24 27 mod errors; 25 28 26 29 use std::io::{BufRead, BufReader, Write}; 27 30 use std::sync::Arc; 28 31 32 + use atproto_client::client::{ 33 + AppPasswordAuth, get_apppassword_json_with_headers, get_json_with_headers, 34 + post_apppassword_json_with_headers, post_json_with_headers, 35 + }; 36 + use atproto_client::com::atproto::server::{create_session, refresh_session}; 29 37 use atproto_identity::resolve::{HickoryDnsResolver, InnerIdentityResolver}; 38 + use atproto_identity::url::build_url; 30 39 use errors::ToolError; 40 + use reqwest::header::HeaderMap; 31 41 use serde::{Deserialize, Serialize}; 32 42 use serde_json::Value; 33 43 use tracing_subscriber::EnvFilter; ··· 230 240 "required": ["uri"], 231 241 "additionalProperties": false 232 242 } 243 + }, 244 + { 245 + "name": "get_lexicon", 246 + "description": "Fetch a lexicon schema record by NSID. Resolves the authority from the NSID via DNS if no repository is provided, then retrieves the lexicon from the repository's PDS.", 247 + "inputSchema": { 248 + "type": "object", 249 + "properties": { 250 + "nsid": { 251 + "type": "string", 252 + "description": "The NSID of the lexicon to fetch (e.g. 'app.bsky.feed.post')." 253 + }, 254 + "repo": { 255 + "type": "string", 256 + "description": "Optional repository DID or handle. If omitted, the authority is resolved from the NSID via DNS." 257 + } 258 + }, 259 + "required": ["nsid"], 260 + "additionalProperties": false 261 + } 262 + }, 263 + { 264 + "name": "validate_xrpc", 265 + "description": "Validate XRPC parameters and input body against a lexicon schema. Fetches the lexicon for the given NSID, determines whether it defines a query or procedure, and validates the provided params and/or body accordingly.", 266 + "inputSchema": { 267 + "type": "object", 268 + "properties": { 269 + "nsid": { 270 + "type": "string", 271 + "description": "The XRPC method NSID (e.g. 'com.atproto.repo.describeRepo')." 272 + }, 273 + "params": { 274 + "type": "object", 275 + "description": "Query or procedure parameters to validate." 276 + }, 277 + "body": { 278 + "type": "object", 279 + "description": "Procedure input body to validate." 280 + }, 281 + "repo": { 282 + "type": "string", 283 + "description": "Optional repository DID or handle for lexicon resolution. If omitted, the authority is resolved from the NSID via DNS." 284 + } 285 + }, 286 + "required": ["nsid"], 287 + "additionalProperties": false 288 + } 289 + }, 290 + { 291 + "name": "invoke_xrpc", 292 + "description": "Make an XRPC request to an AT Protocol service. Supports queries (GET) and procedures (POST). If a JSON body is provided, a POST is made; otherwise GET. Supports unauthenticated, authenticated (via stored atpxrpc credentials), and proxied requests (via the atproto-proxy header through the authenticated user's PDS).", 293 + "inputSchema": { 294 + "type": "object", 295 + "properties": { 296 + "nsid": { 297 + "type": "string", 298 + "description": "The XRPC method NSID (e.g. 'com.atproto.repo.describeRepo')." 299 + }, 300 + "params": { 301 + "type": "object", 302 + "description": "Query parameters as key-value string pairs for GET requests.", 303 + "additionalProperties": { "type": "string" } 304 + }, 305 + "body": { 306 + "type": "object", 307 + "description": "JSON body for POST (procedure) requests. If present, the request is a POST." 308 + }, 309 + "endpoint": { 310 + "type": "string", 311 + "description": "Explicit service endpoint URL (e.g. 'https://bsky.network'). Either endpoint, identity, or auth_handle is required." 312 + }, 313 + "identity": { 314 + "type": "string", 315 + "description": "Handle or DID to resolve for PDS endpoint discovery. Either endpoint, identity, or auth_handle is required." 316 + }, 317 + "auth_handle": { 318 + "type": "string", 319 + "description": "Handle of a stored atpxrpc account for authenticated requests. If omitted, the request is unauthenticated." 320 + }, 321 + "proxy": { 322 + "type": "string", 323 + "description": "Service audience for proxied requests in DID#serviceId format (e.g. 'did:web:api.bsky.app#bsky_fg'). Requires auth_handle. The request is sent to the authenticated user's PDS with the atproto-proxy header set to this value." 324 + } 325 + }, 326 + "required": ["nsid"], 327 + "additionalProperties": false 328 + } 329 + }, 330 + { 331 + "name": "generate_tid", 332 + "description": "Generate AT Protocol Timestamp Identifiers (TIDs). TIDs are 13-character base32-sortable strings used as record keys. Optionally generate multiple TIDs or generate from a specific microsecond timestamp.", 333 + "inputSchema": { 334 + "type": "object", 335 + "properties": { 336 + "count": { 337 + "type": "integer", 338 + "description": "Number of TIDs to generate (default 1, max 100)." 339 + }, 340 + "timestamp_micros": { 341 + "type": "integer", 342 + "description": "Specific microsecond UNIX timestamp to generate from. If omitted, uses current time." 343 + } 344 + }, 345 + "additionalProperties": false 346 + } 233 347 } 234 348 ] 235 349 }) ··· 266 380 JsonRpcResponse::success(id, handle_parse_facets(arguments, resolver).await) 267 381 } 268 382 "get_record" => JsonRpcResponse::success(id, handle_get_record(arguments, resolver).await), 383 + "get_lexicon" => { 384 + JsonRpcResponse::success(id, handle_get_lexicon(arguments, resolver).await) 385 + } 386 + "validate_xrpc" => { 387 + JsonRpcResponse::success(id, handle_validate_xrpc(arguments, resolver).await) 388 + } 389 + "invoke_xrpc" => { 390 + JsonRpcResponse::success(id, handle_invoke_xrpc(arguments, resolver).await) 391 + } 392 + "generate_tid" => JsonRpcResponse::success(id, handle_generate_tid(arguments)), 269 393 _ => JsonRpcResponse::error(id, METHOD_NOT_FOUND, format!("Unknown tool: {name}")), 270 394 } 271 395 } ··· 492 616 } 493 617 } 494 618 619 + /// Execute the `validate_xrpc` tool. 620 + async fn handle_validate_xrpc(arguments: Value, resolver: &InnerIdentityResolver) -> Value { 621 + let Some(nsid) = arguments.get("nsid").and_then(Value::as_str) else { 622 + return tool_error("The 'nsid' argument must be a string."); 623 + }; 624 + 625 + let params = arguments.get("params").cloned(); 626 + let body = arguments.get("body").cloned(); 627 + let repo = arguments.get("repo").and_then(Value::as_str); 628 + 629 + // Fetch the lexicon schema. 630 + let lexicon_value = match atproto_lexicon::resolve::get_lexicon( 631 + &resolver.http_client, 632 + resolver.dns_resolver.as_ref(), 633 + nsid, 634 + repo, 635 + ) 636 + .await 637 + { 638 + Ok(value) => value, 639 + Err(error) => { 640 + let tool_err = ToolError::XrpcValidationFailed { 641 + reason: format!("Failed to fetch lexicon for {nsid}: {error}"), 642 + }; 643 + tracing::error!(error = ?error, nsid = %nsid, "Lexicon retrieval failed for validation."); 644 + return tool_error(&tool_err.to_string()); 645 + } 646 + }; 647 + 648 + // Parse into a SchemaFile. 649 + let schema_file = 650 + match atproto_lexicon::validation::schema_file::SchemaFile::from_value(lexicon_value) { 651 + Ok(sf) => sf, 652 + Err(error) => { 653 + let tool_err = ToolError::XrpcValidationFailed { 654 + reason: format!("Invalid lexicon schema for {nsid}: {error}"), 655 + }; 656 + tracing::error!(error = ?error, nsid = %nsid, "Lexicon schema parse failed."); 657 + return tool_error(&tool_err.to_string()); 658 + } 659 + }; 660 + 661 + // Determine method type from the main definition. 662 + let main_def = match schema_file.defs.get("main") { 663 + Some(def) => def, 664 + None => { 665 + let tool_err = ToolError::XrpcValidationFailed { 666 + reason: format!("Lexicon {nsid} has no main definition"), 667 + }; 668 + return tool_error(&tool_err.to_string()); 669 + } 670 + }; 671 + 672 + let is_query = matches!(main_def, atproto_lexicon::validation::SchemaDef::Query(_)); 673 + let is_procedure = matches!( 674 + main_def, 675 + atproto_lexicon::validation::SchemaDef::Procedure(_) 676 + ); 677 + 678 + if !is_query && !is_procedure { 679 + let tool_err = ToolError::XrpcValidationFailed { 680 + reason: format!("Lexicon {nsid} main definition is not a query or procedure"), 681 + }; 682 + return tool_error(&tool_err.to_string()); 683 + } 684 + 685 + // Build a catalog with this schema. 686 + let mut catalog = atproto_lexicon::validation::BaseCatalog::new(); 687 + catalog.add_schema(schema_file); 688 + let flags = atproto_lexicon::validation::ValidateFlags::empty(); 689 + 690 + let mut validated = Vec::new(); 691 + let mut errors = Vec::new(); 692 + 693 + // Validate params if provided. 694 + if let Some(ref params_value) = params { 695 + let result = if is_query { 696 + atproto_lexicon::validation::validate_query_params(nsid, params_value, &catalog, flags) 697 + } else { 698 + atproto_lexicon::validation::validate_procedure_params( 699 + nsid, 700 + params_value, 701 + &catalog, 702 + flags, 703 + ) 704 + }; 705 + match result { 706 + Ok(()) => validated.push("params"), 707 + Err(error) => errors.push(format!("params: {error}")), 708 + } 709 + } 710 + 711 + // Validate body if provided (only for procedures). 712 + if let Some(ref body_value) = body { 713 + if is_query { 714 + errors.push("body: queries do not accept an input body".to_string()); 715 + } else { 716 + match atproto_lexicon::validation::validate_procedure_input( 717 + nsid, body_value, &catalog, flags, 718 + ) { 719 + Ok(()) => validated.push("body"), 720 + Err(error) => errors.push(format!("body: {error}")), 721 + } 722 + } 723 + } 724 + 725 + if !errors.is_empty() { 726 + let tool_err = ToolError::XrpcValidationFailed { 727 + reason: errors.join("; "), 728 + }; 729 + tracing::error!(nsid = %nsid, "XRPC validation failed."); 730 + return tool_error(&tool_err.to_string()); 731 + } 732 + 733 + let method_type = if is_query { "query" } else { "procedure" }; 734 + if validated.is_empty() { 735 + tool_success(&format!( 736 + "Lexicon {nsid} is a valid {method_type}. No params or body were provided to validate." 737 + )) 738 + } else { 739 + tool_success(&format!( 740 + "Validation passed for {method_type} {nsid}: {} validated successfully.", 741 + validated.join(" and ") 742 + )) 743 + } 744 + } 745 + 746 + /// Execute the `get_lexicon` tool. 747 + async fn handle_get_lexicon(arguments: Value, resolver: &InnerIdentityResolver) -> Value { 748 + let Some(nsid) = arguments.get("nsid").and_then(Value::as_str) else { 749 + return tool_error("The 'nsid' argument must be a string."); 750 + }; 751 + 752 + let repo = arguments.get("repo").and_then(Value::as_str); 753 + 754 + match atproto_lexicon::resolve::get_lexicon( 755 + &resolver.http_client, 756 + resolver.dns_resolver.as_ref(), 757 + nsid, 758 + repo, 759 + ) 760 + .await 761 + { 762 + Ok(value) => match serde_json::to_string(&value) { 763 + Ok(json) => tool_success(&json), 764 + Err(error) => { 765 + let tool_err = ToolError::LexiconRetrievalFailed { 766 + reason: error.to_string(), 767 + }; 768 + tracing::error!(error = ?error, "Failed to serialize lexicon."); 769 + tool_error(&tool_err.to_string()) 770 + } 771 + }, 772 + Err(error) => { 773 + let tool_err = ToolError::LexiconRetrievalFailed { 774 + reason: error.to_string(), 775 + }; 776 + tracing::error!(error = ?error, nsid = %nsid, "Lexicon retrieval failed."); 777 + tool_error(&tool_err.to_string()) 778 + } 779 + } 780 + } 781 + 782 + /// A stored atpxrpc account with session credentials. 783 + #[derive(Clone, Serialize, Deserialize)] 784 + struct XrpcAccount { 785 + /// The account handle. 786 + handle: String, 787 + /// The resolved DID. 788 + did: String, 789 + /// The PDS endpoint URL. 790 + pds_endpoint: String, 791 + /// The app password used for re-authentication. 792 + app_password: String, 793 + /// The current JWT access token. 794 + access_jwt: String, 795 + /// The current JWT refresh token. 796 + refresh_jwt: String, 797 + } 798 + 799 + /// Persistent atpxrpc configuration. 800 + #[derive(Serialize, Deserialize)] 801 + struct XrpcConfig { 802 + /// List of authenticated accounts. 803 + accounts: Vec<XrpcAccount>, 804 + } 805 + 806 + /// Returns the path to the atpxrpc config file. 807 + fn xrpc_config_path() -> anyhow::Result<std::path::PathBuf> { 808 + if let Ok(path) = std::env::var("ATPXRPC_CONFIG") { 809 + return Ok(std::path::PathBuf::from(path)); 810 + } 811 + let config_dir = dirs::config_dir().ok_or_else(|| { 812 + anyhow::anyhow!( 813 + "error-atpmcp-tool-8 XRPC request failed: could not determine config directory" 814 + ) 815 + })?; 816 + Ok(config_dir.join("atpxrpc").join("config.json")) 817 + } 818 + 819 + /// Loads the atpxrpc config from disk. 820 + fn load_xrpc_config() -> anyhow::Result<XrpcConfig> { 821 + let path = xrpc_config_path()?; 822 + 823 + let content = std::fs::read_to_string(&path).map_err(|error| { 824 + anyhow::anyhow!( 825 + "error-atpmcp-tool-8 XRPC request failed: could not read config file {}: {error}", 826 + path.display() 827 + ) 828 + })?; 829 + 830 + let config: XrpcConfig = serde_json::from_str(&content).map_err(|error| { 831 + anyhow::anyhow!( 832 + "error-atpmcp-tool-8 XRPC request failed: could not parse config file {}: {error}", 833 + path.display() 834 + ) 835 + })?; 836 + 837 + Ok(config) 838 + } 839 + 840 + /// Saves the atpxrpc config to disk. 841 + fn save_xrpc_config(config: &XrpcConfig) -> anyhow::Result<()> { 842 + let path = xrpc_config_path()?; 843 + let content = serde_json::to_string_pretty(config).map_err(|error| { 844 + anyhow::anyhow!( 845 + "error-atpmcp-tool-8 XRPC request failed: could not serialize config: {error}" 846 + ) 847 + })?; 848 + std::fs::write(&path, content).map_err(|error| { 849 + anyhow::anyhow!( 850 + "error-atpmcp-tool-8 XRPC request failed: could not write config file {}: {error}", 851 + path.display() 852 + ) 853 + })?; 854 + 855 + #[cfg(unix)] 856 + { 857 + use std::os::unix::fs::PermissionsExt; 858 + let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)); 859 + } 860 + 861 + Ok(()) 862 + } 863 + 864 + /// Updates a single account in the config file by matching on DID. 865 + fn update_xrpc_account(account: &XrpcAccount) -> anyhow::Result<()> { 866 + let mut config = load_xrpc_config()?; 867 + if let Some(existing) = config.accounts.iter_mut().find(|a| a.did == account.did) { 868 + *existing = account.clone(); 869 + } 870 + save_xrpc_config(&config)?; 871 + Ok(()) 872 + } 873 + 874 + /// Loads an atpxrpc account by handle, returning the account or a tool error. 875 + fn load_xrpc_account(handle: &str) -> Result<XrpcAccount, Value> { 876 + let config = load_xrpc_config().map_err(|error| { 877 + let tool_err = ToolError::XrpcRequestFailed { 878 + reason: error.to_string(), 879 + }; 880 + tracing::error!(error = ?error, "Failed to load atpxrpc config."); 881 + tool_error(&tool_err.to_string()) 882 + })?; 883 + 884 + let account = config 885 + .accounts 886 + .into_iter() 887 + .find(|a| a.handle == handle) 888 + .ok_or_else(|| { 889 + let tool_err = ToolError::XrpcRequestFailed { 890 + reason: format!("No account found for handle '{handle}'"), 891 + }; 892 + tool_error(&tool_err.to_string()) 893 + })?; 894 + 895 + Ok(account) 896 + } 897 + 898 + /// Checks if an XRPC JSON response indicates an expired token. 899 + fn is_expired_token_error(response: &serde_json::Value) -> bool { 900 + response 901 + .get("error") 902 + .and_then(|v| v.as_str()) 903 + .is_some_and(|e| e == "ExpiredToken") 904 + } 905 + 906 + /// Execute the `invoke_xrpc` tool. 907 + async fn handle_invoke_xrpc(arguments: Value, default_resolver: &InnerIdentityResolver) -> Value { 908 + let Some(nsid) = arguments.get("nsid").and_then(Value::as_str) else { 909 + return tool_error("The 'nsid' argument must be a string."); 910 + }; 911 + 912 + let endpoint = arguments.get("endpoint").and_then(Value::as_str); 913 + let identity = arguments.get("identity").and_then(Value::as_str); 914 + let auth_handle = arguments.get("auth_handle").and_then(Value::as_str); 915 + let proxy = arguments.get("proxy").and_then(Value::as_str); 916 + let body = arguments.get("body").cloned(); 917 + let params = arguments.get("params").and_then(Value::as_object); 918 + 919 + // Proxy requires auth_handle. 920 + if proxy.is_some() && auth_handle.is_none() { 921 + return tool_error("The 'proxy' parameter requires 'auth_handle' to be set."); 922 + } 923 + 924 + // Determine the service endpoint. 925 + let service_endpoint = if let Some(ep) = endpoint { 926 + ep.to_string() 927 + } else if let Some(id) = identity { 928 + match default_resolver.resolve(id).await { 929 + Ok(document) => { 930 + let pds_endpoints = document.pds_endpoints(); 931 + match pds_endpoints.first() { 932 + Some(ep) => ep.to_string(), 933 + None => { 934 + let tool_err = ToolError::XrpcRequestFailed { 935 + reason: "No PDS endpoint found in DID document".to_string(), 936 + }; 937 + tracing::error!(identity = %id, "No PDS endpoint found."); 938 + return tool_error(&tool_err.to_string()); 939 + } 940 + } 941 + } 942 + Err(error) => { 943 + let tool_err = ToolError::XrpcRequestFailed { 944 + reason: format!("Failed to resolve identity: {error}"), 945 + }; 946 + tracing::error!(error = ?error, identity = %id, "Identity resolution failed."); 947 + return tool_error(&tool_err.to_string()); 948 + } 949 + } 950 + } else if let Some(handle) = auth_handle { 951 + // Fall back to the PDS endpoint from the auth account. 952 + match load_xrpc_account(handle) { 953 + Ok(account) => account.pds_endpoint, 954 + Err(err) => return err, 955 + } 956 + } else { 957 + return tool_error("Either 'endpoint', 'identity', or 'auth_handle' must be provided."); 958 + }; 959 + 960 + // Build query parameters from the params object. 961 + let query_pairs: Vec<(String, String)> = params 962 + .map(|obj| { 963 + obj.iter() 964 + .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) 965 + .collect() 966 + }) 967 + .unwrap_or_default(); 968 + 969 + let is_procedure = body.is_some(); 970 + 971 + // Build the URL. 972 + let url = if is_procedure { 973 + match build_url( 974 + &service_endpoint, 975 + &format!("/xrpc/{nsid}"), 976 + std::iter::empty::<(&str, &str)>(), 977 + ) { 978 + Ok(u) => u.to_string(), 979 + Err(error) => { 980 + let tool_err = ToolError::XrpcRequestFailed { 981 + reason: format!("Failed to build URL: {error}"), 982 + }; 983 + return tool_error(&tool_err.to_string()); 984 + } 985 + } 986 + } else { 987 + match build_url( 988 + &service_endpoint, 989 + &format!("/xrpc/{nsid}"), 990 + query_pairs.iter().map(|(k, v)| (k.as_str(), v.as_str())), 991 + ) { 992 + Ok(u) => u.to_string(), 993 + Err(error) => { 994 + let tool_err = ToolError::XrpcRequestFailed { 995 + reason: format!("Failed to build URL: {error}"), 996 + }; 997 + return tool_error(&tool_err.to_string()); 998 + } 999 + } 1000 + }; 1001 + 1002 + // Build headers, including the atproto-proxy header for proxied requests. 1003 + let mut headers = HeaderMap::new(); 1004 + if let Some(audience) = proxy { 1005 + match reqwest::header::HeaderValue::from_str(audience) { 1006 + Ok(value) => { 1007 + headers.insert( 1008 + reqwest::header::HeaderName::from_static("atproto-proxy"), 1009 + value, 1010 + ); 1011 + } 1012 + Err(error) => { 1013 + let tool_err = ToolError::XrpcRequestFailed { 1014 + reason: format!("Invalid proxy audience header value: {error}"), 1015 + }; 1016 + return tool_error(&tool_err.to_string()); 1017 + } 1018 + } 1019 + } 1020 + 1021 + // Make the request. 1022 + let result = if let Some(handle) = auth_handle { 1023 + let mut account = match load_xrpc_account(handle) { 1024 + Ok(a) => a, 1025 + Err(err) => return err, 1026 + }; 1027 + 1028 + let app_auth = AppPasswordAuth { 1029 + access_token: account.access_jwt.clone(), 1030 + }; 1031 + 1032 + let first_result = if is_procedure { 1033 + let data = body.clone().unwrap_or(Value::Null); 1034 + post_apppassword_json_with_headers( 1035 + &default_resolver.http_client, 1036 + &app_auth, 1037 + &url, 1038 + data, 1039 + &headers, 1040 + ) 1041 + .await 1042 + } else { 1043 + get_apppassword_json_with_headers( 1044 + &default_resolver.http_client, 1045 + &app_auth, 1046 + &url, 1047 + &headers, 1048 + ) 1049 + .await 1050 + }; 1051 + 1052 + // Check for expired token and attempt refresh. 1053 + match &first_result { 1054 + Ok(response) if is_expired_token_error(response) => { 1055 + tracing::info!(handle = %handle, "Session expired, attempting refresh."); 1056 + 1057 + let refreshed = match refresh_session( 1058 + &default_resolver.http_client, 1059 + &account.pds_endpoint, 1060 + &account.refresh_jwt, 1061 + ) 1062 + .await 1063 + { 1064 + Ok(r) => { 1065 + account.access_jwt = r.access_jwt; 1066 + account.refresh_jwt = r.refresh_jwt; 1067 + true 1068 + } 1069 + Err(refresh_err) => { 1070 + tracing::warn!(error = ?refresh_err, "Refresh failed, re-authenticating."); 1071 + match create_session( 1072 + &default_resolver.http_client, 1073 + &account.pds_endpoint, 1074 + &account.handle, 1075 + &account.app_password, 1076 + None, 1077 + ) 1078 + .await 1079 + { 1080 + Ok(session) => { 1081 + account.access_jwt = session.access_jwt; 1082 + account.refresh_jwt = session.refresh_jwt; 1083 + true 1084 + } 1085 + Err(auth_err) => { 1086 + tracing::error!(error = ?auth_err, "Re-authentication failed."); 1087 + false 1088 + } 1089 + } 1090 + } 1091 + }; 1092 + 1093 + if refreshed { 1094 + if let Err(error) = update_xrpc_account(&account) { 1095 + tracing::warn!(error = ?error, "Failed to persist refreshed tokens."); 1096 + } 1097 + 1098 + let new_auth = AppPasswordAuth { 1099 + access_token: account.access_jwt.clone(), 1100 + }; 1101 + 1102 + if is_procedure { 1103 + let data = body.unwrap_or(Value::Null); 1104 + post_apppassword_json_with_headers( 1105 + &default_resolver.http_client, 1106 + &new_auth, 1107 + &url, 1108 + data, 1109 + &headers, 1110 + ) 1111 + .await 1112 + } else { 1113 + get_apppassword_json_with_headers( 1114 + &default_resolver.http_client, 1115 + &new_auth, 1116 + &url, 1117 + &headers, 1118 + ) 1119 + .await 1120 + } 1121 + } else { 1122 + first_result 1123 + } 1124 + } 1125 + _ => first_result, 1126 + } 1127 + } else if is_procedure { 1128 + let data = body.unwrap_or(Value::Null); 1129 + post_json_with_headers(&default_resolver.http_client, &url, data, &headers).await 1130 + } else { 1131 + get_json_with_headers(&default_resolver.http_client, &url, &headers).await 1132 + }; 1133 + 1134 + match result { 1135 + Ok(value) => match serde_json::to_string(&value) { 1136 + Ok(json) => tool_success(&json), 1137 + Err(error) => { 1138 + let tool_err = ToolError::XrpcRequestFailed { 1139 + reason: error.to_string(), 1140 + }; 1141 + tracing::error!(error = ?error, "Failed to serialize XRPC response."); 1142 + tool_error(&tool_err.to_string()) 1143 + } 1144 + }, 1145 + Err(error) => { 1146 + let tool_err = ToolError::XrpcRequestFailed { 1147 + reason: error.to_string(), 1148 + }; 1149 + tracing::error!(error = ?error, nsid = %nsid, "XRPC request failed."); 1150 + tool_error(&tool_err.to_string()) 1151 + } 1152 + } 1153 + } 1154 + 1155 + /// Execute the `generate_tid` tool. 1156 + fn handle_generate_tid(arguments: Value) -> Value { 1157 + let count = arguments.get("count").and_then(Value::as_u64).unwrap_or(1); 1158 + 1159 + if count == 0 || count > 100 { 1160 + return tool_error("The 'count' argument must be between 1 and 100."); 1161 + } 1162 + 1163 + let timestamp_micros = arguments.get("timestamp_micros").and_then(Value::as_u64); 1164 + 1165 + let tids: Vec<String> = (0..count) 1166 + .map(|_| { 1167 + let tid = match timestamp_micros { 1168 + Some(ts) => atproto_record::tid::Tid::new_with_time(ts), 1169 + None => atproto_record::tid::Tid::new(), 1170 + }; 1171 + tid.to_string() 1172 + }) 1173 + .collect(); 1174 + 1175 + if tids.len() == 1 { 1176 + tool_success(&tids[0]) 1177 + } else { 1178 + tool_success(&tids.join("\n")) 1179 + } 1180 + } 1181 + 495 1182 /// Build a successful tool call result. 496 1183 fn tool_success(text: &str) -> Value { 497 1184 serde_json::json!({ ··· 632 1319 fn test_handle_tools_list() { 633 1320 let result = handle_tools_list(); 634 1321 let tools = result["tools"].as_array().unwrap(); 635 - assert_eq!(tools.len(), 6); 1322 + assert_eq!(tools.len(), 10); 636 1323 assert_eq!(tools[0]["name"], "create_record_cid"); 637 1324 assert_eq!(tools[1]["name"], "validate_lexicon_schema"); 638 1325 assert_eq!(tools[2]["name"], "resolve_handle_to_did"); 639 1326 assert_eq!(tools[3]["name"], "resolve_identity"); 640 1327 assert_eq!(tools[4]["name"], "parse_facets"); 641 1328 assert_eq!(tools[5]["name"], "get_record"); 1329 + assert_eq!(tools[6]["name"], "get_lexicon"); 1330 + assert_eq!(tools[7]["name"], "validate_xrpc"); 1331 + assert_eq!(tools[8]["name"], "invoke_xrpc"); 1332 + assert_eq!(tools[9]["name"], "generate_tid"); 642 1333 for tool in tools { 643 1334 assert!(tool["inputSchema"].is_object()); 644 1335 } ··· 900 1591 assert!(text.contains("Invalid AT-URI")); 901 1592 } 902 1593 1594 + // -- get_lexicon tests -- 1595 + 1596 + #[tokio::test] 1597 + async fn test_get_lexicon_missing_nsid() { 1598 + let resolver = create_test_resolver(); 1599 + let args = serde_json::json!({}); 1600 + let result = handle_get_lexicon(args, &resolver).await; 1601 + assert_eq!(result["isError"], true); 1602 + } 1603 + 1604 + #[tokio::test] 1605 + async fn test_get_lexicon_non_string_nsid() { 1606 + let resolver = create_test_resolver(); 1607 + let args = serde_json::json!({ "nsid": 123 }); 1608 + let result = handle_get_lexicon(args, &resolver).await; 1609 + assert_eq!(result["isError"], true); 1610 + } 1611 + 1612 + #[tokio::test] 1613 + async fn test_get_lexicon_invalid_nsid() { 1614 + let resolver = create_test_resolver(); 1615 + let args = serde_json::json!({ "nsid": "invalid" }); 1616 + let result = handle_get_lexicon(args, &resolver).await; 1617 + assert_eq!(result["isError"], true); 1618 + let text = result["content"][0]["text"].as_str().unwrap(); 1619 + assert!(text.contains("Invalid NSID")); 1620 + } 1621 + 903 1622 // -- message routing tests -- 904 1623 905 1624 #[tokio::test] ··· 961 1680 }; 962 1681 let resp = route_message(msg, &resolver).await.unwrap(); 963 1682 assert!(resp.error.is_some()); 1683 + } 1684 + 1685 + // -- invoke_xrpc tests -- 1686 + 1687 + #[tokio::test] 1688 + async fn test_invoke_xrpc_missing_nsid() { 1689 + let resolver = create_test_resolver(); 1690 + let args = serde_json::json!({}); 1691 + let result = handle_invoke_xrpc(args, &resolver).await; 1692 + assert_eq!(result["isError"], true); 1693 + } 1694 + 1695 + #[tokio::test] 1696 + async fn test_invoke_xrpc_non_string_nsid() { 1697 + let resolver = create_test_resolver(); 1698 + let args = serde_json::json!({ "nsid": 123 }); 1699 + let result = handle_invoke_xrpc(args, &resolver).await; 1700 + assert_eq!(result["isError"], true); 1701 + } 1702 + 1703 + #[tokio::test] 1704 + async fn test_invoke_xrpc_missing_endpoint_and_identity() { 1705 + let resolver = create_test_resolver(); 1706 + let args = serde_json::json!({ "nsid": "com.atproto.repo.describeRepo" }); 1707 + let result = handle_invoke_xrpc(args, &resolver).await; 1708 + assert_eq!(result["isError"], true); 1709 + let text = result["content"][0]["text"].as_str().unwrap(); 1710 + assert!(text.contains("endpoint")); 1711 + } 1712 + 1713 + #[tokio::test] 1714 + async fn test_invoke_xrpc_proxy_requires_auth_handle() { 1715 + let resolver = create_test_resolver(); 1716 + let args = serde_json::json!({ 1717 + "nsid": "app.bsky.feed.getAuthorFeed", 1718 + "endpoint": "https://bsky.network", 1719 + "proxy": "did:web:api.bsky.app#bsky_fg" 1720 + }); 1721 + let result = handle_invoke_xrpc(args, &resolver).await; 1722 + assert_eq!(result["isError"], true); 1723 + let text = result["content"][0]["text"].as_str().unwrap(); 1724 + assert!(text.contains("proxy")); 1725 + assert!(text.contains("auth_handle")); 1726 + } 1727 + 1728 + // -- validate_xrpc tests -- 1729 + 1730 + #[test] 1731 + fn test_validate_xrpc_missing_nsid() { 1732 + let args = serde_json::json!({}); 1733 + let rt = tokio::runtime::Runtime::new().unwrap(); 1734 + let resolver = create_test_resolver(); 1735 + let result = rt.block_on(handle_validate_xrpc(args, &resolver)); 1736 + assert_eq!(result["isError"], true); 1737 + let text = result["content"][0]["text"].as_str().unwrap(); 1738 + assert!(text.contains("nsid")); 1739 + } 1740 + 1741 + #[test] 1742 + fn test_validate_xrpc_non_string_nsid() { 1743 + let args = serde_json::json!({"nsid": 123}); 1744 + let rt = tokio::runtime::Runtime::new().unwrap(); 1745 + let resolver = create_test_resolver(); 1746 + let result = rt.block_on(handle_validate_xrpc(args, &resolver)); 1747 + assert_eq!(result["isError"], true); 1748 + let text = result["content"][0]["text"].as_str().unwrap(); 1749 + assert!(text.contains("nsid")); 1750 + } 1751 + 1752 + // -- generate_tid tests -- 1753 + 1754 + #[test] 1755 + fn test_generate_tid_default() { 1756 + let args = serde_json::json!({}); 1757 + let result = handle_generate_tid(args); 1758 + assert_eq!(result["isError"], false); 1759 + let text = result["content"][0]["text"].as_str().unwrap(); 1760 + assert_eq!(text.len(), 13); 1761 + } 1762 + 1763 + #[test] 1764 + fn test_generate_tid_with_count() { 1765 + let args = serde_json::json!({ "count": 3 }); 1766 + let result = handle_generate_tid(args); 1767 + assert_eq!(result["isError"], false); 1768 + let text = result["content"][0]["text"].as_str().unwrap(); 1769 + let tids: Vec<&str> = text.lines().collect(); 1770 + assert_eq!(tids.len(), 3); 1771 + for tid in &tids { 1772 + assert_eq!(tid.len(), 13); 1773 + } 1774 + } 1775 + 1776 + #[test] 1777 + fn test_generate_tid_with_timestamp() { 1778 + let args = serde_json::json!({ "timestamp_micros": 1773067572000000_u64 }); 1779 + let result = handle_generate_tid(args); 1780 + assert_eq!(result["isError"], false); 1781 + let text = result["content"][0]["text"].as_str().unwrap(); 1782 + assert_eq!(text.len(), 13); 1783 + } 1784 + 1785 + #[test] 1786 + fn test_generate_tid_count_zero() { 1787 + let args = serde_json::json!({ "count": 0 }); 1788 + let result = handle_generate_tid(args); 1789 + assert_eq!(result["isError"], true); 1790 + } 1791 + 1792 + #[test] 1793 + fn test_generate_tid_count_over_max() { 1794 + let args = serde_json::json!({ "count": 101 }); 1795 + let result = handle_generate_tid(args); 1796 + assert_eq!(result["isError"], true); 964 1797 } 965 1798 }
+1 -1
crates/atproto-attestation/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-attestation" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol attestation utilities for creating and verifying record signatures" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-client/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-client" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "HTTP client for AT Protocol services with OAuth and identity integration" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-dasl/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-dasl" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "DASL (Data-Addressed Structures & Links) implementation for AT Protocol" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-extras/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-extras" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol extras - facet parsing and rich text utilities" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-identity/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-identity" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol identity management - DID resolution, handle resolution, and cryptographic operations" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-identity/src/bin/atproto-identity-plc-audit.rs
··· 487 487 let url = format!("{}/{}/log/audit", plc_url, did); 488 488 489 489 let client = reqwest::Client::builder() 490 - .user_agent("atproto-identity-plc-audit/0.14.1") 490 + .user_agent("atproto-identity-plc-audit/0.14.2") 491 491 .timeout(std::time::Duration::from_secs(30)) 492 492 .build()?; 493 493
+1 -1
crates/atproto-identity/src/bin/atproto-identity-plc-fork-viz.rs
··· 594 594 let url = format!("{}/{}/log/audit", plc_url, did); 595 595 596 596 let client = reqwest::Client::builder() 597 - .user_agent("atproto-identity-plc-fork-viz/0.14.1") 597 + .user_agent("atproto-identity-plc-fork-viz/0.14.2") 598 598 .timeout(std::time::Duration::from_secs(30)) 599 599 .build()?; 600 600
+1 -1
crates/atproto-jetstream/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-jetstream" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol Jetstream event consumer library with WebSocket streaming and compression support" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-lexicon/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-lexicon" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol lexicon resolution and validation" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-lexicon/README.md
··· 27 27 28 28 ```toml 29 29 [dependencies] 30 - atproto-lexicon = "0.14.1" 30 + atproto-lexicon = "0.14.2" 31 31 ``` 32 32 33 33 ## Usage
+7
crates/atproto-lexicon/src/errors.rs
··· 54 54 /// Error message from PDS 55 55 message: String, 56 56 }, 57 + 58 + /// Invalid NSID provided for resolution. 59 + #[error("error-atproto-lexicon-resolve-7 Invalid NSID: {nsid}")] 60 + InvalidNsid { 61 + /// The invalid NSID string 62 + nsid: String, 63 + }, 57 64 } 58 65 59 66 /// Errors that can occur during lexicon validation.
+40 -2
crates/atproto-lexicon/src/resolve.rs
··· 113 113 114 114 /// Get PDS endpoint from DID document. 115 115 #[instrument(skip(http_client), err)] 116 - async fn get_pds_from_did(http_client: &reqwest::Client, did: &str) -> Result<String> { 116 + pub async fn get_pds_from_did(http_client: &reqwest::Client, did: &str) -> Result<String> { 117 117 use atproto_identity::{ 118 118 model::Document, 119 119 plc, ··· 145 145 146 146 /// Fetch lexicon schema from PDS using XRPC. 147 147 #[instrument(skip(http_client), err)] 148 - async fn fetch_lexicon_from_pds( 148 + pub async fn fetch_lexicon_from_pds( 149 149 http_client: &reqwest::Client, 150 150 pds_endpoint: &str, 151 151 did: &str, ··· 188 188 } 189 189 } 190 190 } 191 + 192 + /// Fetch a lexicon schema record by NSID. 193 + /// 194 + /// If `repo` is provided, the lexicon is fetched from that repository's PDS. 195 + /// If `repo` is `None`, the authority is resolved from the NSID via DNS TXT 196 + /// lookup on the `_lexicon` prefixed domain. 197 + /// 198 + /// # Parameters 199 + /// - `http_client`: HTTP client for making requests 200 + /// - `dns_resolver`: DNS resolver for TXT lookups and handle resolution 201 + /// - `nsid`: The NSID of the lexicon to fetch (e.g., "app.bsky.feed.post") 202 + /// - `repo`: Optional repository DID or handle to fetch the lexicon from 203 + #[instrument(skip(http_client, dns_resolver), err)] 204 + pub async fn get_lexicon<R: DnsResolver + ?Sized>( 205 + http_client: &reqwest::Client, 206 + dns_resolver: &R, 207 + nsid: &str, 208 + repo: Option<&str>, 209 + ) -> Result<Value> { 210 + if !validation::is_valid_nsid(nsid) { 211 + return Err(LexiconResolveError::InvalidNsid { 212 + nsid: nsid.to_string(), 213 + } 214 + .into()); 215 + } 216 + 217 + let resolved_did = match repo { 218 + Some(subject) => resolve_subject(http_client, dns_resolver, subject).await?, 219 + None => { 220 + let dns_name = validation::nsid_to_dns_name(nsid)?; 221 + let did = resolve_lexicon_dns(dns_resolver, &dns_name).await?; 222 + resolve_subject(http_client, dns_resolver, &did).await? 223 + } 224 + }; 225 + 226 + let pds_endpoint = get_pds_from_did(http_client, &resolved_did).await?; 227 + fetch_lexicon_from_pds(http_client, &pds_endpoint, &resolved_did, nsid).await 228 + }
+1 -1
crates/atproto-oauth-aip/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-oauth-aip" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "ATProtocol AIP OAuth tools" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-oauth-axum/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-oauth-axum" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "Axum web framework integration for AT Protocol OAuth workflows" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-oauth/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-oauth" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "OAuth workflow implementation for AT Protocol - PKCE, DPoP, and secure authentication flows" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+8 -1
crates/atproto-record/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-record" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol record signature operations - cryptographic signing and verification for AT Protocol records" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates" ··· 16 16 17 17 [[bin]] 18 18 name = "atproto-record-cid" 19 + test = false 20 + bench = false 21 + doc = true 22 + required-features = ["clap"] 23 + 24 + [[bin]] 25 + name = "atptid" 19 26 test = false 20 27 bench = false 21 28 doc = true
+106
crates/atproto-record/src/bin/atptid.rs
··· 1 + //! Command-line tool for generating and parsing AT Protocol TIDs. 2 + //! 3 + //! TIDs (Timestamp Identifiers) are 13-character base32-sortable strings that encode 4 + //! a microsecond-precision timestamp with a random clock identifier for collision 5 + //! resistance. This tool supports generating new TIDs, batch generation, parsing 6 + //! existing TIDs, and creating TIDs from specific timestamps. 7 + //! 8 + //! # Example Usage 9 + //! 10 + //! ```bash 11 + //! # Generate a single TID 12 + //! cargo run --features clap --bin atptid 13 + //! 14 + //! # Generate 5 TIDs 15 + //! cargo run --features clap --bin atptid -- -n 5 16 + //! 17 + //! # Parse a TID and display its timestamp 18 + //! cargo run --features clap --bin atptid -- 3mgn37sytoc2c 19 + //! 20 + //! # Validate a TID quietly (exit code 0 = valid, 1 = invalid) 21 + //! cargo run --features clap --bin atptid -- -q 3mgn37sytoc2c 22 + //! 23 + //! # Generate a TID from a microsecond timestamp 24 + //! cargo run --features clap --bin atptid -- 1773067572 25 + //! ``` 26 + 27 + use std::process; 28 + 29 + use atproto_record::tid::Tid; 30 + use clap::Parser; 31 + 32 + /// AT Protocol TID Generator and Parser 33 + #[derive(Parser)] 34 + #[command( 35 + name = "atptid", 36 + version, 37 + about = "Generate and parse AT Protocol Timestamp Identifiers (TIDs)", 38 + long_about = " 39 + A command-line tool for generating and parsing AT Protocol TIDs (Timestamp 40 + Identifiers). TIDs are 13-character base32-sortable strings combining a 41 + microsecond timestamp with a random clock identifier. 42 + 43 + MODES: 44 + Generate: atptid Generate a single TID 45 + Batch: atptid -n 5 Generate multiple TIDs 46 + Parse: atptid <tid> Parse a TID and display its timestamp 47 + Validate: atptid -q <tid> Validate a TID (exit 1 if invalid) 48 + From time: atptid <timestamp> Generate a TID from microsecond timestamp 49 + 50 + EXAMPLES: 51 + atptid 52 + atptid -n 10 53 + atptid 3mgn37sytoc2c 54 + atptid -q 3mgn37sytoc2c 55 + atptid 1773067572000000 56 + " 57 + )] 58 + struct Args { 59 + /// TID to parse, or microsecond timestamp to generate a TID from 60 + input: Option<String>, 61 + 62 + /// Number of TIDs to generate 63 + #[arg(short = 'n', long)] 64 + count: Option<usize>, 65 + 66 + /// Quiet mode - validate without output, exit 1 if invalid 67 + #[arg(short, long)] 68 + quiet: bool, 69 + } 70 + 71 + fn main() { 72 + let args = Args::parse(); 73 + 74 + match args.input { 75 + None => { 76 + let count = args.count.unwrap_or(1); 77 + for _ in 0..count { 78 + println!("{}", Tid::new()); 79 + } 80 + } 81 + Some(input) => { 82 + if input.chars().all(|c| c.is_ascii_digit()) { 83 + let timestamp: u64 = input.parse().unwrap_or_else(|_| { 84 + eprintln!("error-atproto-record-cli-11 Invalid timestamp: {input}"); 85 + process::exit(1); 86 + }); 87 + let tid = Tid::new_with_time(timestamp); 88 + println!("{tid}"); 89 + } else { 90 + match Tid::decode(&input) { 91 + Ok(tid) => { 92 + if !args.quiet { 93 + println!("{}", tid.timestamp_micros()); 94 + } 95 + } 96 + Err(err) => { 97 + if !args.quiet { 98 + eprintln!("{err}"); 99 + } 100 + process::exit(1); 101 + } 102 + } 103 + } 104 + } 105 + } 106 + }
+1 -1
crates/atproto-repo/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-repo" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol repository handling - CAR v1 serialization and Merkle Search Tree operations" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-tap/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-tap" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol TAP (Trusted Attestation Protocol) service consumer" 5 5 readme = "README.md" 6 6 homepage = "https://tangled.org/ngerakines.me/atproto-crates"
+1 -1
crates/atproto-xrpcs-helloworld/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-xrpcs-helloworld" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "Complete example implementation of an AT Protocol XRPC service with DID web functionality and JWT authentication" 5 5 edition.workspace = true 6 6 rust-version.workspace = true
+1 -1
crates/atproto-xrpcs/Cargo.toml
··· 1 1 [package] 2 2 name = "atproto-xrpcs" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "Core building blocks for implementing AT Protocol XRPC services with JWT authorization" 5 5 edition.workspace = true 6 6 rust-version.workspace = true
+1 -1
crates/atpxrpc/Cargo.toml
··· 1 1 [package] 2 2 name = "atpxrpc" 3 - version = "0.14.1" 3 + version = "0.14.2" 4 4 description = "AT Protocol XRPC client with persistent session management" 5 5 6 6 edition.workspace = true