Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

Compare changes

Choose any two refs to compare.

-3
.gitmodules
··· 1 - [submodule "cli/jacquard"] 2 - path = cli/jacquard 3 - url = https://tangled.org/@nonbinary.computer/jacquard
···
+622 -72
cli/Cargo.lock
··· 139 140 [[package]] 141 name = "async-compression" 142 - version = "0.4.32" 143 source = "registry+https://github.com/rust-lang/crates.io-index" 144 - checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" 145 dependencies = [ 146 "compression-codecs", 147 "compression-core", ··· 158 dependencies = [ 159 "proc-macro2", 160 "quote", 161 - "syn 2.0.108", 162 ] 163 164 [[package]] ··· 174 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 175 176 [[package]] 177 name = "backtrace" 178 version = "0.3.76" 179 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 274 "proc-macro2", 275 "quote", 276 "rustversion", 277 - "syn 2.0.108", 278 ] 279 280 [[package]] ··· 348 checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 349 350 [[package]] 351 name = "bytes" 352 version = "1.10.1" 353 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 367 368 [[package]] 369 name = "cc" 370 - version = "1.2.44" 371 source = "registry+https://github.com/rust-lang/crates.io-index" 372 - checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" 373 dependencies = [ 374 "find-msvc-tools", 375 "shlex", ··· 494 "heck 0.5.0", 495 "proc-macro2", 496 "quote", 497 - "syn 2.0.108", 498 ] 499 500 [[package]] ··· 521 522 [[package]] 523 name = "compression-codecs" 524 - version = "0.4.31" 525 source = "registry+https://github.com/rust-lang/crates.io-index" 526 - checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" 527 dependencies = [ 528 "compression-core", 529 "flate2", ··· 532 533 [[package]] 534 name = "compression-core" 535 - version = "0.4.29" 536 source = "registry+https://github.com/rust-lang/crates.io-index" 537 - checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" 538 539 [[package]] 540 name = "const-oid" ··· 549 checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 550 551 [[package]] 552 name = "core-foundation" 553 version = "0.9.4" 554 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 665 "proc-macro2", 666 "quote", 667 "strsim", 668 - "syn 2.0.108", 669 ] 670 671 [[package]] ··· 676 dependencies = [ 677 "darling_core", 678 "quote", 679 - "syn 2.0.108", 680 ] 681 682 [[package]] ··· 716 checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 717 dependencies = [ 718 "data-encoding", 719 - "syn 2.0.108", 720 ] 721 722 [[package]] ··· 751 ] 752 753 [[package]] 754 name = "digest" 755 version = "0.10.7" 756 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 791 dependencies = [ 792 "proc-macro2", 793 "quote", 794 - "syn 2.0.108", 795 ] 796 797 [[package]] ··· 852 "heck 0.5.0", 853 "proc-macro2", 854 "quote", 855 - "syn 2.0.108", 856 ] 857 858 [[package]] ··· 956 ] 957 958 [[package]] 959 name = "futures-channel" 960 version = "0.3.31" 961 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 989 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 990 991 [[package]] 992 name = "futures-macro" 993 version = "0.3.31" 994 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 996 dependencies = [ 997 "proc-macro2", 998 "quote", 999 - "syn 2.0.108", 1000 ] 1001 1002 [[package]] ··· 1027 "pin-project-lite", 1028 "pin-utils", 1029 "slab", 1030 ] 1031 1032 [[package]] ··· 1236 "markup5ever", 1237 "proc-macro2", 1238 "quote", 1239 - "syn 2.0.108", 1240 ] 1241 1242 [[package]] ··· 1274 ] 1275 1276 [[package]] 1277 name = "httparse" 1278 version = "1.10.1" 1279 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1287 1288 [[package]] 1289 name = "hyper" 1290 - version = "1.7.0" 1291 source = "registry+https://github.com/rust-lang/crates.io-index" 1292 - checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" 1293 dependencies = [ 1294 "atomic-waker", 1295 "bytes", ··· 1299 "http", 1300 "http-body", 1301 "httparse", 1302 "itoa", 1303 "pin-project-lite", 1304 "pin-utils", ··· 1362 "js-sys", 1363 "log", 1364 "wasm-bindgen", 1365 - "windows-core", 1366 ] 1367 1368 [[package]] ··· 1554 1555 [[package]] 1556 name = "iri-string" 1557 - version = "0.7.8" 1558 source = "registry+https://github.com/rust-lang/crates.io-index" 1559 - checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" 1560 dependencies = [ 1561 "memchr", 1562 "serde", ··· 1583 [[package]] 1584 name = "jacquard" 1585 version = "0.9.0" 1586 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1587 dependencies = [ 1588 "bytes", 1589 "getrandom 0.2.16", ··· 1611 [[package]] 1612 name = "jacquard-api" 1613 version = "0.9.0" 1614 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1615 dependencies = [ 1616 "bon", 1617 "bytes", ··· 1629 [[package]] 1630 name = "jacquard-common" 1631 version = "0.9.0" 1632 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1633 dependencies = [ 1634 "base64 0.22.1", 1635 "bon", 1636 "bytes", 1637 "chrono", 1638 "cid", 1639 "getrandom 0.2.16", 1640 "getrandom 0.3.4", 1641 "http", ··· 1645 "miette", 1646 "multibase", 1647 "multihash", 1648 "ouroboros", 1649 "p256", 1650 "rand 0.9.2", ··· 1658 "smol_str", 1659 "thiserror 2.0.17", 1660 "tokio", 1661 "tokio-util", 1662 "trait-variant", 1663 "url", ··· 1666 [[package]] 1667 name = "jacquard-derive" 1668 version = "0.9.0" 1669 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1670 dependencies = [ 1671 "heck 0.5.0", 1672 "jacquard-lexicon", 1673 "proc-macro2", 1674 "quote", 1675 - "syn 2.0.108", 1676 ] 1677 1678 [[package]] 1679 name = "jacquard-identity" 1680 version = "0.9.1" 1681 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1682 dependencies = [ 1683 "bon", 1684 "bytes", ··· 1704 [[package]] 1705 name = "jacquard-lexicon" 1706 version = "0.9.1" 1707 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1708 dependencies = [ 1709 "cid", 1710 "dashmap", ··· 1722 "serde_repr", 1723 "serde_with", 1724 "sha2", 1725 - "syn 2.0.108", 1726 "thiserror 2.0.17", 1727 "unicode-segmentation", 1728 ] ··· 1730 [[package]] 1731 name = "jacquard-oauth" 1732 version = "0.9.0" 1733 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#b5cc9b35e38e24e1890ae55e700dcfad0d6d433a" 1734 dependencies = [ 1735 "base64 0.22.1", 1736 "bytes", ··· 1856 source = "registry+https://github.com/rust-lang/crates.io-index" 1857 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1858 dependencies = [ 1859 - "spin", 1860 ] 1861 1862 [[package]] ··· 1916 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 1917 1918 [[package]] 1919 name = "lru-cache" 1920 version = "0.1.2" 1921 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1974 ] 1975 1976 [[package]] 1977 name = "memchr" 1978 version = "2.7.6" 1979 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2006 dependencies = [ 2007 "proc-macro2", 2008 "quote", 2009 - "syn 2.0.108", 2010 ] 2011 2012 [[package]] ··· 2108 ] 2109 2110 [[package]] 2111 name = "ndk-context" 2112 version = "0.1.1" 2113 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2130 ] 2131 2132 [[package]] 2133 name = "num-bigint-dig" 2134 - version = "0.8.5" 2135 source = "registry+https://github.com/rust-lang/crates.io-index" 2136 - checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" 2137 dependencies = [ 2138 "lazy_static", 2139 "libm", ··· 2247 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 2248 2249 [[package]] 2250 name = "option-ext" 2251 version = "0.2.0" 2252 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2273 "proc-macro2", 2274 "proc-macro2-diagnostics", 2275 "quote", 2276 - "syn 2.0.108", 2277 ] 2278 2279 [[package]] ··· 2303 "elliptic-curve", 2304 "primeorder", 2305 ] 2306 2307 [[package]] 2308 name = "parking_lot" ··· 2381 ] 2382 2383 [[package]] 2384 name = "pin-project-lite" 2385 version = "0.2.16" 2386 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2450 checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 2451 dependencies = [ 2452 "proc-macro2", 2453 - "syn 2.0.108", 2454 ] 2455 2456 [[package]] ··· 2503 dependencies = [ 2504 "proc-macro2", 2505 "quote", 2506 - "syn 2.0.108", 2507 "version_check", 2508 "yansi", 2509 ] ··· 2571 2572 [[package]] 2573 name = "quote" 2574 - version = "1.0.41" 2575 source = "registry+https://github.com/rust-lang/crates.io-index" 2576 - checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 2577 dependencies = [ 2578 "proc-macro2", 2579 ] ··· 2686 dependencies = [ 2687 "proc-macro2", 2688 "quote", 2689 - "syn 2.0.108", 2690 ] 2691 2692 [[package]] ··· 2752 "tokio", 2753 "tokio-rustls", 2754 "tokio-util", 2755 - "tower", 2756 - "tower-http", 2757 "tower-service", 2758 "url", 2759 "wasm-bindgen", ··· 2864 2865 [[package]] 2866 name = "rustls" 2867 - version = "0.23.34" 2868 source = "registry+https://github.com/rust-lang/crates.io-index" 2869 - checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" 2870 dependencies = [ 2871 "once_cell", 2872 "ring", ··· 2874 "rustls-webpki", 2875 "subtle", 2876 "zeroize", 2877 ] 2878 2879 [[package]] ··· 2925 ] 2926 2927 [[package]] 2928 name = "schemars" 2929 version = "0.9.0" 2930 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2938 2939 [[package]] 2940 name = "schemars" 2941 - version = "1.0.4" 2942 source = "registry+https://github.com/rust-lang/crates.io-index" 2943 - checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" 2944 dependencies = [ 2945 "dyn-clone", 2946 "ref-cast", ··· 2949 ] 2950 2951 [[package]] 2952 name = "scopeguard" 2953 version = "1.2.0" 2954 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2967 "subtle", 2968 "zeroize", 2969 ] 2970 2971 [[package]] 2972 name = "serde" ··· 3005 dependencies = [ 3006 "proc-macro2", 3007 "quote", 3008 - "syn 2.0.108", 3009 ] 3010 3011 [[package]] ··· 3047 ] 3048 3049 [[package]] 3050 name = "serde_repr" 3051 version = "0.1.20" 3052 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3054 dependencies = [ 3055 "proc-macro2", 3056 "quote", 3057 - "syn 2.0.108", 3058 ] 3059 3060 [[package]] ··· 3081 "indexmap 1.9.3", 3082 "indexmap 2.12.0", 3083 "schemars 0.9.0", 3084 - "schemars 1.0.4", 3085 "serde_core", 3086 "serde_json", 3087 "serde_with_macros", ··· 3097 "darling", 3098 "proc-macro2", 3099 "quote", 3100 - "syn 2.0.108", 3101 ] 3102 3103 [[package]] ··· 3115 "cfg-if", 3116 "cpufeatures", 3117 "digest", 3118 ] 3119 3120 [[package]] ··· 3212 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 3213 3214 [[package]] 3215 name = "spki" 3216 version = "0.7.3" 3217 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3243 "quote", 3244 "serde", 3245 "sha2", 3246 - "syn 2.0.108", 3247 "thiserror 1.0.69", 3248 ] 3249 ··· 3324 3325 [[package]] 3326 name = "syn" 3327 - version = "2.0.108" 3328 source = "registry+https://github.com/rust-lang/crates.io-index" 3329 - checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" 3330 dependencies = [ 3331 "proc-macro2", 3332 "quote", ··· 3350 dependencies = [ 3351 "proc-macro2", 3352 "quote", 3353 - "syn 2.0.108", 3354 ] 3355 3356 [[package]] ··· 3450 dependencies = [ 3451 "proc-macro2", 3452 "quote", 3453 - "syn 2.0.108", 3454 ] 3455 3456 [[package]] ··· 3461 dependencies = [ 3462 "proc-macro2", 3463 "quote", 3464 - "syn 2.0.108", 3465 ] 3466 3467 [[package]] ··· 3568 dependencies = [ 3569 "proc-macro2", 3570 "quote", 3571 - "syn 2.0.108", 3572 ] 3573 3574 [[package]] ··· 3582 ] 3583 3584 [[package]] 3585 name = "tokio-util" 3586 - version = "0.7.16" 3587 source = "registry+https://github.com/rust-lang/crates.io-index" 3588 - checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 3589 dependencies = [ 3590 "bytes", 3591 "futures-core", 3592 "futures-sink", 3593 "pin-project-lite", 3594 "tokio", 3595 ] 3596 3597 [[package]] 3598 name = "tower" 3599 version = "0.5.2" 3600 source = "registry+https://github.com/rust-lang/crates.io-index" 3601 checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" ··· 3607 "tokio", 3608 "tower-layer", 3609 "tower-service", 3610 ] 3611 3612 [[package]] ··· 3622 "http-body", 3623 "iri-string", 3624 "pin-project-lite", 3625 - "tower", 3626 "tower-layer", 3627 "tower-service", 3628 ] ··· 3645 source = "registry+https://github.com/rust-lang/crates.io-index" 3646 checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 3647 dependencies = [ 3648 "pin-project-lite", 3649 "tracing-attributes", 3650 "tracing-core", ··· 3658 dependencies = [ 3659 "proc-macro2", 3660 "quote", 3661 - "syn 2.0.108", 3662 ] 3663 3664 [[package]] ··· 3668 checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 3669 dependencies = [ 3670 "once_cell", 3671 ] 3672 3673 [[package]] ··· 3678 dependencies = [ 3679 "proc-macro2", 3680 "quote", 3681 - "syn 2.0.108", 3682 ] 3683 3684 [[package]] ··· 3694 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3695 3696 [[package]] 3697 name = "twoway" 3698 version = "0.1.8" 3699 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3745 checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 3746 3747 [[package]] 3748 name = "unsigned-varint" 3749 version = "0.8.0" 3750 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3791 version = "0.2.2" 3792 source = "registry+https://github.com/rust-lang/crates.io-index" 3793 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 3794 3795 [[package]] 3796 name = "version_check" ··· 3877 "bumpalo", 3878 "proc-macro2", 3879 "quote", 3880 - "syn 2.0.108", 3881 "wasm-bindgen-shared", 3882 ] 3883 ··· 3976 ] 3977 3978 [[package]] 3979 name = "windows-core" 3980 version = "0.62.2" 3981 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3989 ] 3990 3991 [[package]] 3992 name = "windows-implement" 3993 version = "0.60.2" 3994 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3996 dependencies = [ 3997 "proc-macro2", 3998 "quote", 3999 - "syn 2.0.108", 4000 ] 4001 4002 [[package]] ··· 4007 dependencies = [ 4008 "proc-macro2", 4009 "quote", 4010 - "syn 2.0.108", 4011 ] 4012 4013 [[package]] ··· 4021 version = "0.2.1" 4022 source = "registry+https://github.com/rust-lang/crates.io-index" 4023 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4024 4025 [[package]] 4026 name = "windows-registry" ··· 4178 ] 4179 4180 [[package]] 4181 name = "windows_aarch64_gnullvm" 4182 version = "0.42.2" 4183 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4369 4370 [[package]] 4371 name = "wisp-cli" 4372 - version = "0.1.0" 4373 dependencies = [ 4374 "base64 0.22.1", 4375 "bytes", 4376 "clap", 4377 "flate2", 4378 "futures", ··· 4387 "mime_guess", 4388 "multibase", 4389 "multihash", 4390 "reqwest", 4391 "rustversion", 4392 "serde", ··· 4394 "sha2", 4395 "shellexpand", 4396 "tokio", 4397 "walkdir", 4398 ] 4399 ··· 4445 dependencies = [ 4446 "proc-macro2", 4447 "quote", 4448 - "syn 2.0.108", 4449 "synstructure", 4450 ] 4451 ··· 4466 dependencies = [ 4467 "proc-macro2", 4468 "quote", 4469 - "syn 2.0.108", 4470 ] 4471 4472 [[package]] ··· 4486 dependencies = [ 4487 "proc-macro2", 4488 "quote", 4489 - "syn 2.0.108", 4490 "synstructure", 4491 ] 4492 ··· 4529 dependencies = [ 4530 "proc-macro2", 4531 "quote", 4532 - "syn 2.0.108", 4533 ]
··· 139 140 [[package]] 141 name = "async-compression" 142 + version = "0.4.33" 143 source = "registry+https://github.com/rust-lang/crates.io-index" 144 + checksum = "93c1f86859c1af3d514fa19e8323147ff10ea98684e6c7b307912509f50e67b2" 145 dependencies = [ 146 "compression-codecs", 147 "compression-core", ··· 158 dependencies = [ 159 "proc-macro2", 160 "quote", 161 + "syn 2.0.110", 162 ] 163 164 [[package]] ··· 174 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 175 176 [[package]] 177 + name = "axum" 178 + version = "0.7.9" 179 + source = "registry+https://github.com/rust-lang/crates.io-index" 180 + checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" 181 + dependencies = [ 182 + "async-trait", 183 + "axum-core", 184 + "bytes", 185 + "futures-util", 186 + "http", 187 + "http-body", 188 + "http-body-util", 189 + "hyper", 190 + "hyper-util", 191 + "itoa", 192 + "matchit", 193 + "memchr", 194 + "mime", 195 + "percent-encoding", 196 + "pin-project-lite", 197 + "rustversion", 198 + "serde", 199 + "serde_json", 200 + "serde_path_to_error", 201 + "serde_urlencoded", 202 + "sync_wrapper", 203 + "tokio", 204 + "tower 0.5.2", 205 + "tower-layer", 206 + "tower-service", 207 + "tracing", 208 + ] 209 + 210 + [[package]] 211 + name = "axum-core" 212 + version = "0.4.5" 213 + source = "registry+https://github.com/rust-lang/crates.io-index" 214 + checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" 215 + dependencies = [ 216 + "async-trait", 217 + "bytes", 218 + "futures-util", 219 + "http", 220 + "http-body", 221 + "http-body-util", 222 + "mime", 223 + "pin-project-lite", 224 + "rustversion", 225 + "sync_wrapper", 226 + "tower-layer", 227 + "tower-service", 228 + "tracing", 229 + ] 230 + 231 + [[package]] 232 name = "backtrace" 233 version = "0.3.76" 234 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 329 "proc-macro2", 330 "quote", 331 "rustversion", 332 + "syn 2.0.110", 333 ] 334 335 [[package]] ··· 403 checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 404 405 [[package]] 406 + name = "byteorder" 407 + version = "1.5.0" 408 + source = "registry+https://github.com/rust-lang/crates.io-index" 409 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 410 + 411 + [[package]] 412 name = "bytes" 413 version = "1.10.1" 414 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 428 429 [[package]] 430 name = "cc" 431 + version = "1.2.45" 432 source = "registry+https://github.com/rust-lang/crates.io-index" 433 + checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" 434 dependencies = [ 435 "find-msvc-tools", 436 "shlex", ··· 555 "heck 0.5.0", 556 "proc-macro2", 557 "quote", 558 + "syn 2.0.110", 559 ] 560 561 [[package]] ··· 582 583 [[package]] 584 name = "compression-codecs" 585 + version = "0.4.32" 586 source = "registry+https://github.com/rust-lang/crates.io-index" 587 + checksum = "680dc087785c5230f8e8843e2e57ac7c1c90488b6a91b88caa265410568f441b" 588 dependencies = [ 589 "compression-core", 590 "flate2", ··· 593 594 [[package]] 595 name = "compression-core" 596 + version = "0.4.30" 597 source = "registry+https://github.com/rust-lang/crates.io-index" 598 + checksum = "3a9b614a5787ef0c8802a55766480563cb3a93b435898c422ed2a359cf811582" 599 600 [[package]] 601 name = "const-oid" ··· 610 checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 611 612 [[package]] 613 + name = "cordyceps" 614 + version = "0.3.4" 615 + source = "registry+https://github.com/rust-lang/crates.io-index" 616 + checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" 617 + dependencies = [ 618 + "loom", 619 + "tracing", 620 + ] 621 + 622 + [[package]] 623 name = "core-foundation" 624 version = "0.9.4" 625 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 736 "proc-macro2", 737 "quote", 738 "strsim", 739 + "syn 2.0.110", 740 ] 741 742 [[package]] ··· 747 dependencies = [ 748 "darling_core", 749 "quote", 750 + "syn 2.0.110", 751 ] 752 753 [[package]] ··· 787 checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 788 dependencies = [ 789 "data-encoding", 790 + "syn 2.0.110", 791 ] 792 793 [[package]] ··· 822 ] 823 824 [[package]] 825 + name = "derive_more" 826 + version = "1.0.0" 827 + source = "registry+https://github.com/rust-lang/crates.io-index" 828 + checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" 829 + dependencies = [ 830 + "derive_more-impl", 831 + ] 832 + 833 + [[package]] 834 + name = "derive_more-impl" 835 + version = "1.0.0" 836 + source = "registry+https://github.com/rust-lang/crates.io-index" 837 + checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" 838 + dependencies = [ 839 + "proc-macro2", 840 + "quote", 841 + "syn 2.0.110", 842 + "unicode-xid", 843 + ] 844 + 845 + [[package]] 846 + name = "diatomic-waker" 847 + version = "0.2.3" 848 + source = "registry+https://github.com/rust-lang/crates.io-index" 849 + checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 850 + 851 + [[package]] 852 name = "digest" 853 version = "0.10.7" 854 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 889 dependencies = [ 890 "proc-macro2", 891 "quote", 892 + "syn 2.0.110", 893 ] 894 895 [[package]] ··· 950 "heck 0.5.0", 951 "proc-macro2", 952 "quote", 953 + "syn 2.0.110", 954 ] 955 956 [[package]] ··· 1054 ] 1055 1056 [[package]] 1057 + name = "futures-buffered" 1058 + version = "0.2.12" 1059 + source = "registry+https://github.com/rust-lang/crates.io-index" 1060 + checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" 1061 + dependencies = [ 1062 + "cordyceps", 1063 + "diatomic-waker", 1064 + "futures-core", 1065 + "pin-project-lite", 1066 + "spin 0.10.0", 1067 + ] 1068 + 1069 + [[package]] 1070 name = "futures-channel" 1071 version = "0.3.31" 1072 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1100 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 1101 1102 [[package]] 1103 + name = "futures-lite" 1104 + version = "2.6.1" 1105 + source = "registry+https://github.com/rust-lang/crates.io-index" 1106 + checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" 1107 + dependencies = [ 1108 + "fastrand", 1109 + "futures-core", 1110 + "futures-io", 1111 + "parking", 1112 + "pin-project-lite", 1113 + ] 1114 + 1115 + [[package]] 1116 name = "futures-macro" 1117 version = "0.3.31" 1118 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1120 dependencies = [ 1121 "proc-macro2", 1122 "quote", 1123 + "syn 2.0.110", 1124 ] 1125 1126 [[package]] ··· 1151 "pin-project-lite", 1152 "pin-utils", 1153 "slab", 1154 + ] 1155 + 1156 + [[package]] 1157 + name = "generator" 1158 + version = "0.8.7" 1159 + source = "registry+https://github.com/rust-lang/crates.io-index" 1160 + checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" 1161 + dependencies = [ 1162 + "cc", 1163 + "cfg-if", 1164 + "libc", 1165 + "log", 1166 + "rustversion", 1167 + "windows", 1168 ] 1169 1170 [[package]] ··· 1374 "markup5ever", 1375 "proc-macro2", 1376 "quote", 1377 + "syn 2.0.110", 1378 ] 1379 1380 [[package]] ··· 1412 ] 1413 1414 [[package]] 1415 + name = "http-range-header" 1416 + version = "0.4.2" 1417 + source = "registry+https://github.com/rust-lang/crates.io-index" 1418 + checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" 1419 + 1420 + [[package]] 1421 name = "httparse" 1422 version = "1.10.1" 1423 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1431 1432 [[package]] 1433 name = "hyper" 1434 + version = "1.8.0" 1435 source = "registry+https://github.com/rust-lang/crates.io-index" 1436 + checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" 1437 dependencies = [ 1438 "atomic-waker", 1439 "bytes", ··· 1443 "http", 1444 "http-body", 1445 "httparse", 1446 + "httpdate", 1447 "itoa", 1448 "pin-project-lite", 1449 "pin-utils", ··· 1507 "js-sys", 1508 "log", 1509 "wasm-bindgen", 1510 + "windows-core 0.62.2", 1511 ] 1512 1513 [[package]] ··· 1699 1700 [[package]] 1701 name = "iri-string" 1702 + version = "0.7.9" 1703 source = "registry+https://github.com/rust-lang/crates.io-index" 1704 + checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1705 dependencies = [ 1706 "memchr", 1707 "serde", ··· 1728 [[package]] 1729 name = "jacquard" 1730 version = "0.9.0" 1731 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1732 dependencies = [ 1733 "bytes", 1734 "getrandom 0.2.16", ··· 1756 [[package]] 1757 name = "jacquard-api" 1758 version = "0.9.0" 1759 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1760 dependencies = [ 1761 "bon", 1762 "bytes", ··· 1774 [[package]] 1775 name = "jacquard-common" 1776 version = "0.9.0" 1777 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1778 dependencies = [ 1779 "base64 0.22.1", 1780 "bon", 1781 "bytes", 1782 "chrono", 1783 + "ciborium", 1784 "cid", 1785 + "futures", 1786 "getrandom 0.2.16", 1787 "getrandom 0.3.4", 1788 "http", ··· 1792 "miette", 1793 "multibase", 1794 "multihash", 1795 + "n0-future", 1796 "ouroboros", 1797 "p256", 1798 "rand 0.9.2", ··· 1806 "smol_str", 1807 "thiserror 2.0.17", 1808 "tokio", 1809 + "tokio-tungstenite-wasm", 1810 "tokio-util", 1811 "trait-variant", 1812 "url", ··· 1815 [[package]] 1816 name = "jacquard-derive" 1817 version = "0.9.0" 1818 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1819 dependencies = [ 1820 "heck 0.5.0", 1821 "jacquard-lexicon", 1822 "proc-macro2", 1823 "quote", 1824 + "syn 2.0.110", 1825 ] 1826 1827 [[package]] 1828 name = "jacquard-identity" 1829 version = "0.9.1" 1830 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1831 dependencies = [ 1832 "bon", 1833 "bytes", ··· 1853 [[package]] 1854 name = "jacquard-lexicon" 1855 version = "0.9.1" 1856 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1857 dependencies = [ 1858 "cid", 1859 "dashmap", ··· 1871 "serde_repr", 1872 "serde_with", 1873 "sha2", 1874 + "syn 2.0.110", 1875 "thiserror 2.0.17", 1876 "unicode-segmentation", 1877 ] ··· 1879 [[package]] 1880 name = "jacquard-oauth" 1881 version = "0.9.0" 1882 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 1883 dependencies = [ 1884 "base64 0.22.1", 1885 "bytes", ··· 2005 source = "registry+https://github.com/rust-lang/crates.io-index" 2006 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 2007 dependencies = [ 2008 + "spin 0.9.8", 2009 ] 2010 2011 [[package]] ··· 2065 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 2066 2067 [[package]] 2068 + name = "loom" 2069 + version = "0.7.2" 2070 + source = "registry+https://github.com/rust-lang/crates.io-index" 2071 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 2072 + dependencies = [ 2073 + "cfg-if", 2074 + "generator", 2075 + "scoped-tls", 2076 + "tracing", 2077 + "tracing-subscriber", 2078 + ] 2079 + 2080 + [[package]] 2081 name = "lru-cache" 2082 version = "0.1.2" 2083 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2136 ] 2137 2138 [[package]] 2139 + name = "matchers" 2140 + version = "0.2.0" 2141 + source = "registry+https://github.com/rust-lang/crates.io-index" 2142 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 2143 + dependencies = [ 2144 + "regex-automata", 2145 + ] 2146 + 2147 + [[package]] 2148 + name = "matchit" 2149 + version = "0.7.3" 2150 + source = "registry+https://github.com/rust-lang/crates.io-index" 2151 + checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 2152 + 2153 + [[package]] 2154 name = "memchr" 2155 version = "2.7.6" 2156 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2183 dependencies = [ 2184 "proc-macro2", 2185 "quote", 2186 + "syn 2.0.110", 2187 ] 2188 2189 [[package]] ··· 2285 ] 2286 2287 [[package]] 2288 + name = "n0-future" 2289 + version = "0.1.3" 2290 + source = "registry+https://github.com/rust-lang/crates.io-index" 2291 + checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" 2292 + dependencies = [ 2293 + "cfg_aliases", 2294 + "derive_more", 2295 + "futures-buffered", 2296 + "futures-lite", 2297 + "futures-util", 2298 + "js-sys", 2299 + "pin-project", 2300 + "send_wrapper", 2301 + "tokio", 2302 + "tokio-util", 2303 + "wasm-bindgen", 2304 + "wasm-bindgen-futures", 2305 + "web-time", 2306 + ] 2307 + 2308 + [[package]] 2309 name = "ndk-context" 2310 version = "0.1.1" 2311 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2328 ] 2329 2330 [[package]] 2331 + name = "nu-ansi-term" 2332 + version = "0.50.3" 2333 + source = "registry+https://github.com/rust-lang/crates.io-index" 2334 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 2335 + dependencies = [ 2336 + "windows-sys 0.61.2", 2337 + ] 2338 + 2339 + [[package]] 2340 name = "num-bigint-dig" 2341 + version = "0.8.6" 2342 source = "registry+https://github.com/rust-lang/crates.io-index" 2343 + checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" 2344 dependencies = [ 2345 "lazy_static", 2346 "libm", ··· 2454 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 2455 2456 [[package]] 2457 + name = "openssl-probe" 2458 + version = "0.1.6" 2459 + source = "registry+https://github.com/rust-lang/crates.io-index" 2460 + checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 2461 + 2462 + [[package]] 2463 name = "option-ext" 2464 version = "0.2.0" 2465 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2486 "proc-macro2", 2487 "proc-macro2-diagnostics", 2488 "quote", 2489 + "syn 2.0.110", 2490 ] 2491 2492 [[package]] ··· 2516 "elliptic-curve", 2517 "primeorder", 2518 ] 2519 + 2520 + [[package]] 2521 + name = "parking" 2522 + version = "2.2.1" 2523 + source = "registry+https://github.com/rust-lang/crates.io-index" 2524 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 2525 2526 [[package]] 2527 name = "parking_lot" ··· 2600 ] 2601 2602 [[package]] 2603 + name = "pin-project" 2604 + version = "1.1.10" 2605 + source = "registry+https://github.com/rust-lang/crates.io-index" 2606 + checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 2607 + dependencies = [ 2608 + "pin-project-internal", 2609 + ] 2610 + 2611 + [[package]] 2612 + name = "pin-project-internal" 2613 + version = "1.1.10" 2614 + source = "registry+https://github.com/rust-lang/crates.io-index" 2615 + checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 2616 + dependencies = [ 2617 + "proc-macro2", 2618 + "quote", 2619 + "syn 2.0.110", 2620 + ] 2621 + 2622 + [[package]] 2623 name = "pin-project-lite" 2624 version = "0.2.16" 2625 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2689 checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 2690 dependencies = [ 2691 "proc-macro2", 2692 + "syn 2.0.110", 2693 ] 2694 2695 [[package]] ··· 2742 dependencies = [ 2743 "proc-macro2", 2744 "quote", 2745 + "syn 2.0.110", 2746 "version_check", 2747 "yansi", 2748 ] ··· 2810 2811 [[package]] 2812 name = "quote" 2813 + version = "1.0.42" 2814 source = "registry+https://github.com/rust-lang/crates.io-index" 2815 + checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 2816 dependencies = [ 2817 "proc-macro2", 2818 ] ··· 2925 dependencies = [ 2926 "proc-macro2", 2927 "quote", 2928 + "syn 2.0.110", 2929 ] 2930 2931 [[package]] ··· 2991 "tokio", 2992 "tokio-rustls", 2993 "tokio-util", 2994 + "tower 0.5.2", 2995 + "tower-http 0.6.6", 2996 "tower-service", 2997 "url", 2998 "wasm-bindgen", ··· 3103 3104 [[package]] 3105 name = "rustls" 3106 + version = "0.23.35" 3107 source = "registry+https://github.com/rust-lang/crates.io-index" 3108 + checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" 3109 dependencies = [ 3110 "once_cell", 3111 "ring", ··· 3113 "rustls-webpki", 3114 "subtle", 3115 "zeroize", 3116 + ] 3117 + 3118 + [[package]] 3119 + name = "rustls-native-certs" 3120 + version = "0.8.2" 3121 + source = "registry+https://github.com/rust-lang/crates.io-index" 3122 + checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" 3123 + dependencies = [ 3124 + "openssl-probe", 3125 + "rustls-pki-types", 3126 + "schannel", 3127 + "security-framework", 3128 ] 3129 3130 [[package]] ··· 3176 ] 3177 3178 [[package]] 3179 + name = "schannel" 3180 + version = "0.1.28" 3181 + source = "registry+https://github.com/rust-lang/crates.io-index" 3182 + checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 3183 + dependencies = [ 3184 + "windows-sys 0.61.2", 3185 + ] 3186 + 3187 + [[package]] 3188 name = "schemars" 3189 version = "0.9.0" 3190 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3198 3199 [[package]] 3200 name = "schemars" 3201 + version = "1.1.0" 3202 source = "registry+https://github.com/rust-lang/crates.io-index" 3203 + checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" 3204 dependencies = [ 3205 "dyn-clone", 3206 "ref-cast", ··· 3209 ] 3210 3211 [[package]] 3212 + name = "scoped-tls" 3213 + version = "1.0.1" 3214 + source = "registry+https://github.com/rust-lang/crates.io-index" 3215 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 3216 + 3217 + [[package]] 3218 name = "scopeguard" 3219 version = "1.2.0" 3220 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3233 "subtle", 3234 "zeroize", 3235 ] 3236 + 3237 + [[package]] 3238 + name = "security-framework" 3239 + version = "3.5.1" 3240 + source = "registry+https://github.com/rust-lang/crates.io-index" 3241 + checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 3242 + dependencies = [ 3243 + "bitflags", 3244 + "core-foundation 0.10.1", 3245 + "core-foundation-sys", 3246 + "libc", 3247 + "security-framework-sys", 3248 + ] 3249 + 3250 + [[package]] 3251 + name = "security-framework-sys" 3252 + version = "2.15.0" 3253 + source = "registry+https://github.com/rust-lang/crates.io-index" 3254 + checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 3255 + dependencies = [ 3256 + "core-foundation-sys", 3257 + "libc", 3258 + ] 3259 + 3260 + [[package]] 3261 + name = "send_wrapper" 3262 + version = "0.6.0" 3263 + source = "registry+https://github.com/rust-lang/crates.io-index" 3264 + checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 3265 3266 [[package]] 3267 name = "serde" ··· 3300 dependencies = [ 3301 "proc-macro2", 3302 "quote", 3303 + "syn 2.0.110", 3304 ] 3305 3306 [[package]] ··· 3342 ] 3343 3344 [[package]] 3345 + name = "serde_path_to_error" 3346 + version = "0.1.20" 3347 + source = "registry+https://github.com/rust-lang/crates.io-index" 3348 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 3349 + dependencies = [ 3350 + "itoa", 3351 + "serde", 3352 + "serde_core", 3353 + ] 3354 + 3355 + [[package]] 3356 name = "serde_repr" 3357 version = "0.1.20" 3358 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3360 dependencies = [ 3361 "proc-macro2", 3362 "quote", 3363 + "syn 2.0.110", 3364 ] 3365 3366 [[package]] ··· 3387 "indexmap 1.9.3", 3388 "indexmap 2.12.0", 3389 "schemars 0.9.0", 3390 + "schemars 1.1.0", 3391 "serde_core", 3392 "serde_json", 3393 "serde_with_macros", ··· 3403 "darling", 3404 "proc-macro2", 3405 "quote", 3406 + "syn 2.0.110", 3407 + ] 3408 + 3409 + [[package]] 3410 + name = "sha1" 3411 + version = "0.10.6" 3412 + source = "registry+https://github.com/rust-lang/crates.io-index" 3413 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 3414 + dependencies = [ 3415 + "cfg-if", 3416 + "cpufeatures", 3417 + "digest", 3418 ] 3419 3420 [[package]] ··· 3432 "cfg-if", 3433 "cpufeatures", 3434 "digest", 3435 + ] 3436 + 3437 + [[package]] 3438 + name = "sharded-slab" 3439 + version = "0.1.7" 3440 + source = "registry+https://github.com/rust-lang/crates.io-index" 3441 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 3442 + dependencies = [ 3443 + "lazy_static", 3444 ] 3445 3446 [[package]] ··· 3538 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 3539 3540 [[package]] 3541 + name = "spin" 3542 + version = "0.10.0" 3543 + source = "registry+https://github.com/rust-lang/crates.io-index" 3544 + checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" 3545 + 3546 + [[package]] 3547 name = "spki" 3548 version = "0.7.3" 3549 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3575 "quote", 3576 "serde", 3577 "sha2", 3578 + "syn 2.0.110", 3579 "thiserror 1.0.69", 3580 ] 3581 ··· 3656 3657 [[package]] 3658 name = "syn" 3659 + version = "2.0.110" 3660 source = "registry+https://github.com/rust-lang/crates.io-index" 3661 + checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 3662 dependencies = [ 3663 "proc-macro2", 3664 "quote", ··· 3682 dependencies = [ 3683 "proc-macro2", 3684 "quote", 3685 + "syn 2.0.110", 3686 ] 3687 3688 [[package]] ··· 3782 dependencies = [ 3783 "proc-macro2", 3784 "quote", 3785 + "syn 2.0.110", 3786 ] 3787 3788 [[package]] ··· 3793 dependencies = [ 3794 "proc-macro2", 3795 "quote", 3796 + "syn 2.0.110", 3797 + ] 3798 + 3799 + [[package]] 3800 + name = "thread_local" 3801 + version = "1.1.9" 3802 + source = "registry+https://github.com/rust-lang/crates.io-index" 3803 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 3804 + dependencies = [ 3805 + "cfg-if", 3806 ] 3807 3808 [[package]] ··· 3909 dependencies = [ 3910 "proc-macro2", 3911 "quote", 3912 + "syn 2.0.110", 3913 ] 3914 3915 [[package]] ··· 3923 ] 3924 3925 [[package]] 3926 + name = "tokio-tungstenite" 3927 + version = "0.24.0" 3928 + source = "registry+https://github.com/rust-lang/crates.io-index" 3929 + checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" 3930 + dependencies = [ 3931 + "futures-util", 3932 + "log", 3933 + "rustls", 3934 + "rustls-native-certs", 3935 + "rustls-pki-types", 3936 + "tokio", 3937 + "tokio-rustls", 3938 + "tungstenite", 3939 + ] 3940 + 3941 + [[package]] 3942 + name = "tokio-tungstenite-wasm" 3943 + version = "0.4.0" 3944 + source = "registry+https://github.com/rust-lang/crates.io-index" 3945 + checksum = "e21a5c399399c3db9f08d8297ac12b500e86bca82e930253fdc62eaf9c0de6ae" 3946 + dependencies = [ 3947 + "futures-channel", 3948 + "futures-util", 3949 + "http", 3950 + "httparse", 3951 + "js-sys", 3952 + "rustls", 3953 + "thiserror 1.0.69", 3954 + "tokio", 3955 + "tokio-tungstenite", 3956 + "wasm-bindgen", 3957 + "web-sys", 3958 + ] 3959 + 3960 + [[package]] 3961 name = "tokio-util" 3962 + version = "0.7.17" 3963 source = "registry+https://github.com/rust-lang/crates.io-index" 3964 + checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 3965 dependencies = [ 3966 "bytes", 3967 "futures-core", 3968 "futures-sink", 3969 + "futures-util", 3970 "pin-project-lite", 3971 "tokio", 3972 ] 3973 3974 [[package]] 3975 name = "tower" 3976 + version = "0.4.13" 3977 + source = "registry+https://github.com/rust-lang/crates.io-index" 3978 + checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 3979 + dependencies = [ 3980 + "tower-layer", 3981 + "tower-service", 3982 + "tracing", 3983 + ] 3984 + 3985 + [[package]] 3986 + name = "tower" 3987 version = "0.5.2" 3988 source = "registry+https://github.com/rust-lang/crates.io-index" 3989 checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" ··· 3995 "tokio", 3996 "tower-layer", 3997 "tower-service", 3998 + "tracing", 3999 + ] 4000 + 4001 + [[package]] 4002 + name = "tower-http" 4003 + version = "0.5.2" 4004 + source = "registry+https://github.com/rust-lang/crates.io-index" 4005 + checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" 4006 + dependencies = [ 4007 + "async-compression", 4008 + "bitflags", 4009 + "bytes", 4010 + "futures-core", 4011 + "futures-util", 4012 + "http", 4013 + "http-body", 4014 + "http-body-util", 4015 + "http-range-header", 4016 + "httpdate", 4017 + "mime", 4018 + "mime_guess", 4019 + "percent-encoding", 4020 + "pin-project-lite", 4021 + "tokio", 4022 + "tokio-util", 4023 + "tower-layer", 4024 + "tower-service", 4025 + "tracing", 4026 ] 4027 4028 [[package]] ··· 4038 "http-body", 4039 "iri-string", 4040 "pin-project-lite", 4041 + "tower 0.5.2", 4042 "tower-layer", 4043 "tower-service", 4044 ] ··· 4061 source = "registry+https://github.com/rust-lang/crates.io-index" 4062 checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 4063 dependencies = [ 4064 + "log", 4065 "pin-project-lite", 4066 "tracing-attributes", 4067 "tracing-core", ··· 4075 dependencies = [ 4076 "proc-macro2", 4077 "quote", 4078 + "syn 2.0.110", 4079 ] 4080 4081 [[package]] ··· 4085 checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 4086 dependencies = [ 4087 "once_cell", 4088 + "valuable", 4089 + ] 4090 + 4091 + [[package]] 4092 + name = "tracing-log" 4093 + version = "0.2.0" 4094 + source = "registry+https://github.com/rust-lang/crates.io-index" 4095 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 4096 + dependencies = [ 4097 + "log", 4098 + "once_cell", 4099 + "tracing-core", 4100 + ] 4101 + 4102 + [[package]] 4103 + name = "tracing-subscriber" 4104 + version = "0.3.20" 4105 + source = "registry+https://github.com/rust-lang/crates.io-index" 4106 + checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 4107 + dependencies = [ 4108 + "matchers", 4109 + "nu-ansi-term", 4110 + "once_cell", 4111 + "regex-automata", 4112 + "sharded-slab", 4113 + "smallvec", 4114 + "thread_local", 4115 + "tracing", 4116 + "tracing-core", 4117 + "tracing-log", 4118 ] 4119 4120 [[package]] ··· 4125 dependencies = [ 4126 "proc-macro2", 4127 "quote", 4128 + "syn 2.0.110", 4129 ] 4130 4131 [[package]] ··· 4141 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 4142 4143 [[package]] 4144 + name = "tungstenite" 4145 + version = "0.24.0" 4146 + source = "registry+https://github.com/rust-lang/crates.io-index" 4147 + checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" 4148 + dependencies = [ 4149 + "byteorder", 4150 + "bytes", 4151 + "data-encoding", 4152 + "http", 4153 + "httparse", 4154 + "log", 4155 + "rand 0.8.5", 4156 + "rustls", 4157 + "rustls-pki-types", 4158 + "sha1", 4159 + "thiserror 1.0.69", 4160 + "utf-8", 4161 + ] 4162 + 4163 + [[package]] 4164 name = "twoway" 4165 version = "0.1.8" 4166 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4212 checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 4213 4214 [[package]] 4215 + name = "unicode-xid" 4216 + version = "0.2.6" 4217 + source = "registry+https://github.com/rust-lang/crates.io-index" 4218 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 4219 + 4220 + [[package]] 4221 name = "unsigned-varint" 4222 version = "0.8.0" 4223 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4264 version = "0.2.2" 4265 source = "registry+https://github.com/rust-lang/crates.io-index" 4266 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 4267 + 4268 + [[package]] 4269 + name = "valuable" 4270 + version = "0.1.1" 4271 + source = "registry+https://github.com/rust-lang/crates.io-index" 4272 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4273 4274 [[package]] 4275 name = "version_check" ··· 4356 "bumpalo", 4357 "proc-macro2", 4358 "quote", 4359 + "syn 2.0.110", 4360 "wasm-bindgen-shared", 4361 ] 4362 ··· 4455 ] 4456 4457 [[package]] 4458 + name = "windows" 4459 + version = "0.61.3" 4460 + source = "registry+https://github.com/rust-lang/crates.io-index" 4461 + checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 4462 + dependencies = [ 4463 + "windows-collections", 4464 + "windows-core 0.61.2", 4465 + "windows-future", 4466 + "windows-link 0.1.3", 4467 + "windows-numerics", 4468 + ] 4469 + 4470 + [[package]] 4471 + name = "windows-collections" 4472 + version = "0.2.0" 4473 + source = "registry+https://github.com/rust-lang/crates.io-index" 4474 + checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 4475 + dependencies = [ 4476 + "windows-core 0.61.2", 4477 + ] 4478 + 4479 + [[package]] 4480 + name = "windows-core" 4481 + version = "0.61.2" 4482 + source = "registry+https://github.com/rust-lang/crates.io-index" 4483 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 4484 + dependencies = [ 4485 + "windows-implement", 4486 + "windows-interface", 4487 + "windows-link 0.1.3", 4488 + "windows-result 0.3.4", 4489 + "windows-strings 0.4.2", 4490 + ] 4491 + 4492 + [[package]] 4493 name = "windows-core" 4494 version = "0.62.2" 4495 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4503 ] 4504 4505 [[package]] 4506 + name = "windows-future" 4507 + version = "0.2.1" 4508 + source = "registry+https://github.com/rust-lang/crates.io-index" 4509 + checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 4510 + dependencies = [ 4511 + "windows-core 0.61.2", 4512 + "windows-link 0.1.3", 4513 + "windows-threading", 4514 + ] 4515 + 4516 + [[package]] 4517 name = "windows-implement" 4518 version = "0.60.2" 4519 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4521 dependencies = [ 4522 "proc-macro2", 4523 "quote", 4524 + "syn 2.0.110", 4525 ] 4526 4527 [[package]] ··· 4532 dependencies = [ 4533 "proc-macro2", 4534 "quote", 4535 + "syn 2.0.110", 4536 ] 4537 4538 [[package]] ··· 4546 version = "0.2.1" 4547 source = "registry+https://github.com/rust-lang/crates.io-index" 4548 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4549 + 4550 + [[package]] 4551 + name = "windows-numerics" 4552 + version = "0.2.0" 4553 + source = "registry+https://github.com/rust-lang/crates.io-index" 4554 + checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 4555 + dependencies = [ 4556 + "windows-core 0.61.2", 4557 + "windows-link 0.1.3", 4558 + ] 4559 4560 [[package]] 4561 name = "windows-registry" ··· 4713 ] 4714 4715 [[package]] 4716 + name = "windows-threading" 4717 + version = "0.1.0" 4718 + source = "registry+https://github.com/rust-lang/crates.io-index" 4719 + checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 4720 + dependencies = [ 4721 + "windows-link 0.1.3", 4722 + ] 4723 + 4724 + [[package]] 4725 name = "windows_aarch64_gnullvm" 4726 version = "0.42.2" 4727 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4913 4914 [[package]] 4915 name = "wisp-cli" 4916 + version = "0.2.0" 4917 dependencies = [ 4918 + "axum", 4919 "base64 0.22.1", 4920 "bytes", 4921 + "chrono", 4922 "clap", 4923 "flate2", 4924 "futures", ··· 4933 "mime_guess", 4934 "multibase", 4935 "multihash", 4936 + "n0-future", 4937 "reqwest", 4938 "rustversion", 4939 "serde", ··· 4941 "sha2", 4942 "shellexpand", 4943 "tokio", 4944 + "tower 0.4.13", 4945 + "tower-http 0.5.2", 4946 + "url", 4947 "walkdir", 4948 ] 4949 ··· 4995 dependencies = [ 4996 "proc-macro2", 4997 "quote", 4998 + "syn 2.0.110", 4999 "synstructure", 5000 ] 5001 ··· 5016 dependencies = [ 5017 "proc-macro2", 5018 "quote", 5019 + "syn 2.0.110", 5020 ] 5021 5022 [[package]] ··· 5036 dependencies = [ 5037 "proc-macro2", 5038 "quote", 5039 + "syn 2.0.110", 5040 "synstructure", 5041 ] 5042 ··· 5079 dependencies = [ 5080 "proc-macro2", 5081 "quote", 5082 + "syn 2.0.110", 5083 ]
+8 -2
cli/Cargo.toml
··· 1 [package] 2 name = "wisp-cli" 3 - version = "0.1.0" 4 edition = "2024" 5 6 [features] ··· 11 jacquard = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["loopback"] } 12 jacquard-oauth = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 13 jacquard-api = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 14 - jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 15 jacquard-identity = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["dns"] } 16 jacquard-derive = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 17 jacquard-lexicon = { git = "https://tangled.org/@nonbinary.computer/jacquard" } ··· 33 multihash = "0.19.3" 34 multibase = "0.9" 35 sha2 = "0.10"
··· 1 [package] 2 name = "wisp-cli" 3 + version = "0.2.0" 4 edition = "2024" 5 6 [features] ··· 11 jacquard = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["loopback"] } 12 jacquard-oauth = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 13 jacquard-api = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 14 + jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["websocket"] } 15 jacquard-identity = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["dns"] } 16 jacquard-derive = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 17 jacquard-lexicon = { git = "https://tangled.org/@nonbinary.computer/jacquard" } ··· 33 multihash = "0.19.3" 34 multibase = "0.9" 35 sha2 = "0.10" 36 + axum = "0.7" 37 + tower-http = { version = "0.5", features = ["fs", "compression-gzip"] } 38 + tower = "0.4" 39 + n0-future = "0.1" 40 + chrono = "0.4" 41 + url = "2.5"
+71
cli/src/download.rs
···
··· 1 + use base64::Engine; 2 + use bytes::Bytes; 3 + use flate2::read::GzDecoder; 4 + use jacquard_common::types::blob::BlobRef; 5 + use miette::IntoDiagnostic; 6 + use std::io::Read; 7 + use url::Url; 8 + 9 + /// Download a blob from the PDS 10 + pub async fn download_blob(pds_url: &Url, blob_ref: &BlobRef<'_>, did: &str) -> miette::Result<Bytes> { 11 + // Extract CID from blob ref 12 + let cid = blob_ref.blob().r#ref.to_string(); 13 + 14 + // Construct blob download URL 15 + // The correct endpoint is: /xrpc/com.atproto.sync.getBlob?did={did}&cid={cid} 16 + let blob_url = pds_url 17 + .join(&format!("/xrpc/com.atproto.sync.getBlob?did={}&cid={}", did, cid)) 18 + .into_diagnostic()?; 19 + 20 + let client = reqwest::Client::new(); 21 + let response = client 22 + .get(blob_url) 23 + .send() 24 + .await 25 + .into_diagnostic()?; 26 + 27 + if !response.status().is_success() { 28 + return Err(miette::miette!( 29 + "Failed to download blob: {}", 30 + response.status() 31 + )); 32 + } 33 + 34 + let bytes = response.bytes().await.into_diagnostic()?; 35 + Ok(bytes) 36 + } 37 + 38 + /// Decompress and decode a blob (base64 + gzip) 39 + pub fn decompress_blob(data: &[u8], is_base64: bool, is_gzipped: bool) -> miette::Result<Vec<u8>> { 40 + let mut current_data = data.to_vec(); 41 + 42 + // First, decode base64 if needed 43 + if is_base64 { 44 + current_data = base64::prelude::BASE64_STANDARD 45 + .decode(&current_data) 46 + .into_diagnostic()?; 47 + } 48 + 49 + // Then, decompress gzip if needed 50 + if is_gzipped { 51 + let mut decoder = GzDecoder::new(&current_data[..]); 52 + let mut decompressed = Vec::new(); 53 + decoder.read_to_end(&mut decompressed).into_diagnostic()?; 54 + current_data = decompressed; 55 + } 56 + 57 + Ok(current_data) 58 + } 59 + 60 + /// Download and decompress a blob 61 + pub async fn download_and_decompress_blob( 62 + pds_url: &Url, 63 + blob_ref: &BlobRef<'_>, 64 + did: &str, 65 + is_base64: bool, 66 + is_gzipped: bool, 67 + ) -> miette::Result<Vec<u8>> { 68 + let data = download_blob(pds_url, blob_ref, did).await?; 69 + decompress_blob(&data, is_base64, is_gzipped) 70 + } 71 +
+109 -16
cli/src/main.rs
··· 2 mod place_wisp; 3 mod cid; 4 mod blob_map; 5 6 - use clap::Parser; 7 use jacquard::CowStr; 8 use jacquard::client::{Agent, FileAuthStore, AgentSessionExt, MemoryCredentialSession, AgentSession}; 9 use jacquard::oauth::client::OAuthClient; ··· 23 use place_wisp::fs::*; 24 25 #[derive(Parser, Debug)] 26 - #[command(author, version, about = "Deploy a static site to wisp.place")] 27 struct Args { 28 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 29 - input: CowStr<'static>, 30 31 /// Path to the directory containing your static site 32 - #[arg(short, long, default_value = ".")] 33 - path: PathBuf, 34 35 /// Site name (defaults to directory name) 36 - #[arg(short, long)] 37 site: Option<String>, 38 39 - /// Path to auth store file (will be created if missing, only used with OAuth) 40 - #[arg(long, default_value = "/tmp/wisp-oauth-session.json")] 41 - store: String, 42 43 - /// App Password for authentication (alternative to OAuth) 44 - #[arg(long)] 45 password: Option<CowStr<'static>>, 46 } 47 48 #[tokio::main] 49 async fn main() -> miette::Result<()> { 50 let args = Args::parse(); 51 52 - // Dispatch to appropriate authentication method 53 - if let Some(password) = args.password { 54 - run_with_app_password(args.input, password, args.path, args.site).await 55 - } else { 56 - run_with_oauth(args.input, args.store, args.path, args.site).await 57 } 58 } 59
··· 2 mod place_wisp; 3 mod cid; 4 mod blob_map; 5 + mod metadata; 6 + mod download; 7 + mod pull; 8 + mod serve; 9 10 + use clap::{Parser, Subcommand}; 11 use jacquard::CowStr; 12 use jacquard::client::{Agent, FileAuthStore, AgentSessionExt, MemoryCredentialSession, AgentSession}; 13 use jacquard::oauth::client::OAuthClient; ··· 27 use place_wisp::fs::*; 28 29 #[derive(Parser, Debug)] 30 + #[command(author, version, about = "wisp.place CLI tool")] 31 struct Args { 32 + #[command(subcommand)] 33 + command: Option<Commands>, 34 + 35 + // Deploy arguments (when no subcommand is specified) 36 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 37 + #[arg(global = true, conflicts_with = "command")] 38 + input: Option<CowStr<'static>>, 39 40 /// Path to the directory containing your static site 41 + #[arg(short, long, global = true, conflicts_with = "command")] 42 + path: Option<PathBuf>, 43 44 /// Site name (defaults to directory name) 45 + #[arg(short, long, global = true, conflicts_with = "command")] 46 site: Option<String>, 47 48 + /// Path to auth store file 49 + #[arg(long, global = true, conflicts_with = "command")] 50 + store: Option<String>, 51 52 + /// App Password for authentication 53 + #[arg(long, global = true, conflicts_with = "command")] 54 password: Option<CowStr<'static>>, 55 } 56 57 + #[derive(Subcommand, Debug)] 58 + enum Commands { 59 + /// Deploy a static site to wisp.place (default command) 60 + Deploy { 61 + /// Handle (e.g., alice.bsky.social), DID, or PDS URL 62 + input: CowStr<'static>, 63 + 64 + /// Path to the directory containing your static site 65 + #[arg(short, long, default_value = ".")] 66 + path: PathBuf, 67 + 68 + /// Site name (defaults to directory name) 69 + #[arg(short, long)] 70 + site: Option<String>, 71 + 72 + /// Path to auth store file (will be created if missing, only used with OAuth) 73 + #[arg(long, default_value = "/tmp/wisp-oauth-session.json")] 74 + store: String, 75 + 76 + /// App Password for authentication (alternative to OAuth) 77 + #[arg(long)] 78 + password: Option<CowStr<'static>>, 79 + }, 80 + /// Pull a site from the PDS to a local directory 81 + Pull { 82 + /// Handle (e.g., alice.bsky.social) or DID 83 + input: CowStr<'static>, 84 + 85 + /// Site name (record key) 86 + #[arg(short, long)] 87 + site: String, 88 + 89 + /// Output directory for the downloaded site 90 + #[arg(short, long, default_value = ".")] 91 + output: PathBuf, 92 + }, 93 + /// Serve a site locally with real-time firehose updates 94 + Serve { 95 + /// Handle (e.g., alice.bsky.social) or DID 96 + input: CowStr<'static>, 97 + 98 + /// Site name (record key) 99 + #[arg(short, long)] 100 + site: String, 101 + 102 + /// Output directory for the site files 103 + #[arg(short, long, default_value = ".")] 104 + output: PathBuf, 105 + 106 + /// Port to serve on 107 + #[arg(short, long, default_value = "8080")] 108 + port: u16, 109 + }, 110 + } 111 + 112 #[tokio::main] 113 async fn main() -> miette::Result<()> { 114 let args = Args::parse(); 115 116 + match args.command { 117 + Some(Commands::Deploy { input, path, site, store, password }) => { 118 + // Dispatch to appropriate authentication method 119 + if let Some(password) = password { 120 + run_with_app_password(input, password, path, site).await 121 + } else { 122 + run_with_oauth(input, store, path, site).await 123 + } 124 + } 125 + Some(Commands::Pull { input, site, output }) => { 126 + pull::pull_site(input, CowStr::from(site), output).await 127 + } 128 + Some(Commands::Serve { input, site, output, port }) => { 129 + serve::serve_site(input, CowStr::from(site), output, port).await 130 + } 131 + None => { 132 + // Legacy mode: if input is provided, assume deploy command 133 + if let Some(input) = args.input { 134 + let path = args.path.unwrap_or_else(|| PathBuf::from(".")); 135 + let store = args.store.unwrap_or_else(|| "/tmp/wisp-oauth-session.json".to_string()); 136 + 137 + // Dispatch to appropriate authentication method 138 + if let Some(password) = args.password { 139 + run_with_app_password(input, password, path, args.site).await 140 + } else { 141 + run_with_oauth(input, store, path, args.site).await 142 + } 143 + } else { 144 + // No command and no input, show help 145 + use clap::CommandFactory; 146 + Args::command().print_help().into_diagnostic()?; 147 + Ok(()) 148 + } 149 + } 150 } 151 } 152
+46
cli/src/metadata.rs
···
··· 1 + use serde::{Deserialize, Serialize}; 2 + use std::collections::HashMap; 3 + use std::path::Path; 4 + use miette::IntoDiagnostic; 5 + 6 + /// Metadata tracking file CIDs for incremental updates 7 + #[derive(Debug, Clone, Serialize, Deserialize)] 8 + pub struct SiteMetadata { 9 + /// Record CID from the PDS 10 + pub record_cid: String, 11 + /// Map of file paths to their blob CIDs 12 + pub file_cids: HashMap<String, String>, 13 + /// Timestamp when the site was last synced 14 + pub last_sync: i64, 15 + } 16 + 17 + impl SiteMetadata { 18 + pub fn new(record_cid: String, file_cids: HashMap<String, String>) -> Self { 19 + Self { 20 + record_cid, 21 + file_cids, 22 + last_sync: chrono::Utc::now().timestamp(), 23 + } 24 + } 25 + 26 + /// Load metadata from a directory 27 + pub fn load(dir: &Path) -> miette::Result<Option<Self>> { 28 + let metadata_path = dir.join(".wisp-metadata.json"); 29 + if !metadata_path.exists() { 30 + return Ok(None); 31 + } 32 + 33 + let contents = std::fs::read_to_string(&metadata_path).into_diagnostic()?; 34 + let metadata: SiteMetadata = serde_json::from_str(&contents).into_diagnostic()?; 35 + Ok(Some(metadata)) 36 + } 37 + 38 + /// Save metadata to a directory 39 + pub fn save(&self, dir: &Path) -> miette::Result<()> { 40 + let metadata_path = dir.join(".wisp-metadata.json"); 41 + let contents = serde_json::to_string_pretty(self).into_diagnostic()?; 42 + std::fs::write(&metadata_path, contents).into_diagnostic()?; 43 + Ok(()) 44 + } 45 + } 46 +
+305
cli/src/pull.rs
···
··· 1 + use crate::blob_map; 2 + use crate::download; 3 + use crate::metadata::SiteMetadata; 4 + use crate::place_wisp::fs::*; 5 + use jacquard::CowStr; 6 + use jacquard::prelude::IdentityResolver; 7 + use jacquard_common::types::string::Did; 8 + use jacquard_common::xrpc::XrpcExt; 9 + use jacquard_identity::PublicResolver; 10 + use miette::IntoDiagnostic; 11 + use std::collections::HashMap; 12 + use std::path::{Path, PathBuf}; 13 + use url::Url; 14 + 15 + /// Pull a site from the PDS to a local directory 16 + pub async fn pull_site( 17 + input: CowStr<'static>, 18 + rkey: CowStr<'static>, 19 + output_dir: PathBuf, 20 + ) -> miette::Result<()> { 21 + println!("Pulling site {} from {}...", rkey, input); 22 + 23 + // Resolve handle to DID if needed 24 + let resolver = PublicResolver::default(); 25 + let did = if input.starts_with("did:") { 26 + Did::new(&input).into_diagnostic()? 27 + } else { 28 + // It's a handle, resolve it 29 + let handle = jacquard_common::types::string::Handle::new(&input).into_diagnostic()?; 30 + resolver.resolve_handle(&handle).await.into_diagnostic()? 31 + }; 32 + 33 + // Resolve PDS endpoint for the DID 34 + let pds_url = resolver.pds_for_did(&did).await.into_diagnostic()?; 35 + println!("Resolved PDS: {}", pds_url); 36 + 37 + // Fetch the place.wisp.fs record 38 + 39 + println!("Fetching record from PDS..."); 40 + let client = reqwest::Client::new(); 41 + 42 + // Use com.atproto.repo.getRecord 43 + use jacquard::api::com_atproto::repo::get_record::GetRecord; 44 + use jacquard_common::types::string::Rkey as RkeyType; 45 + let rkey_parsed = RkeyType::new(&rkey).into_diagnostic()?; 46 + 47 + use jacquard_common::types::ident::AtIdentifier; 48 + use jacquard_common::types::string::RecordKey; 49 + let request = GetRecord::new() 50 + .repo(AtIdentifier::Did(did.clone())) 51 + .collection(CowStr::from("place.wisp.fs")) 52 + .rkey(RecordKey::from(rkey_parsed)) 53 + .build(); 54 + 55 + let response = client 56 + .xrpc(pds_url.clone()) 57 + .send(&request) 58 + .await 59 + .into_diagnostic()?; 60 + 61 + let record_output = response.into_output().into_diagnostic()?; 62 + let record_cid = record_output.cid.as_ref().map(|c| c.to_string()).unwrap_or_default(); 63 + 64 + // Parse the record value as Fs 65 + use jacquard_common::types::value::from_data; 66 + let fs_record: Fs = from_data(&record_output.value).into_diagnostic()?; 67 + 68 + let file_count = fs_record.file_count.map(|c| c.to_string()).unwrap_or_else(|| "?".to_string()); 69 + println!("Found site '{}' with {} files", fs_record.site, file_count); 70 + 71 + // Load existing metadata for incremental updates 72 + let existing_metadata = SiteMetadata::load(&output_dir)?; 73 + let existing_file_cids = existing_metadata 74 + .as_ref() 75 + .map(|m| m.file_cids.clone()) 76 + .unwrap_or_default(); 77 + 78 + // Extract blob map from the new manifest 79 + let new_blob_map = blob_map::extract_blob_map(&fs_record.root); 80 + let new_file_cids: HashMap<String, String> = new_blob_map 81 + .iter() 82 + .map(|(path, (_blob_ref, cid))| (path.clone(), cid.clone())) 83 + .collect(); 84 + 85 + // Clean up any leftover temp directories from previous failed attempts 86 + let parent = output_dir.parent().unwrap_or_else(|| std::path::Path::new(".")); 87 + let output_name = output_dir.file_name().unwrap_or_else(|| std::ffi::OsStr::new("site")).to_string_lossy(); 88 + let temp_prefix = format!(".tmp-{}-", output_name); 89 + 90 + if let Ok(entries) = parent.read_dir() { 91 + for entry in entries.flatten() { 92 + let name = entry.file_name(); 93 + if name.to_string_lossy().starts_with(&temp_prefix) { 94 + let _ = std::fs::remove_dir_all(entry.path()); 95 + } 96 + } 97 + } 98 + 99 + // Check if we need to update (but only if output directory actually exists with files) 100 + if let Some(metadata) = &existing_metadata { 101 + if metadata.record_cid == record_cid { 102 + // Verify that the output directory actually exists and has content 103 + let has_content = output_dir.exists() && 104 + output_dir.read_dir() 105 + .map(|mut entries| entries.any(|e| { 106 + if let Ok(entry) = e { 107 + !entry.file_name().to_string_lossy().starts_with(".wisp-metadata") 108 + } else { 109 + false 110 + } 111 + })) 112 + .unwrap_or(false); 113 + 114 + if has_content { 115 + println!("Site is already up to date!"); 116 + return Ok(()); 117 + } 118 + } 119 + } 120 + 121 + // Create temporary directory for atomic update 122 + // Place temp dir in parent directory to avoid issues with non-existent output_dir 123 + let parent = output_dir.parent().unwrap_or_else(|| std::path::Path::new(".")); 124 + let temp_dir_name = format!( 125 + ".tmp-{}-{}", 126 + output_dir.file_name().unwrap_or_else(|| std::ffi::OsStr::new("site")).to_string_lossy(), 127 + chrono::Utc::now().timestamp() 128 + ); 129 + let temp_dir = parent.join(temp_dir_name); 130 + std::fs::create_dir_all(&temp_dir).into_diagnostic()?; 131 + 132 + println!("Downloading files..."); 133 + let mut downloaded = 0; 134 + let mut reused = 0; 135 + 136 + // Download files recursively 137 + let download_result = download_directory( 138 + &fs_record.root, 139 + &temp_dir, 140 + &pds_url, 141 + did.as_str(), 142 + &new_blob_map, 143 + &existing_file_cids, 144 + &output_dir, 145 + String::new(), 146 + &mut downloaded, 147 + &mut reused, 148 + ) 149 + .await; 150 + 151 + // If download failed, clean up temp directory 152 + if let Err(e) = download_result { 153 + let _ = std::fs::remove_dir_all(&temp_dir); 154 + return Err(e); 155 + } 156 + 157 + println!( 158 + "Downloaded {} files, reused {} files", 159 + downloaded, reused 160 + ); 161 + 162 + // Save metadata 163 + let metadata = SiteMetadata::new(record_cid, new_file_cids); 164 + metadata.save(&temp_dir)?; 165 + 166 + // Move files from temp to output directory 167 + let output_abs = std::fs::canonicalize(&output_dir).unwrap_or_else(|_| output_dir.clone()); 168 + let current_dir = std::env::current_dir().into_diagnostic()?; 169 + 170 + // Special handling for pulling to current directory 171 + if output_abs == current_dir { 172 + // Move files from temp to current directory 173 + for entry in std::fs::read_dir(&temp_dir).into_diagnostic()? { 174 + let entry = entry.into_diagnostic()?; 175 + let dest = current_dir.join(entry.file_name()); 176 + 177 + // Remove existing file/dir if it exists 178 + if dest.exists() { 179 + if dest.is_dir() { 180 + std::fs::remove_dir_all(&dest).into_diagnostic()?; 181 + } else { 182 + std::fs::remove_file(&dest).into_diagnostic()?; 183 + } 184 + } 185 + 186 + // Move from temp to current dir 187 + std::fs::rename(entry.path(), dest).into_diagnostic()?; 188 + } 189 + 190 + // Clean up temp directory 191 + std::fs::remove_dir_all(&temp_dir).into_diagnostic()?; 192 + } else { 193 + // If output directory exists and has content, remove it first 194 + if output_dir.exists() { 195 + std::fs::remove_dir_all(&output_dir).into_diagnostic()?; 196 + } 197 + 198 + // Ensure parent directory exists 199 + if let Some(parent) = output_dir.parent() { 200 + if !parent.as_os_str().is_empty() && !parent.exists() { 201 + std::fs::create_dir_all(parent).into_diagnostic()?; 202 + } 203 + } 204 + 205 + // Rename temp to final location 206 + match std::fs::rename(&temp_dir, &output_dir) { 207 + Ok(_) => {}, 208 + Err(e) => { 209 + // Clean up temp directory on failure 210 + let _ = std::fs::remove_dir_all(&temp_dir); 211 + return Err(miette::miette!("Failed to move temp directory: {}", e)); 212 + } 213 + } 214 + } 215 + 216 + println!("โœ“ Site pulled successfully to {}", output_dir.display()); 217 + 218 + Ok(()) 219 + } 220 + 221 + /// Recursively download a directory 222 + fn download_directory<'a>( 223 + dir: &'a Directory<'_>, 224 + output_dir: &'a Path, 225 + pds_url: &'a Url, 226 + did: &'a str, 227 + new_blob_map: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 228 + existing_file_cids: &'a HashMap<String, String>, 229 + existing_output_dir: &'a Path, 230 + path_prefix: String, 231 + downloaded: &'a mut usize, 232 + reused: &'a mut usize, 233 + ) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<()>> + Send + 'a>> { 234 + Box::pin(async move { 235 + for entry in &dir.entries { 236 + let entry_name = entry.name.as_str(); 237 + let current_path = if path_prefix.is_empty() { 238 + entry_name.to_string() 239 + } else { 240 + format!("{}/{}", path_prefix, entry_name) 241 + }; 242 + 243 + match &entry.node { 244 + EntryNode::File(file) => { 245 + let output_path = output_dir.join(entry_name); 246 + 247 + // Check if file CID matches existing 248 + if let Some((_blob_ref, new_cid)) = new_blob_map.get(&current_path) { 249 + if let Some(existing_cid) = existing_file_cids.get(&current_path) { 250 + if existing_cid == new_cid { 251 + // File unchanged, copy from existing directory 252 + let existing_path = existing_output_dir.join(&current_path); 253 + if existing_path.exists() { 254 + std::fs::copy(&existing_path, &output_path).into_diagnostic()?; 255 + *reused += 1; 256 + println!(" โœ“ Reused {}", current_path); 257 + continue; 258 + } 259 + } 260 + } 261 + } 262 + 263 + // File is new or changed, download it 264 + println!(" โ†“ Downloading {}", current_path); 265 + let data = download::download_and_decompress_blob( 266 + pds_url, 267 + &file.blob, 268 + did, 269 + file.base64.unwrap_or(false), 270 + file.encoding.as_ref().map(|e| e.as_str() == "gzip").unwrap_or(false), 271 + ) 272 + .await?; 273 + 274 + std::fs::write(&output_path, data).into_diagnostic()?; 275 + *downloaded += 1; 276 + } 277 + EntryNode::Directory(subdir) => { 278 + let subdir_path = output_dir.join(entry_name); 279 + std::fs::create_dir_all(&subdir_path).into_diagnostic()?; 280 + 281 + download_directory( 282 + subdir, 283 + &subdir_path, 284 + pds_url, 285 + did, 286 + new_blob_map, 287 + existing_file_cids, 288 + existing_output_dir, 289 + current_path, 290 + downloaded, 291 + reused, 292 + ) 293 + .await?; 294 + } 295 + EntryNode::Unknown(_) => { 296 + // Skip unknown node types 297 + println!(" โš  Skipping unknown node type for {}", current_path); 298 + } 299 + } 300 + } 301 + 302 + Ok(()) 303 + }) 304 + } 305 +
+202
cli/src/serve.rs
···
··· 1 + use crate::pull::pull_site; 2 + use axum::Router; 3 + use jacquard::CowStr; 4 + use jacquard_common::jetstream::{CommitOperation, JetstreamMessage, JetstreamParams}; 5 + use jacquard_common::types::string::Did; 6 + use jacquard_common::xrpc::{SubscriptionClient, TungsteniteSubscriptionClient}; 7 + use miette::IntoDiagnostic; 8 + use n0_future::StreamExt; 9 + use std::path::PathBuf; 10 + use std::sync::Arc; 11 + use tokio::sync::RwLock; 12 + use tower_http::compression::CompressionLayer; 13 + use tower_http::services::ServeDir; 14 + use url::Url; 15 + 16 + /// Shared state for the server 17 + #[derive(Clone)] 18 + struct ServerState { 19 + did: CowStr<'static>, 20 + rkey: CowStr<'static>, 21 + output_dir: PathBuf, 22 + last_cid: Arc<RwLock<Option<String>>>, 23 + } 24 + 25 + /// Serve a site locally with real-time firehose updates 26 + pub async fn serve_site( 27 + input: CowStr<'static>, 28 + rkey: CowStr<'static>, 29 + output_dir: PathBuf, 30 + port: u16, 31 + ) -> miette::Result<()> { 32 + println!("Serving site {} from {} on port {}...", rkey, input, port); 33 + 34 + // Resolve handle to DID if needed 35 + use jacquard_identity::PublicResolver; 36 + use jacquard::prelude::IdentityResolver; 37 + 38 + let resolver = PublicResolver::default(); 39 + let did = if input.starts_with("did:") { 40 + Did::new(&input).into_diagnostic()? 41 + } else { 42 + // It's a handle, resolve it 43 + let handle = jacquard_common::types::string::Handle::new(&input).into_diagnostic()?; 44 + resolver.resolve_handle(&handle).await.into_diagnostic()? 45 + }; 46 + 47 + println!("Resolved to DID: {}", did.as_str()); 48 + 49 + // Create output directory if it doesn't exist 50 + std::fs::create_dir_all(&output_dir).into_diagnostic()?; 51 + 52 + // Initial pull of the site 53 + println!("Performing initial pull..."); 54 + let did_str = CowStr::from(did.as_str().to_string()); 55 + pull_site(did_str.clone(), rkey.clone(), output_dir.clone()).await?; 56 + 57 + // Create shared state 58 + let state = ServerState { 59 + did: did_str.clone(), 60 + rkey: rkey.clone(), 61 + output_dir: output_dir.clone(), 62 + last_cid: Arc::new(RwLock::new(None)), 63 + }; 64 + 65 + // Start firehose listener in background 66 + let firehose_state = state.clone(); 67 + tokio::spawn(async move { 68 + if let Err(e) = watch_firehose(firehose_state).await { 69 + eprintln!("Firehose error: {}", e); 70 + } 71 + }); 72 + 73 + // Create HTTP server with gzip compression 74 + let app = Router::new() 75 + .fallback_service( 76 + ServeDir::new(&output_dir) 77 + .precompressed_gzip() 78 + ) 79 + .layer(CompressionLayer::new()) 80 + .with_state(state); 81 + 82 + let addr = format!("0.0.0.0:{}", port); 83 + let listener = tokio::net::TcpListener::bind(&addr) 84 + .await 85 + .into_diagnostic()?; 86 + 87 + println!("\nโœ“ Server running at http://localhost:{}", port); 88 + println!(" Watching for updates on the firehose...\n"); 89 + 90 + axum::serve(listener, app).await.into_diagnostic()?; 91 + 92 + Ok(()) 93 + } 94 + 95 + /// Watch the firehose for updates to the specific site 96 + fn watch_firehose(state: ServerState) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<()>> + Send>> { 97 + Box::pin(async move { 98 + let jetstream_url = Url::parse("wss://jetstream1.us-east.fire.hose.cam") 99 + .into_diagnostic()?; 100 + 101 + println!("[Firehose] Connecting to Jetstream..."); 102 + 103 + // Create subscription client 104 + let client = TungsteniteSubscriptionClient::from_base_uri(jetstream_url); 105 + 106 + // Subscribe with no filters (we'll filter manually) 107 + // Jetstream doesn't support filtering by collection in the params builder 108 + let params = JetstreamParams::new().build(); 109 + 110 + let stream = client.subscribe(&params).await.into_diagnostic()?; 111 + println!("[Firehose] Connected! Watching for updates..."); 112 + 113 + // Convert to typed message stream 114 + let (_sink, mut messages) = stream.into_stream(); 115 + 116 + loop { 117 + match messages.next().await { 118 + Some(Ok(msg)) => { 119 + if let Err(e) = handle_firehose_message(&state, msg).await { 120 + eprintln!("[Firehose] Error handling message: {}", e); 121 + } 122 + } 123 + Some(Err(e)) => { 124 + eprintln!("[Firehose] Stream error: {}", e); 125 + // Try to reconnect after a delay 126 + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 127 + return Box::pin(watch_firehose(state)).await; 128 + } 129 + None => { 130 + println!("[Firehose] Stream ended, reconnecting..."); 131 + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 132 + return Box::pin(watch_firehose(state)).await; 133 + } 134 + } 135 + } 136 + }) 137 + } 138 + 139 + /// Handle a firehose message 140 + async fn handle_firehose_message( 141 + state: &ServerState, 142 + msg: JetstreamMessage<'_>, 143 + ) -> miette::Result<()> { 144 + match msg { 145 + JetstreamMessage::Commit { 146 + did, 147 + commit, 148 + .. 149 + } => { 150 + // Check if this is our site 151 + if did.as_str() == state.did.as_str() 152 + && commit.collection.as_str() == "place.wisp.fs" 153 + && commit.rkey.as_str() == state.rkey.as_str() 154 + { 155 + match commit.operation { 156 + CommitOperation::Create | CommitOperation::Update => { 157 + let new_cid = commit.cid.as_ref().map(|c| c.to_string()); 158 + 159 + // Check if CID changed 160 + let should_update = { 161 + let last_cid = state.last_cid.read().await; 162 + new_cid != *last_cid 163 + }; 164 + 165 + if should_update { 166 + println!("\n[Update] Detected change to site {} (CID: {:?})", state.rkey, new_cid); 167 + println!("[Update] Pulling latest version..."); 168 + 169 + // Pull the updated site 170 + match pull_site( 171 + state.did.clone(), 172 + state.rkey.clone(), 173 + state.output_dir.clone(), 174 + ) 175 + .await 176 + { 177 + Ok(_) => { 178 + // Update last CID 179 + let mut last_cid = state.last_cid.write().await; 180 + *last_cid = new_cid; 181 + println!("[Update] โœ“ Site updated successfully!\n"); 182 + } 183 + Err(e) => { 184 + eprintln!("[Update] Failed to pull site: {}", e); 185 + } 186 + } 187 + } 188 + } 189 + CommitOperation::Delete => { 190 + println!("\n[Update] Site {} was deleted", state.rkey); 191 + } 192 + } 193 + } 194 + } 195 + _ => { 196 + // Ignore identity and account messages 197 + } 198 + } 199 + 200 + Ok(()) 201 + } 202 +
+28 -1
crates.nix
··· 19 targets.x86_64-pc-windows-gnu.latest.rust-std 20 targets.x86_64-unknown-linux-gnu.latest.rust-std 21 targets.aarch64-apple-darwin.latest.rust-std 22 ]; 23 # configure crates 24 nci.crates."wisp-cli" = { ··· 26 dev.runTests = false; 27 release.runTests = false; 28 }; 29 - targets."x86_64-unknown-linux-gnu" = { 30 default = true; 31 }; 32 targets."x86_64-pc-windows-gnu" = let 33 targetPkgs = pkgs.pkgsCross.mingwW64; ··· 46 }; 47 targets."aarch64-apple-darwin" = let 48 targetPkgs = pkgs.pkgsCross.aarch64-darwin; 49 targetCC = targetPkgs.stdenv.cc; 50 targetCargoEnvVarTarget = targetPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget; 51 in rec {
··· 19 targets.x86_64-pc-windows-gnu.latest.rust-std 20 targets.x86_64-unknown-linux-gnu.latest.rust-std 21 targets.aarch64-apple-darwin.latest.rust-std 22 + targets.aarch64-unknown-linux-gnu.latest.rust-std 23 ]; 24 # configure crates 25 nci.crates."wisp-cli" = { ··· 27 dev.runTests = false; 28 release.runTests = false; 29 }; 30 + targets."x86_64-unknown-linux-gnu" = let 31 + targetPkgs = pkgs.pkgsCross.gnu64; 32 + targetCC = targetPkgs.stdenv.cc; 33 + targetCargoEnvVarTarget = targetPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget; 34 + in rec { 35 default = true; 36 + depsDrvConfig.mkDerivation = { 37 + nativeBuildInputs = [targetCC]; 38 + }; 39 + depsDrvConfig.env = rec { 40 + TARGET_CC = "${targetCC.targetPrefix}cc"; 41 + "CARGO_TARGET_${targetCargoEnvVarTarget}_LINKER" = TARGET_CC; 42 + }; 43 + drvConfig = depsDrvConfig; 44 }; 45 targets."x86_64-pc-windows-gnu" = let 46 targetPkgs = pkgs.pkgsCross.mingwW64; ··· 59 }; 60 targets."aarch64-apple-darwin" = let 61 targetPkgs = pkgs.pkgsCross.aarch64-darwin; 62 + targetCC = targetPkgs.stdenv.cc; 63 + targetCargoEnvVarTarget = targetPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget; 64 + in rec { 65 + depsDrvConfig.mkDerivation = { 66 + nativeBuildInputs = [targetCC]; 67 + }; 68 + depsDrvConfig.env = rec { 69 + TARGET_CC = "${targetCC.targetPrefix}cc"; 70 + "CARGO_TARGET_${targetCargoEnvVarTarget}_LINKER" = TARGET_CC; 71 + }; 72 + drvConfig = depsDrvConfig; 73 + }; 74 + targets."aarch64-unknown-linux-gnu" = let 75 + targetPkgs = pkgs.pkgsCross.aarch64-multiplatform; 76 targetCC = targetPkgs.stdenv.cc; 77 targetCargoEnvVarTarget = targetPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget; 78 in rec {
+25 -2
flake.nix
··· 26 ... 27 }: let 28 crateOutputs = config.nci.outputs."wisp-cli"; 29 in { 30 devShells.default = crateOutputs.devShell; 31 packages.default = crateOutputs.packages.release; 32 - packages.wisp-cli-windows = crateOutputs.allTargets."x86_64-pc-windows-gnu".packages.release; 33 - packages.wisp-cli-darwin = crateOutputs.allTargets."aarch64-apple-darwin".packages.release; 34 }; 35 }; 36 }
··· 26 ... 27 }: let 28 crateOutputs = config.nci.outputs."wisp-cli"; 29 + mkRenamedPackage = name: pkg: isWindows: pkgs.runCommand name {} '' 30 + mkdir -p $out/bin 31 + if [ -f ${pkg}/bin/wisp-cli.exe ]; then 32 + cp ${pkg}/bin/wisp-cli.exe $out/bin/${name} 33 + elif [ -f ${pkg}/bin/wisp-cli ]; then 34 + cp ${pkg}/bin/wisp-cli $out/bin/${name} 35 + else 36 + echo "Error: Could not find wisp-cli binary in ${pkg}/bin/" 37 + ls -la ${pkg}/bin/ || true 38 + exit 1 39 + fi 40 + ''; 41 in { 42 devShells.default = crateOutputs.devShell; 43 packages.default = crateOutputs.packages.release; 44 + packages.wisp-cli-x86_64-linux = mkRenamedPackage "wisp-cli-x86_64-linux" crateOutputs.packages.release false; 45 + packages.wisp-cli-aarch64-linux = mkRenamedPackage "wisp-cli-aarch64-linux" crateOutputs.allTargets."aarch64-unknown-linux-gnu".packages.release false; 46 + packages.wisp-cli-x86_64-windows = mkRenamedPackage "wisp-cli-x86_64-windows.exe" crateOutputs.allTargets."x86_64-pc-windows-gnu".packages.release true; 47 + packages.wisp-cli-aarch64-darwin = mkRenamedPackage "wisp-cli-aarch64-darwin" crateOutputs.allTargets."aarch64-apple-darwin".packages.release false; 48 + packages.all = pkgs.symlinkJoin { 49 + name = "wisp-cli-all"; 50 + paths = [ 51 + config.packages.wisp-cli-x86_64-linux 52 + config.packages.wisp-cli-aarch64-linux 53 + config.packages.wisp-cli-x86_64-windows 54 + config.packages.wisp-cli-aarch64-darwin 55 + ]; 56 + }; 57 }; 58 }; 59 }
+78 -14
public/editor/tabs/CLITab.tsx
··· 16 <CardHeader> 17 <div className="flex items-center gap-2 mb-2"> 18 <CardTitle>Wisp CLI Tool</CardTitle> 19 - <Badge variant="secondary" className="text-xs">v0.1.0</Badge> 20 <Badge variant="outline" className="text-xs">Alpha</Badge> 21 </div> 22 <CardDescription> ··· 32 </div> 33 34 <div className="space-y-3"> 35 - <h3 className="text-sm font-semibold">Download CLI</h3> 36 <div className="grid gap-2"> 37 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> 38 <a 39 - href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64" 40 target="_blank" 41 rel="noopener noreferrer" 42 className="flex items-center justify-between mb-2" ··· 45 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 46 </a> 47 <div className="text-xs text-muted-foreground"> 48 - <span className="font-mono">SHA256: 637e325d9668ca745e01493d80dfc72447ef0a889b313e28913ca65c94c7aaae</span> 49 </div> 50 </div> 51 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 59 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 60 </a> 61 <div className="text-xs text-muted-foreground"> 62 - <span className="font-mono">SHA256: 01561656b64826f95b39f13c65c97da8bcc63ecd9f4d7e4e369c8ba8c903c22a</span> 63 </div> 64 </div> 65 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 73 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 74 </a> 75 <div className="text-xs text-muted-foreground"> 76 - <span className="font-mono">SHA256: 1ff485b9bcf89bc5721a862863c4843cf4530cbcd2489cf200cb24a44f7865a2</span> 77 </div> 78 </div> 79 </div> 80 </div> 81 82 <div className="space-y-3"> 83 - <h3 className="text-sm font-semibold">Basic Usage</h3> 84 <CodeBlock 85 code={`# Download and make executable 86 - curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64 87 - chmod +x wisp-cli-macos-arm64 88 89 - # Deploy your site (will use OAuth) 90 - ./wisp-cli-macos-arm64 your-handle.bsky.social \\ 91 --path ./dist \\ 92 - --site my-site 93 94 # Your site will be available at: 95 # https://sites.wisp.place/your-handle/my-site`} ··· 98 </div> 99 100 <div className="space-y-3"> 101 <h3 className="text-sm font-semibold">CI/CD with Tangled Spindle</h3> 102 <p className="text-xs text-muted-foreground"> 103 Deploy automatically on every push using{' '} ··· 147 chmod +x wisp-cli 148 149 # Deploy to Wisp 150 - ./wisp-cli \\ 151 "$WISP_HANDLE" \\ 152 --path "$SITE_PATH" \\ 153 --site "$SITE_NAME" \\ ··· 210 chmod +x wisp-cli 211 212 # Deploy to Wisp 213 - ./wisp-cli \\ 214 "$WISP_HANDLE" \\ 215 --path "$SITE_PATH" \\ 216 --site "$SITE_NAME" \\
··· 16 <CardHeader> 17 <div className="flex items-center gap-2 mb-2"> 18 <CardTitle>Wisp CLI Tool</CardTitle> 19 + <Badge variant="secondary" className="text-xs">v0.2.0</Badge> 20 <Badge variant="outline" className="text-xs">Alpha</Badge> 21 </div> 22 <CardDescription> ··· 32 </div> 33 34 <div className="space-y-3"> 35 + <h3 className="text-sm font-semibold">Features</h3> 36 + <ul className="text-sm text-muted-foreground space-y-2 list-disc list-inside"> 37 + <li><strong>Deploy:</strong> Push static sites directly from your terminal</li> 38 + <li><strong>Pull:</strong> Download sites from the PDS for development or backup</li> 39 + <li><strong>Serve:</strong> Run a local server with real-time firehose updates</li> 40 + </ul> 41 + </div> 42 + 43 + <div className="space-y-3"> 44 + <h3 className="text-sm font-semibold">Download v0.2.0</h3> 45 <div className="grid gap-2"> 46 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> 47 <a 48 + href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" 49 target="_blank" 50 rel="noopener noreferrer" 51 className="flex items-center justify-between mb-2" ··· 54 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 55 </a> 56 <div className="text-xs text-muted-foreground"> 57 + <span className="font-mono">SHA-1: a8c27ea41c5e2672bfecb3476ece1c801741d759</span> 58 </div> 59 </div> 60 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 68 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 69 </a> 70 <div className="text-xs text-muted-foreground"> 71 + <span className="font-mono">SHA-1: fd7ee689c7600fc953179ea755b0357c8481a622</span> 72 </div> 73 </div> 74 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 82 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 83 </a> 84 <div className="text-xs text-muted-foreground"> 85 + <span className="font-mono">SHA-1: 8bca6992559e19e1d29ab3d2fcc6d09b28e5a485</span> 86 + </div> 87 + </div> 88 + <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> 89 + <a 90 + href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-windows.exe" 91 + target="_blank" 92 + rel="noopener noreferrer" 93 + className="flex items-center justify-between mb-2" 94 + > 95 + <span className="font-mono text-sm">Windows (x86_64)</span> 96 + <ExternalLink className="w-4 h-4 text-muted-foreground" /> 97 + </a> 98 + <div className="text-xs text-muted-foreground"> 99 + <span className="font-mono">SHA-1: 90ea3987a06597fa6c42e1df9009e9758e92dd54</span> 100 </div> 101 </div> 102 </div> 103 </div> 104 105 <div className="space-y-3"> 106 + <h3 className="text-sm font-semibold">Deploy a Site</h3> 107 <CodeBlock 108 code={`# Download and make executable 109 + curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin 110 + chmod +x wisp-cli-aarch64-darwin 111 112 + # Deploy your site 113 + ./wisp-cli-aarch64-darwin deploy your-handle.bsky.social \\ 114 --path ./dist \\ 115 + --site my-site \\ 116 + --password your-app-password 117 118 # Your site will be available at: 119 # https://sites.wisp.place/your-handle/my-site`} ··· 122 </div> 123 124 <div className="space-y-3"> 125 + <h3 className="text-sm font-semibold">Pull a Site from PDS</h3> 126 + <p className="text-xs text-muted-foreground"> 127 + Download a site from the PDS to your local machine (uses OAuth authentication): 128 + </p> 129 + <CodeBlock 130 + code={`# Pull a site to a specific directory 131 + wisp-cli pull your-handle.bsky.social \\ 132 + --site my-site \\ 133 + --output ./my-site 134 + 135 + # Pull to current directory 136 + wisp-cli pull your-handle.bsky.social \\ 137 + --site my-site 138 + 139 + # Opens browser for OAuth authentication on first run`} 140 + language="bash" 141 + /> 142 + </div> 143 + 144 + <div className="space-y-3"> 145 + <h3 className="text-sm font-semibold">Serve a Site Locally with Real-Time Updates</h3> 146 + <p className="text-xs text-muted-foreground"> 147 + Run a local server that monitors the firehose for real-time updates (uses OAuth authentication): 148 + </p> 149 + <CodeBlock 150 + code={`# Serve on http://localhost:8080 (default) 151 + wisp-cli serve your-handle.bsky.social \\ 152 + --site my-site 153 + 154 + # Serve on a custom port 155 + wisp-cli serve your-handle.bsky.social \\ 156 + --site my-site \\ 157 + --port 3000 158 + 159 + # Downloads site, serves it, and watches firehose for live updates!`} 160 + language="bash" 161 + /> 162 + </div> 163 + 164 + <div className="space-y-3"> 165 <h3 className="text-sm font-semibold">CI/CD with Tangled Spindle</h3> 166 <p className="text-xs text-muted-foreground"> 167 Deploy automatically on every push using{' '} ··· 211 chmod +x wisp-cli 212 213 # Deploy to Wisp 214 + ./wisp-cli deploy \\ 215 "$WISP_HANDLE" \\ 216 --path "$SITE_PATH" \\ 217 --site "$SITE_NAME" \\ ··· 274 chmod +x wisp-cli 275 276 # Deploy to Wisp 277 + ./wisp-cli deploy \\ 278 "$WISP_HANDLE" \\ 279 --path "$SITE_PATH" \\ 280 --site "$SITE_NAME" \\
+5 -5
src/index.ts
··· 70 }, 71 cookie: { 72 secrets: cookieSecret, 73 - sign: true 74 } 75 }) 76 // Observability middleware ··· 105 .onError(observabilityMiddleware('main-app').onError) 106 .use(csrfProtection()) 107 .use(authRoutes(client, cookieSecret)) 108 - .use(wispRoutes(client)) 109 - .use(domainRoutes(client)) 110 - .use(userRoutes(client)) 111 - .use(siteRoutes(client)) 112 .use(adminRoutes(cookieSecret)) 113 .use( 114 await staticPlugin({
··· 70 }, 71 cookie: { 72 secrets: cookieSecret, 73 + sign: ['did'] 74 } 75 }) 76 // Observability middleware ··· 105 .onError(observabilityMiddleware('main-app').onError) 106 .use(csrfProtection()) 107 .use(authRoutes(client, cookieSecret)) 108 + .use(wispRoutes(client, cookieSecret)) 109 + .use(domainRoutes(client, cookieSecret)) 110 + .use(userRoutes(client, cookieSecret)) 111 + .use(siteRoutes(client, cookieSecret)) 112 .use(adminRoutes(cookieSecret)) 113 .use( 114 await staticPlugin({
+6 -22
src/routes/auth.ts
··· 5 import { authenticateRequest } from '../lib/wisp-auth' 6 import { logger } from '../lib/observability' 7 8 - export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia() 9 .post('/api/auth/signin', async (c) => { 10 let handle = 'unknown' 11 try { ··· 74 c.cookie.did.remove() 75 return c.redirect('/?error=auth_failed') 76 } 77 - }, { 78 - cookie: t.Cookie({ 79 - did: t.Optional(t.String()) 80 - }, { 81 - secrets: cookieSecret, 82 - sign: ['did'] 83 - }) 84 }) 85 .post('/api/auth/logout', async (c) => { 86 try { ··· 106 logger.error('[Auth] Logout error', err) 107 return { error: 'Logout failed' } 108 } 109 - }, { 110 - cookie: t.Cookie({ 111 - did: t.Optional(t.String()) 112 - }, { 113 - secrets: cookieSecret, 114 - sign: ['did'] 115 - }) 116 }) 117 .get('/api/auth/status', async (c) => { 118 try { ··· 132 c.cookie.did.remove() 133 return { authenticated: false } 134 } 135 - }, { 136 - cookie: t.Cookie({ 137 - did: t.Optional(t.String()) 138 - }, { 139 - secrets: cookieSecret, 140 - sign: ['did'] 141 - }) 142 })
··· 5 import { authenticateRequest } from '../lib/wisp-auth' 6 import { logger } from '../lib/observability' 7 8 + export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia({ 9 + cookie: { 10 + secrets: cookieSecret, 11 + sign: ['did'] 12 + } 13 + }) 14 .post('/api/auth/signin', async (c) => { 15 let handle = 'unknown' 16 try { ··· 79 c.cookie.did.remove() 80 return c.redirect('/?error=auth_failed') 81 } 82 }) 83 .post('/api/auth/logout', async (c) => { 84 try { ··· 104 logger.error('[Auth] Logout error', err) 105 return { error: 'Logout failed' } 106 } 107 }) 108 .get('/api/auth/status', async (c) => { 109 try { ··· 123 c.cookie.did.remove() 124 return { authenticated: false } 125 } 126 })
+8 -2
src/routes/domain.ts
··· 24 import { verifyCustomDomain } from '../lib/dns-verify' 25 import { logger } from '../lib/logger' 26 27 - export const domainRoutes = (client: NodeOAuthClient) => 28 - new Elysia({ prefix: '/api/domain' }) 29 // Public endpoints (no auth required) 30 .get('/check', async ({ query }) => { 31 try {
··· 24 import { verifyCustomDomain } from '../lib/dns-verify' 25 import { logger } from '../lib/logger' 26 27 + export const domainRoutes = (client: NodeOAuthClient, cookieSecret: string) => 28 + new Elysia({ 29 + prefix: '/api/domain', 30 + cookie: { 31 + secrets: cookieSecret, 32 + sign: ['did'] 33 + } 34 + }) 35 // Public endpoints (no auth required) 36 .get('/check', async ({ query }) => { 37 try {
+8 -2
src/routes/site.ts
··· 5 import { deleteSite } from '../lib/db' 6 import { logger } from '../lib/logger' 7 8 - export const siteRoutes = (client: NodeOAuthClient) => 9 - new Elysia({ prefix: '/api/site' }) 10 .derive(async ({ cookie }) => { 11 const auth = await requireAuth(client, cookie) 12 return { auth }
··· 5 import { deleteSite } from '../lib/db' 6 import { logger } from '../lib/logger' 7 8 + export const siteRoutes = (client: NodeOAuthClient, cookieSecret: string) => 9 + new Elysia({ 10 + prefix: '/api/site', 11 + cookie: { 12 + secrets: cookieSecret, 13 + sign: ['did'] 14 + } 15 + }) 16 .derive(async ({ cookie }) => { 17 const auth = await requireAuth(client, cookie) 18 return { auth }
+9 -3
src/routes/user.ts
··· 1 - import { Elysia } from 'elysia' 2 import { requireAuth } from '../lib/wisp-auth' 3 import { NodeOAuthClient } from '@atproto/oauth-client-node' 4 import { Agent } from '@atproto/api' ··· 6 import { syncSitesFromPDS } from '../lib/sync-sites' 7 import { logger } from '../lib/logger' 8 9 - export const userRoutes = (client: NodeOAuthClient) => 10 - new Elysia({ prefix: '/api/user' }) 11 .derive(async ({ cookie }) => { 12 const auth = await requireAuth(client, cookie) 13 return { auth }
··· 1 + import { Elysia, t } from 'elysia' 2 import { requireAuth } from '../lib/wisp-auth' 3 import { NodeOAuthClient } from '@atproto/oauth-client-node' 4 import { Agent } from '@atproto/api' ··· 6 import { syncSitesFromPDS } from '../lib/sync-sites' 7 import { logger } from '../lib/logger' 8 9 + export const userRoutes = (client: NodeOAuthClient, cookieSecret: string) => 10 + new Elysia({ 11 + prefix: '/api/user', 12 + cookie: { 13 + secrets: cookieSecret, 14 + sign: ['did'] 15 + } 16 + }) 17 .derive(async ({ cookie }) => { 18 const auth = await requireAuth(client, cookie) 19 return { auth }
+8 -2
src/routes/wisp.ts
··· 37 return true; 38 } 39 40 - export const wispRoutes = (client: NodeOAuthClient) => 41 - new Elysia({ prefix: '/wisp' }) 42 .derive(async ({ cookie }) => { 43 const auth = await requireAuth(client, cookie) 44 return { auth }
··· 37 return true; 38 } 39 40 + export const wispRoutes = (client: NodeOAuthClient, cookieSecret: string) => 41 + new Elysia({ 42 + prefix: '/wisp', 43 + cookie: { 44 + secrets: cookieSecret, 45 + sign: ['did'] 46 + } 47 + }) 48 .derive(async ({ cookie }) => { 49 const auth = await requireAuth(client, cookie) 50 return { auth }