Initial g1t: services, event bus, intents and attempts
A git forge for agents on Cloudflare Workers and Artifacts. - packages/contracts: service interfaces, event catalogue, shared types - services/identity: accounts, sessions, SSH keys, access tokens - services/repos: registry, contents, forks, git over HTTPS (Artifacts) - services/events: event bus with a durable log and per-subscriber queues - services/work: intents, attempts (a fork each), sessions - apps/web: server-rendered site on g1t.sh - crates/sshd: SSH-to-Artifacts bridge (not yet deployed)
91 files+13306−00/91 viewed
| 1 | + | * text=auto eol=lf |
| 1 | + | # Generated account passwords (services/identity/scripts/create-user.mjs) | |
| 2 | + | /.credentials/ | |
| 3 | + | ||
| 4 | + | node_modules/ | |
| 5 | + | target/ | |
| 6 | + | .DS_Store | |
| 7 | + | worker-configuration.d.ts | |
| 8 | + | .wrangler/ | |
| 9 | + | .dev.vars* |
| 1 | + | # This file is automatically @generated by Cargo. | |
| 2 | + | # It is not intended for manual editing. | |
| 3 | + | version = 4 | |
| 4 | + | ||
| 5 | + | [[package]] | |
| 6 | + | name = "adler2" | |
| 7 | + | version = "2.0.1" | |
| 8 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9 | + | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" | |
| 10 | + | ||
| 11 | + | [[package]] | |
| 12 | + | name = "aead" | |
| 13 | + | version = "0.6.1" | |
| 14 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 15 | + | checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" | |
| 16 | + | dependencies = [ | |
| 17 | + | "crypto-common", | |
| 18 | + | "inout", | |
| 19 | + | ] | |
| 20 | + | ||
| 21 | + | [[package]] | |
| 22 | + | name = "aes" | |
| 23 | + | version = "0.9.3" | |
| 24 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 25 | + | checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" | |
| 26 | + | dependencies = [ | |
| 27 | + | "cipher", | |
| 28 | + | "cpubits", | |
| 29 | + | "cpufeatures", | |
| 30 | + | "zeroize", | |
| 31 | + | ] | |
| 32 | + | ||
| 33 | + | [[package]] | |
| 34 | + | name = "aes-gcm" | |
| 35 | + | version = "0.11.1" | |
| 36 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 37 | + | checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" | |
| 38 | + | dependencies = [ | |
| 39 | + | "aead", | |
| 40 | + | "aes", | |
| 41 | + | "cipher", | |
| 42 | + | "ctr", | |
| 43 | + | "ctutils", | |
| 44 | + | "ghash", | |
| 45 | + | "zeroize", | |
| 46 | + | ] | |
| 47 | + | ||
| 48 | + | [[package]] | |
| 49 | + | name = "android_system_properties" | |
| 50 | + | version = "0.1.6" | |
| 51 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 52 | + | checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" | |
| 53 | + | dependencies = [ | |
| 54 | + | "libc", | |
| 55 | + | ] | |
| 56 | + | ||
| 57 | + | [[package]] | |
| 58 | + | name = "anyhow" | |
| 59 | + | version = "1.0.104" | |
| 60 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 61 | + | checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" | |
| 62 | + | ||
| 63 | + | [[package]] | |
| 64 | + | name = "argon2" | |
| 65 | + | version = "0.6.0" | |
| 66 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 67 | + | checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" | |
| 68 | + | dependencies = [ | |
| 69 | + | "base64ct", | |
| 70 | + | "blake2", | |
| 71 | + | "cpufeatures", | |
| 72 | + | "password-hash", | |
| 73 | + | ] | |
| 74 | + | ||
| 75 | + | [[package]] | |
| 76 | + | name = "atomic-waker" | |
| 77 | + | version = "1.1.2" | |
| 78 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 79 | + | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" | |
| 80 | + | ||
| 81 | + | [[package]] | |
| 82 | + | name = "autocfg" | |
| 83 | + | version = "1.5.1" | |
| 84 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 85 | + | checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" | |
| 86 | + | ||
| 87 | + | [[package]] | |
| 88 | + | name = "aws-lc-rs" | |
| 89 | + | version = "1.18.1" | |
| 90 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 91 | + | checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" | |
| 92 | + | dependencies = [ | |
| 93 | + | "aws-lc-sys", | |
| 94 | + | "untrusted 0.7.1", | |
| 95 | + | "zeroize", | |
| 96 | + | ] | |
| 97 | + | ||
| 98 | + | [[package]] | |
| 99 | + | name = "aws-lc-sys" | |
| 100 | + | version = "0.45.0" | |
| 101 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 102 | + | checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" | |
| 103 | + | dependencies = [ | |
| 104 | + | "cc", | |
| 105 | + | "cmake", | |
| 106 | + | "dunce", | |
| 107 | + | "fs_extra", | |
| 108 | + | "pkg-config", | |
| 109 | + | ] | |
| 110 | + | ||
| 111 | + | [[package]] | |
| 112 | + | name = "base16ct" | |
| 113 | + | version = "1.0.0" | |
| 114 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 115 | + | checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" | |
| 116 | + | ||
| 117 | + | [[package]] | |
| 118 | + | name = "base64" | |
| 119 | + | version = "0.23.1" | |
| 120 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 121 | + | checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" | |
| 122 | + | ||
| 123 | + | [[package]] | |
| 124 | + | name = "base64ct" | |
| 125 | + | version = "1.8.3" | |
| 126 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 127 | + | checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" | |
| 128 | + | ||
| 129 | + | [[package]] | |
| 130 | + | name = "bcrypt-pbkdf" | |
| 131 | + | version = "0.11.0" | |
| 132 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 133 | + | checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" | |
| 134 | + | dependencies = [ | |
| 135 | + | "blowfish", | |
| 136 | + | "pbkdf2", | |
| 137 | + | "sha2", | |
| 138 | + | ] | |
| 139 | + | ||
| 140 | + | [[package]] | |
| 141 | + | name = "bitflags" | |
| 142 | + | version = "2.13.2" | |
| 143 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 144 | + | checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" | |
| 145 | + | ||
| 146 | + | [[package]] | |
| 147 | + | name = "blake2" | |
| 148 | + | version = "0.11.0" | |
| 149 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 150 | + | checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" | |
| 151 | + | dependencies = [ | |
| 152 | + | "digest", | |
| 153 | + | ] | |
| 154 | + | ||
| 155 | + | [[package]] | |
| 156 | + | name = "block-buffer" | |
| 157 | + | version = "0.12.1" | |
| 158 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 159 | + | checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" | |
| 160 | + | dependencies = [ | |
| 161 | + | "hybrid-array", | |
| 162 | + | "zeroize", | |
| 163 | + | ] | |
| 164 | + | ||
| 165 | + | [[package]] | |
| 166 | + | name = "block-padding" | |
| 167 | + | version = "0.4.2" | |
| 168 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 169 | + | checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" | |
| 170 | + | dependencies = [ | |
| 171 | + | "hybrid-array", | |
| 172 | + | ] | |
| 173 | + | ||
| 174 | + | [[package]] | |
| 175 | + | name = "blowfish" | |
| 176 | + | version = "0.10.0" | |
| 177 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 178 | + | checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" | |
| 179 | + | dependencies = [ | |
| 180 | + | "byteorder", | |
| 181 | + | "cipher", | |
| 182 | + | ] | |
| 183 | + | ||
| 184 | + | [[package]] | |
| 185 | + | name = "bumpalo" | |
| 186 | + | version = "3.20.3" | |
| 187 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 188 | + | checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" | |
| 189 | + | ||
| 190 | + | [[package]] | |
| 191 | + | name = "byteorder" | |
| 192 | + | version = "1.5.0" | |
| 193 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 194 | + | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | |
| 195 | + | ||
| 196 | + | [[package]] | |
| 197 | + | name = "bytes" | |
| 198 | + | version = "1.12.1" | |
| 199 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 200 | + | checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" | |
| 201 | + | ||
| 202 | + | [[package]] | |
| 203 | + | name = "cbc" | |
| 204 | + | version = "0.2.1" | |
| 205 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 206 | + | checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" | |
| 207 | + | dependencies = [ | |
| 208 | + | "cipher", | |
| 209 | + | ] | |
| 210 | + | ||
| 211 | + | [[package]] | |
| 212 | + | name = "cc" | |
| 213 | + | version = "1.5.1" | |
| 214 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 215 | + | checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" | |
| 216 | + | dependencies = [ | |
| 217 | + | "find-msvc-tools", | |
| 218 | + | "jobserver", | |
| 219 | + | "libc", | |
| 220 | + | "shlex", | |
| 221 | + | ] | |
| 222 | + | ||
| 223 | + | [[package]] | |
| 224 | + | name = "cfg-if" | |
| 225 | + | version = "1.0.5" | |
| 226 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 227 | + | checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" | |
| 228 | + | ||
| 229 | + | [[package]] | |
| 230 | + | name = "cfg_aliases" | |
| 231 | + | version = "0.2.2" | |
| 232 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 233 | + | checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" | |
| 234 | + | ||
| 235 | + | [[package]] | |
| 236 | + | name = "chacha20" | |
| 237 | + | version = "0.10.2" | |
| 238 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 239 | + | checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" | |
| 240 | + | dependencies = [ | |
| 241 | + | "cfg-if", | |
| 242 | + | "cipher", | |
| 243 | + | "cpufeatures", | |
| 244 | + | "rand_core", | |
| 245 | + | "zeroize", | |
| 246 | + | ] | |
| 247 | + | ||
| 248 | + | [[package]] | |
| 249 | + | name = "chrono" | |
| 250 | + | version = "0.4.45" | |
| 251 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 252 | + | checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" | |
| 253 | + | dependencies = [ | |
| 254 | + | "iana-time-zone", | |
| 255 | + | "js-sys", | |
| 256 | + | "num-traits", | |
| 257 | + | "wasm-bindgen", | |
| 258 | + | "windows-link", | |
| 259 | + | ] | |
| 260 | + | ||
| 261 | + | [[package]] | |
| 262 | + | name = "cipher" | |
| 263 | + | version = "0.5.2" | |
| 264 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 265 | + | checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" | |
| 266 | + | dependencies = [ | |
| 267 | + | "block-buffer", | |
| 268 | + | "crypto-common", | |
| 269 | + | "inout", | |
| 270 | + | "zeroize", | |
| 271 | + | ] | |
| 272 | + | ||
| 273 | + | [[package]] | |
| 274 | + | name = "cmake" | |
| 275 | + | version = "0.1.58" | |
| 276 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 277 | + | checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" | |
| 278 | + | dependencies = [ | |
| 279 | + | "cc", | |
| 280 | + | ] | |
| 281 | + | ||
| 282 | + | [[package]] | |
| 283 | + | name = "cmov" | |
| 284 | + | version = "0.5.4" | |
| 285 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 286 | + | checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" | |
| 287 | + | ||
| 288 | + | [[package]] | |
| 289 | + | name = "combine" | |
| 290 | + | version = "4.6.8" | |
| 291 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 292 | + | checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" | |
| 293 | + | dependencies = [ | |
| 294 | + | "bytes", | |
| 295 | + | "memchr", | |
| 296 | + | ] | |
| 297 | + | ||
| 298 | + | [[package]] | |
| 299 | + | name = "const-oid" | |
| 300 | + | version = "0.10.2" | |
| 301 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 302 | + | checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" | |
| 303 | + | ||
| 304 | + | [[package]] | |
| 305 | + | name = "core-foundation" | |
| 306 | + | version = "0.9.4" | |
| 307 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 308 | + | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" | |
| 309 | + | dependencies = [ | |
| 310 | + | "core-foundation-sys", | |
| 311 | + | "libc", | |
| 312 | + | ] | |
| 313 | + | ||
| 314 | + | [[package]] | |
| 315 | + | name = "core-foundation" | |
| 316 | + | version = "0.10.1" | |
| 317 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 318 | + | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" | |
| 319 | + | dependencies = [ | |
| 320 | + | "core-foundation-sys", | |
| 321 | + | "libc", | |
| 322 | + | ] | |
| 323 | + | ||
| 324 | + | [[package]] | |
| 325 | + | name = "core-foundation-sys" | |
| 326 | + | version = "0.8.7" | |
| 327 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 328 | + | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" | |
| 329 | + | ||
| 330 | + | [[package]] | |
| 331 | + | name = "core_detect" | |
| 332 | + | version = "1.0.0" | |
| 333 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 334 | + | checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" | |
| 335 | + | ||
| 336 | + | [[package]] | |
| 337 | + | name = "cpubits" | |
| 338 | + | version = "0.1.1" | |
| 339 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 340 | + | checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" | |
| 341 | + | ||
| 342 | + | [[package]] | |
| 343 | + | name = "cpufeatures" | |
| 344 | + | version = "0.3.1" | |
| 345 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 346 | + | checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" | |
| 347 | + | dependencies = [ | |
| 348 | + | "libc", | |
| 349 | + | ] | |
| 350 | + | ||
| 351 | + | [[package]] | |
| 352 | + | name = "crc32fast" | |
| 353 | + | version = "1.5.2" | |
| 354 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 355 | + | checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" | |
| 356 | + | dependencies = [ | |
| 357 | + | "cfg-if", | |
| 358 | + | ] | |
| 359 | + | ||
| 360 | + | [[package]] | |
| 361 | + | name = "crypto-bigint" | |
| 362 | + | version = "0.7.5" | |
| 363 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 364 | + | checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" | |
| 365 | + | dependencies = [ | |
| 366 | + | "cpubits", | |
| 367 | + | "ctutils", | |
| 368 | + | "getrandom 0.4.3", | |
| 369 | + | "hybrid-array", | |
| 370 | + | "num-traits", | |
| 371 | + | "rand_core", | |
| 372 | + | "serdect", | |
| 373 | + | "subtle", | |
| 374 | + | "zeroize", | |
| 375 | + | ] | |
| 376 | + | ||
| 377 | + | [[package]] | |
| 378 | + | name = "crypto-common" | |
| 379 | + | version = "0.2.2" | |
| 380 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 381 | + | checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" | |
| 382 | + | dependencies = [ | |
| 383 | + | "getrandom 0.4.3", | |
| 384 | + | "hybrid-array", | |
| 385 | + | "rand_core", | |
| 386 | + | ] | |
| 387 | + | ||
| 388 | + | [[package]] | |
| 389 | + | name = "crypto-primes" | |
| 390 | + | version = "0.7.2" | |
| 391 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 392 | + | checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" | |
| 393 | + | dependencies = [ | |
| 394 | + | "crypto-bigint", | |
| 395 | + | "rand_core", | |
| 396 | + | ] | |
| 397 | + | ||
| 398 | + | [[package]] | |
| 399 | + | name = "ctr" | |
| 400 | + | version = "0.10.1" | |
| 401 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 402 | + | checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" | |
| 403 | + | dependencies = [ | |
| 404 | + | "cipher", | |
| 405 | + | ] | |
| 406 | + | ||
| 407 | + | [[package]] | |
| 408 | + | name = "ctutils" | |
| 409 | + | version = "0.4.2" | |
| 410 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 411 | + | checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" | |
| 412 | + | dependencies = [ | |
| 413 | + | "cmov", | |
| 414 | + | "subtle", | |
| 415 | + | ] | |
| 416 | + | ||
| 417 | + | [[package]] | |
| 418 | + | name = "curve25519-dalek" | |
| 419 | + | version = "5.0.0" | |
| 420 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 421 | + | checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" | |
| 422 | + | dependencies = [ | |
| 423 | + | "cfg-if", | |
| 424 | + | "cpufeatures", | |
| 425 | + | "curve25519-dalek-derive", | |
| 426 | + | "digest", | |
| 427 | + | "fiat-crypto", | |
| 428 | + | "rand_core", | |
| 429 | + | "rustc_version", | |
| 430 | + | "subtle", | |
| 431 | + | "zeroize", | |
| 432 | + | ] | |
| 433 | + | ||
| 434 | + | [[package]] | |
| 435 | + | name = "curve25519-dalek-derive" | |
| 436 | + | version = "0.1.1" | |
| 437 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 438 | + | checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" | |
| 439 | + | dependencies = [ | |
| 440 | + | "proc-macro2", | |
| 441 | + | "quote", | |
| 442 | + | "syn 2.0.119", | |
| 443 | + | ] | |
| 444 | + | ||
| 445 | + | [[package]] | |
| 446 | + | name = "data-encoding" | |
| 447 | + | version = "2.11.1" | |
| 448 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 449 | + | checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" | |
| 450 | + | ||
| 451 | + | [[package]] | |
| 452 | + | name = "delegate" | |
| 453 | + | version = "0.13.5" | |
| 454 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 455 | + | checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" | |
| 456 | + | dependencies = [ | |
| 457 | + | "proc-macro2", | |
| 458 | + | "quote", | |
| 459 | + | "syn 2.0.119", | |
| 460 | + | ] | |
| 461 | + | ||
| 462 | + | [[package]] | |
| 463 | + | name = "der" | |
| 464 | + | version = "0.8.2" | |
| 465 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 466 | + | checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" | |
| 467 | + | dependencies = [ | |
| 468 | + | "const-oid", | |
| 469 | + | "pem-rfc7468", | |
| 470 | + | "zeroize", | |
| 471 | + | ] | |
| 472 | + | ||
| 473 | + | [[package]] | |
| 474 | + | name = "des" | |
| 475 | + | version = "0.9.0" | |
| 476 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 477 | + | checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" | |
| 478 | + | dependencies = [ | |
| 479 | + | "cipher", | |
| 480 | + | ] | |
| 481 | + | ||
| 482 | + | [[package]] | |
| 483 | + | name = "digest" | |
| 484 | + | version = "0.11.3" | |
| 485 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 486 | + | checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" | |
| 487 | + | dependencies = [ | |
| 488 | + | "block-buffer", | |
| 489 | + | "const-oid", | |
| 490 | + | "crypto-common", | |
| 491 | + | "ctutils", | |
| 492 | + | ] | |
| 493 | + | ||
| 494 | + | [[package]] | |
| 495 | + | name = "displaydoc" | |
| 496 | + | version = "0.2.7" | |
| 497 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 498 | + | checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" | |
| 499 | + | dependencies = [ | |
| 500 | + | "proc-macro2", | |
| 501 | + | "quote", | |
| 502 | + | "syn 3.0.6", | |
| 503 | + | ] | |
| 504 | + | ||
| 505 | + | [[package]] | |
| 506 | + | name = "dunce" | |
| 507 | + | version = "1.0.5" | |
| 508 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 509 | + | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" | |
| 510 | + | ||
| 511 | + | [[package]] | |
| 512 | + | name = "ecdsa" | |
| 513 | + | version = "0.17.0" | |
| 514 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 515 | + | checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" | |
| 516 | + | dependencies = [ | |
| 517 | + | "der", | |
| 518 | + | "digest", | |
| 519 | + | "elliptic-curve", | |
| 520 | + | "rfc6979", | |
| 521 | + | "signature", | |
| 522 | + | "spki", | |
| 523 | + | "zeroize", | |
| 524 | + | ] | |
| 525 | + | ||
| 526 | + | [[package]] | |
| 527 | + | name = "ed25519" | |
| 528 | + | version = "3.0.0" | |
| 529 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 530 | + | checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" | |
| 531 | + | dependencies = [ | |
| 532 | + | "pkcs8", | |
| 533 | + | "signature", | |
| 534 | + | ] | |
| 535 | + | ||
| 536 | + | [[package]] | |
| 537 | + | name = "ed25519-dalek" | |
| 538 | + | version = "3.0.0" | |
| 539 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 540 | + | checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" | |
| 541 | + | dependencies = [ | |
| 542 | + | "curve25519-dalek", | |
| 543 | + | "ed25519", | |
| 544 | + | "rand_core", | |
| 545 | + | "serde", | |
| 546 | + | "sha2", | |
| 547 | + | "signature", | |
| 548 | + | "subtle", | |
| 549 | + | "zeroize", | |
| 550 | + | ] | |
| 551 | + | ||
| 552 | + | [[package]] | |
| 553 | + | name = "elliptic-curve" | |
| 554 | + | version = "0.14.1" | |
| 555 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 556 | + | checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" | |
| 557 | + | dependencies = [ | |
| 558 | + | "base16ct", | |
| 559 | + | "crypto-bigint", | |
| 560 | + | "crypto-common", | |
| 561 | + | "digest", | |
| 562 | + | "ff", | |
| 563 | + | "group", | |
| 564 | + | "hkdf", | |
| 565 | + | "hybrid-array", | |
| 566 | + | "pem-rfc7468", | |
| 567 | + | "pkcs8", | |
| 568 | + | "rand_core", | |
| 569 | + | "sec1", | |
| 570 | + | "subtle", | |
| 571 | + | "zeroize", | |
| 572 | + | ] | |
| 573 | + | ||
| 574 | + | [[package]] | |
| 575 | + | name = "encoding_rs" | |
| 576 | + | version = "0.8.42" | |
| 577 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 578 | + | checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" | |
| 579 | + | dependencies = [ | |
| 580 | + | "cfg-if", | |
| 581 | + | "core_detect", | |
| 582 | + | "multiversion_no_op", | |
| 583 | + | "rustversion", | |
| 584 | + | "scopeguard", | |
| 585 | + | "simdutf8", | |
| 586 | + | ] | |
| 587 | + | ||
| 588 | + | [[package]] | |
| 589 | + | name = "enum_dispatch" | |
| 590 | + | version = "0.3.13" | |
| 591 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 592 | + | checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" | |
| 593 | + | dependencies = [ | |
| 594 | + | "once_cell", | |
| 595 | + | "proc-macro2", | |
| 596 | + | "quote", | |
| 597 | + | "syn 2.0.119", | |
| 598 | + | ] | |
| 599 | + | ||
| 600 | + | [[package]] | |
| 601 | + | name = "equivalent" | |
| 602 | + | version = "1.0.2" | |
| 603 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 604 | + | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | |
| 605 | + | ||
| 606 | + | [[package]] | |
| 607 | + | name = "errno" | |
| 608 | + | version = "0.3.14" | |
| 609 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 610 | + | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 611 | + | dependencies = [ | |
| 612 | + | "libc", | |
| 613 | + | "windows-sys 0.61.2", | |
| 614 | + | ] | |
| 615 | + | ||
| 616 | + | [[package]] | |
| 617 | + | name = "ff" | |
| 618 | + | version = "0.14.0" | |
| 619 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 620 | + | checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" | |
| 621 | + | dependencies = [ | |
| 622 | + | "rand_core", | |
| 623 | + | "subtle", | |
| 624 | + | ] | |
| 625 | + | ||
| 626 | + | [[package]] | |
| 627 | + | name = "fiat-crypto" | |
| 628 | + | version = "0.3.0" | |
| 629 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 630 | + | checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" | |
| 631 | + | ||
| 632 | + | [[package]] | |
| 633 | + | name = "find-msvc-tools" | |
| 634 | + | version = "0.1.14" | |
| 635 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 636 | + | checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" | |
| 637 | + | ||
| 638 | + | [[package]] | |
| 639 | + | name = "flate2" | |
| 640 | + | version = "1.1.10" | |
| 641 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 642 | + | checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" | |
| 643 | + | dependencies = [ | |
| 644 | + | "crc32fast", | |
| 645 | + | "miniz_oxide", | |
| 646 | + | "zlib-rs", | |
| 647 | + | ] | |
| 648 | + | ||
| 649 | + | [[package]] | |
| 650 | + | name = "fnv" | |
| 651 | + | version = "1.0.7" | |
| 652 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 653 | + | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | |
| 654 | + | ||
| 655 | + | [[package]] | |
| 656 | + | name = "form_urlencoded" | |
| 657 | + | version = "1.2.2" | |
| 658 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 659 | + | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" | |
| 660 | + | dependencies = [ | |
| 661 | + | "percent-encoding", | |
| 662 | + | ] | |
| 663 | + | ||
| 664 | + | [[package]] | |
| 665 | + | name = "fs_extra" | |
| 666 | + | version = "1.3.0" | |
| 667 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 668 | + | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" | |
| 669 | + | ||
| 670 | + | [[package]] | |
| 671 | + | name = "futures" | |
| 672 | + | version = "0.3.34" | |
| 673 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 674 | + | checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" | |
| 675 | + | dependencies = [ | |
| 676 | + | "futures-channel", | |
| 677 | + | "futures-core", | |
| 678 | + | "futures-executor", | |
| 679 | + | "futures-io", | |
| 680 | + | "futures-sink", | |
| 681 | + | "futures-task", | |
| 682 | + | "futures-util", | |
| 683 | + | ] | |
| 684 | + | ||
| 685 | + | [[package]] | |
| 686 | + | name = "futures-channel" | |
| 687 | + | version = "0.3.34" | |
| 688 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 689 | + | checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" | |
| 690 | + | dependencies = [ | |
| 691 | + | "futures-core", | |
| 692 | + | "futures-sink", | |
| 693 | + | ] | |
| 694 | + | ||
| 695 | + | [[package]] | |
| 696 | + | name = "futures-core" | |
| 697 | + | version = "0.3.34" | |
| 698 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 699 | + | checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" | |
| 700 | + | ||
| 701 | + | [[package]] | |
| 702 | + | name = "futures-executor" | |
| 703 | + | version = "0.3.34" | |
| 704 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 705 | + | checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" | |
| 706 | + | dependencies = [ | |
| 707 | + | "futures-core", | |
| 708 | + | "futures-task", | |
| 709 | + | "futures-util", | |
| 710 | + | ] | |
| 711 | + | ||
| 712 | + | [[package]] | |
| 713 | + | name = "futures-io" | |
| 714 | + | version = "0.3.34" | |
| 715 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 716 | + | checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" | |
| 717 | + | ||
| 718 | + | [[package]] | |
| 719 | + | name = "futures-macro" | |
| 720 | + | version = "0.3.34" | |
| 721 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 722 | + | checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" | |
| 723 | + | dependencies = [ | |
| 724 | + | "proc-macro2", | |
| 725 | + | "quote", | |
| 726 | + | "syn 3.0.6", | |
| 727 | + | ] | |
| 728 | + | ||
| 729 | + | [[package]] | |
| 730 | + | name = "futures-sink" | |
| 731 | + | version = "0.3.34" | |
| 732 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 733 | + | checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" | |
| 734 | + | ||
| 735 | + | [[package]] | |
| 736 | + | name = "futures-task" | |
| 737 | + | version = "0.3.34" | |
| 738 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 739 | + | checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" | |
| 740 | + | ||
| 741 | + | [[package]] | |
| 742 | + | name = "futures-util" | |
| 743 | + | version = "0.3.34" | |
| 744 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 745 | + | checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" | |
| 746 | + | dependencies = [ | |
| 747 | + | "futures-channel", | |
| 748 | + | "futures-core", | |
| 749 | + | "futures-io", | |
| 750 | + | "futures-macro", | |
| 751 | + | "futures-sink", | |
| 752 | + | "futures-task", | |
| 753 | + | "memchr", | |
| 754 | + | "pin-project-lite", | |
| 755 | + | "slab", | |
| 756 | + | ] | |
| 757 | + | ||
| 758 | + | [[package]] | |
| 759 | + | name = "g1t" | |
| 760 | + | version = "0.1.0" | |
| 761 | + | ||
| 762 | + | [[package]] | |
| 763 | + | name = "g1t-sshd" | |
| 764 | + | version = "0.1.0" | |
| 765 | + | dependencies = [ | |
| 766 | + | "anyhow", | |
| 767 | + | "flate2", | |
| 768 | + | "futures-util", | |
| 769 | + | "reqwest", | |
| 770 | + | "russh", | |
| 771 | + | "serde", | |
| 772 | + | "serde_json", | |
| 773 | + | "tokio", | |
| 774 | + | "tokio-tungstenite", | |
| 775 | + | ] | |
| 776 | + | ||
| 777 | + | [[package]] | |
| 778 | + | name = "generic-array" | |
| 779 | + | version = "0.14.9" | |
| 780 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 781 | + | checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" | |
| 782 | + | dependencies = [ | |
| 783 | + | "typenum", | |
| 784 | + | "version_check", | |
| 785 | + | ] | |
| 786 | + | ||
| 787 | + | [[package]] | |
| 788 | + | name = "generic-array" | |
| 789 | + | version = "1.4.5" | |
| 790 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 791 | + | checksum = "337d46834ee672ab3e48caca2cb0c78cc174fb12b3a68d0d88f99a0519a5e36e" | |
| 792 | + | dependencies = [ | |
| 793 | + | "generic-array 0.14.9", | |
| 794 | + | "rustversion", | |
| 795 | + | "typenum", | |
| 796 | + | ] | |
| 797 | + | ||
| 798 | + | [[package]] | |
| 799 | + | name = "getrandom" | |
| 800 | + | version = "0.2.17" | |
| 801 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 802 | + | checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" | |
| 803 | + | dependencies = [ | |
| 804 | + | "cfg-if", | |
| 805 | + | "js-sys", | |
| 806 | + | "libc", | |
| 807 | + | "wasi", | |
| 808 | + | "wasm-bindgen", | |
| 809 | + | ] | |
| 810 | + | ||
| 811 | + | [[package]] | |
| 812 | + | name = "getrandom" | |
| 813 | + | version = "0.4.3" | |
| 814 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 815 | + | checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" | |
| 816 | + | dependencies = [ | |
| 817 | + | "cfg-if", | |
| 818 | + | "js-sys", | |
| 819 | + | "libc", | |
| 820 | + | "r-efi", | |
| 821 | + | "rand_core", | |
| 822 | + | "wasm-bindgen", | |
| 823 | + | ] | |
| 824 | + | ||
| 825 | + | [[package]] | |
| 826 | + | name = "ghash" | |
| 827 | + | version = "0.6.0" | |
| 828 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 829 | + | checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" | |
| 830 | + | dependencies = [ | |
| 831 | + | "polyval", | |
| 832 | + | "zeroize", | |
| 833 | + | ] | |
| 834 | + | ||
| 835 | + | [[package]] | |
| 836 | + | name = "group" | |
| 837 | + | version = "0.14.0" | |
| 838 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 839 | + | checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" | |
| 840 | + | dependencies = [ | |
| 841 | + | "ff", | |
| 842 | + | "rand_core", | |
| 843 | + | "subtle", | |
| 844 | + | ] | |
| 845 | + | ||
| 846 | + | [[package]] | |
| 847 | + | name = "h2" | |
| 848 | + | version = "0.4.19" | |
| 849 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 850 | + | checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" | |
| 851 | + | dependencies = [ | |
| 852 | + | "atomic-waker", | |
| 853 | + | "bytes", | |
| 854 | + | "fnv", | |
| 855 | + | "futures-core", | |
| 856 | + | "futures-sink", | |
| 857 | + | "http", | |
| 858 | + | "indexmap", | |
| 859 | + | "slab", | |
| 860 | + | "tokio", | |
| 861 | + | "tokio-util", | |
| 862 | + | "tracing", | |
| 863 | + | ] | |
| 864 | + | ||
| 865 | + | [[package]] | |
| 866 | + | name = "hashbrown" | |
| 867 | + | version = "0.17.1" | |
| 868 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 869 | + | checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" | |
| 870 | + | ||
| 871 | + | [[package]] | |
| 872 | + | name = "hex" | |
| 873 | + | version = "0.4.3" | |
| 874 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 875 | + | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" | |
| 876 | + | ||
| 877 | + | [[package]] | |
| 878 | + | name = "hex-literal" | |
| 879 | + | version = "1.1.0" | |
| 880 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 881 | + | checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" | |
| 882 | + | ||
| 883 | + | [[package]] | |
| 884 | + | name = "hkdf" | |
| 885 | + | version = "0.13.0" | |
| 886 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 887 | + | checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" | |
| 888 | + | dependencies = [ | |
| 889 | + | "hmac", | |
| 890 | + | ] | |
| 891 | + | ||
| 892 | + | [[package]] | |
| 893 | + | name = "hmac" | |
| 894 | + | version = "0.13.0" | |
| 895 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 896 | + | checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" | |
| 897 | + | dependencies = [ | |
| 898 | + | "digest", | |
| 899 | + | ] | |
| 900 | + | ||
| 901 | + | [[package]] | |
| 902 | + | name = "http" | |
| 903 | + | version = "1.5.0" | |
| 904 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 905 | + | checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" | |
| 906 | + | dependencies = [ | |
| 907 | + | "bytes", | |
| 908 | + | "itoa", | |
| 909 | + | ] | |
| 910 | + | ||
| 911 | + | [[package]] | |
| 912 | + | name = "http-body" | |
| 913 | + | version = "1.1.0" | |
| 914 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 915 | + | checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" | |
| 916 | + | dependencies = [ | |
| 917 | + | "bytes", | |
| 918 | + | "http", | |
| 919 | + | ] | |
| 920 | + | ||
| 921 | + | [[package]] | |
| 922 | + | name = "http-body-util" | |
| 923 | + | version = "0.1.5" | |
| 924 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 925 | + | checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" | |
| 926 | + | dependencies = [ | |
| 927 | + | "bytes", | |
| 928 | + | "futures-core", | |
| 929 | + | "http", | |
| 930 | + | "http-body", | |
| 931 | + | "pin-project-lite", | |
| 932 | + | ] | |
| 933 | + | ||
| 934 | + | [[package]] | |
| 935 | + | name = "httparse" | |
| 936 | + | version = "1.10.1" | |
| 937 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 938 | + | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | |
| 939 | + | ||
| 940 | + | [[package]] | |
| 941 | + | name = "hybrid-array" | |
| 942 | + | version = "0.4.15" | |
| 943 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 944 | + | checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" | |
| 945 | + | dependencies = [ | |
| 946 | + | "ctutils", | |
| 947 | + | "subtle", | |
| 948 | + | "typenum", | |
| 949 | + | "zeroize", | |
| 950 | + | ] | |
| 951 | + | ||
| 952 | + | [[package]] | |
| 953 | + | name = "hyper" | |
| 954 | + | version = "1.11.1" | |
| 955 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 956 | + | checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" | |
| 957 | + | dependencies = [ | |
| 958 | + | "atomic-waker", | |
| 959 | + | "bytes", | |
| 960 | + | "futures-channel", | |
| 961 | + | "futures-core", | |
| 962 | + | "h2", | |
| 963 | + | "http", | |
| 964 | + | "http-body", | |
| 965 | + | "httparse", | |
| 966 | + | "itoa", | |
| 967 | + | "pin-project-lite", | |
| 968 | + | "smallvec", | |
| 969 | + | "tokio", | |
| 970 | + | "want", | |
| 971 | + | ] | |
| 972 | + | ||
| 973 | + | [[package]] | |
| 974 | + | name = "hyper-rustls" | |
| 975 | + | version = "0.27.10" | |
| 976 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 977 | + | checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" | |
| 978 | + | dependencies = [ | |
| 979 | + | "http", | |
| 980 | + | "hyper", | |
| 981 | + | "hyper-util", | |
| 982 | + | "rustls", | |
| 983 | + | "tokio", | |
| 984 | + | "tokio-rustls", | |
| 985 | + | "tower-service", | |
| 986 | + | ] | |
| 987 | + | ||
| 988 | + | [[package]] | |
| 989 | + | name = "hyper-util" | |
| 990 | + | version = "0.1.21" | |
| 991 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 992 | + | checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" | |
| 993 | + | dependencies = [ | |
| 994 | + | "base64", | |
| 995 | + | "bytes", | |
| 996 | + | "futures-channel", | |
| 997 | + | "futures-util", | |
| 998 | + | "http", | |
| 999 | + | "http-body", | |
| 1000 | + | "httparse", | |
| 1001 | + | "hyper", | |
| 1002 | + | "ipnet", | |
| 1003 | + | "libc", | |
| 1004 | + | "percent-encoding", | |
| 1005 | + | "pin-project-lite", | |
| 1006 | + | "socket2", | |
| 1007 | + | "system-configuration", | |
| 1008 | + | "tokio", | |
| 1009 | + | "tower-service", | |
| 1010 | + | "tracing", | |
| 1011 | + | "windows-registry", | |
| 1012 | + | ] | |
| 1013 | + | ||
| 1014 | + | [[package]] | |
| 1015 | + | name = "iana-time-zone" | |
| 1016 | + | version = "0.1.65" | |
| 1017 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1018 | + | checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" | |
| 1019 | + | dependencies = [ | |
| 1020 | + | "android_system_properties", | |
| 1021 | + | "core-foundation-sys", | |
| 1022 | + | "iana-time-zone-haiku", | |
| 1023 | + | "js-sys", | |
| 1024 | + | "log", | |
| 1025 | + | "wasm-bindgen", | |
| 1026 | + | "windows-core", | |
| 1027 | + | ] | |
| 1028 | + | ||
| 1029 | + | [[package]] | |
| 1030 | + | name = "iana-time-zone-haiku" | |
| 1031 | + | version = "0.1.2" | |
| 1032 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1033 | + | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" | |
| 1034 | + | dependencies = [ | |
| 1035 | + | "cc", | |
| 1036 | + | ] | |
| 1037 | + | ||
| 1038 | + | [[package]] | |
| 1039 | + | name = "icu_collections" | |
| 1040 | + | version = "2.3.0" | |
| 1041 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1042 | + | checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" | |
| 1043 | + | dependencies = [ | |
| 1044 | + | "displaydoc", | |
| 1045 | + | "potential_utf", | |
| 1046 | + | "utf8_iter", | |
| 1047 | + | "yoke", | |
| 1048 | + | "zerofrom", | |
| 1049 | + | "zerovec", | |
| 1050 | + | ] | |
| 1051 | + | ||
| 1052 | + | [[package]] | |
| 1053 | + | name = "icu_locale_core" | |
| 1054 | + | version = "2.3.0" | |
| 1055 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1056 | + | checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" | |
| 1057 | + | dependencies = [ | |
| 1058 | + | "displaydoc", | |
| 1059 | + | "litemap", | |
| 1060 | + | "tinystr", | |
| 1061 | + | "writeable", | |
| 1062 | + | "zerovec", | |
| 1063 | + | ] | |
| 1064 | + | ||
| 1065 | + | [[package]] | |
| 1066 | + | name = "icu_normalizer" | |
| 1067 | + | version = "2.3.0" | |
| 1068 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1069 | + | checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" | |
| 1070 | + | dependencies = [ | |
| 1071 | + | "icu_collections", | |
| 1072 | + | "icu_normalizer_data", | |
| 1073 | + | "icu_properties", | |
| 1074 | + | "icu_provider", | |
| 1075 | + | "smallvec", | |
| 1076 | + | "zerovec", | |
| 1077 | + | ] | |
| 1078 | + | ||
| 1079 | + | [[package]] | |
| 1080 | + | name = "icu_normalizer_data" | |
| 1081 | + | version = "2.3.0" | |
| 1082 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1083 | + | checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" | |
| 1084 | + | ||
| 1085 | + | [[package]] | |
| 1086 | + | name = "icu_properties" | |
| 1087 | + | version = "2.3.0" | |
| 1088 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1089 | + | checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" | |
| 1090 | + | dependencies = [ | |
| 1091 | + | "displaydoc", | |
| 1092 | + | "icu_collections", | |
| 1093 | + | "icu_locale_core", | |
| 1094 | + | "icu_properties_data", | |
| 1095 | + | "icu_provider", | |
| 1096 | + | "zerotrie", | |
| 1097 | + | "zerovec", | |
| 1098 | + | ] | |
| 1099 | + | ||
| 1100 | + | [[package]] | |
| 1101 | + | name = "icu_properties_data" | |
| 1102 | + | version = "2.3.0" | |
| 1103 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1104 | + | checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" | |
| 1105 | + | ||
| 1106 | + | [[package]] | |
| 1107 | + | name = "icu_provider" | |
| 1108 | + | version = "2.3.1" | |
| 1109 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1110 | + | checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" | |
| 1111 | + | dependencies = [ | |
| 1112 | + | "displaydoc", | |
| 1113 | + | "icu_locale_core", | |
| 1114 | + | "writeable", | |
| 1115 | + | "yoke", | |
| 1116 | + | "zerofrom", | |
| 1117 | + | "zerotrie", | |
| 1118 | + | "zerovec", | |
| 1119 | + | ] | |
| 1120 | + | ||
| 1121 | + | [[package]] | |
| 1122 | + | name = "idna" | |
| 1123 | + | version = "1.1.0" | |
| 1124 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1125 | + | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" | |
| 1126 | + | dependencies = [ | |
| 1127 | + | "idna_adapter", | |
| 1128 | + | "smallvec", | |
| 1129 | + | "utf8_iter", | |
| 1130 | + | ] | |
| 1131 | + | ||
| 1132 | + | [[package]] | |
| 1133 | + | name = "idna_adapter" | |
| 1134 | + | version = "1.2.2" | |
| 1135 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1136 | + | checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" | |
| 1137 | + | dependencies = [ | |
| 1138 | + | "icu_normalizer", | |
| 1139 | + | "icu_properties", | |
| 1140 | + | ] | |
| 1141 | + | ||
| 1142 | + | [[package]] | |
| 1143 | + | name = "indexmap" | |
| 1144 | + | version = "2.14.2" | |
| 1145 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1146 | + | checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" | |
| 1147 | + | dependencies = [ | |
| 1148 | + | "equivalent", | |
| 1149 | + | "hashbrown", | |
| 1150 | + | ] | |
| 1151 | + | ||
| 1152 | + | [[package]] | |
| 1153 | + | name = "inout" | |
| 1154 | + | version = "0.2.2" | |
| 1155 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1156 | + | checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" | |
| 1157 | + | dependencies = [ | |
| 1158 | + | "block-padding", | |
| 1159 | + | "hybrid-array", | |
| 1160 | + | ] | |
| 1161 | + | ||
| 1162 | + | [[package]] | |
| 1163 | + | name = "ipnet" | |
| 1164 | + | version = "2.12.2" | |
| 1165 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1166 | + | checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" | |
| 1167 | + | ||
| 1168 | + | [[package]] | |
| 1169 | + | name = "itoa" | |
| 1170 | + | version = "1.0.18" | |
| 1171 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1172 | + | checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" | |
| 1173 | + | ||
| 1174 | + | [[package]] | |
| 1175 | + | name = "jni" | |
| 1176 | + | version = "0.22.4" | |
| 1177 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1178 | + | checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" | |
| 1179 | + | dependencies = [ | |
| 1180 | + | "cfg-if", | |
| 1181 | + | "combine", | |
| 1182 | + | "jni-macros", | |
| 1183 | + | "jni-sys", | |
| 1184 | + | "log", | |
| 1185 | + | "simd_cesu8", | |
| 1186 | + | "thiserror", | |
| 1187 | + | "walkdir", | |
| 1188 | + | "windows-link", | |
| 1189 | + | ] | |
| 1190 | + | ||
| 1191 | + | [[package]] | |
| 1192 | + | name = "jni-macros" | |
| 1193 | + | version = "0.22.4" | |
| 1194 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1195 | + | checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" | |
| 1196 | + | dependencies = [ | |
| 1197 | + | "proc-macro2", | |
| 1198 | + | "quote", | |
| 1199 | + | "rustc_version", | |
| 1200 | + | "simd_cesu8", | |
| 1201 | + | "syn 2.0.119", | |
| 1202 | + | ] | |
| 1203 | + | ||
| 1204 | + | [[package]] | |
| 1205 | + | name = "jni-sys" | |
| 1206 | + | version = "0.4.1" | |
| 1207 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1208 | + | checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" | |
| 1209 | + | dependencies = [ | |
| 1210 | + | "jni-sys-macros", | |
| 1211 | + | ] | |
| 1212 | + | ||
| 1213 | + | [[package]] | |
| 1214 | + | name = "jni-sys-macros" | |
| 1215 | + | version = "0.4.1" | |
| 1216 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1217 | + | checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" | |
| 1218 | + | dependencies = [ | |
| 1219 | + | "quote", | |
| 1220 | + | "syn 2.0.119", | |
| 1221 | + | ] | |
| 1222 | + | ||
| 1223 | + | [[package]] | |
| 1224 | + | name = "jobserver" | |
| 1225 | + | version = "0.1.35" | |
| 1226 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1227 | + | checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" | |
| 1228 | + | dependencies = [ | |
| 1229 | + | "getrandom 0.4.3", | |
| 1230 | + | "libc", | |
| 1231 | + | ] | |
| 1232 | + | ||
| 1233 | + | [[package]] | |
| 1234 | + | name = "js-sys" | |
| 1235 | + | version = "0.3.106" | |
| 1236 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1237 | + | checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" | |
| 1238 | + | dependencies = [ | |
| 1239 | + | "cfg-if", | |
| 1240 | + | "futures-util", | |
| 1241 | + | "wasm-bindgen", | |
| 1242 | + | ] | |
| 1243 | + | ||
| 1244 | + | [[package]] | |
| 1245 | + | name = "keccak" | |
| 1246 | + | version = "0.2.2" | |
| 1247 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1248 | + | checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" | |
| 1249 | + | dependencies = [ | |
| 1250 | + | "cfg-if", | |
| 1251 | + | "cpufeatures", | |
| 1252 | + | ] | |
| 1253 | + | ||
| 1254 | + | [[package]] | |
| 1255 | + | name = "kem" | |
| 1256 | + | version = "0.3.0" | |
| 1257 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1258 | + | checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" | |
| 1259 | + | dependencies = [ | |
| 1260 | + | "crypto-common", | |
| 1261 | + | "rand_core", | |
| 1262 | + | ] | |
| 1263 | + | ||
| 1264 | + | [[package]] | |
| 1265 | + | name = "libc" | |
| 1266 | + | version = "0.2.189" | |
| 1267 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1268 | + | checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" | |
| 1269 | + | ||
| 1270 | + | [[package]] | |
| 1271 | + | name = "litemap" | |
| 1272 | + | version = "0.8.3" | |
| 1273 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1274 | + | checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" | |
| 1275 | + | ||
| 1276 | + | [[package]] | |
| 1277 | + | name = "lock_api" | |
| 1278 | + | version = "0.4.14" | |
| 1279 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1280 | + | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" | |
| 1281 | + | dependencies = [ | |
| 1282 | + | "scopeguard", | |
| 1283 | + | ] | |
| 1284 | + | ||
| 1285 | + | [[package]] | |
| 1286 | + | name = "log" | |
| 1287 | + | version = "0.4.34" | |
| 1288 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1289 | + | checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" | |
| 1290 | + | ||
| 1291 | + | [[package]] | |
| 1292 | + | name = "lru-slab" | |
| 1293 | + | version = "0.1.3" | |
| 1294 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1295 | + | checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" | |
| 1296 | + | ||
| 1297 | + | [[package]] | |
| 1298 | + | name = "md5" | |
| 1299 | + | version = "0.8.1" | |
| 1300 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1301 | + | checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" | |
| 1302 | + | ||
| 1303 | + | [[package]] | |
| 1304 | + | name = "memchr" | |
| 1305 | + | version = "2.8.3" | |
| 1306 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1307 | + | checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" | |
| 1308 | + | ||
| 1309 | + | [[package]] | |
| 1310 | + | name = "mime" | |
| 1311 | + | version = "0.3.17" | |
| 1312 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1313 | + | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" | |
| 1314 | + | ||
| 1315 | + | [[package]] | |
| 1316 | + | name = "miniz_oxide" | |
| 1317 | + | version = "0.9.1" | |
| 1318 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1319 | + | checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" | |
| 1320 | + | dependencies = [ | |
| 1321 | + | "adler2", | |
| 1322 | + | "simd-adler32", | |
| 1323 | + | ] | |
| 1324 | + | ||
| 1325 | + | [[package]] | |
| 1326 | + | name = "mio" | |
| 1327 | + | version = "1.2.3" | |
| 1328 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1329 | + | checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" | |
| 1330 | + | dependencies = [ | |
| 1331 | + | "libc", | |
| 1332 | + | "wasi", | |
| 1333 | + | "windows-sys 0.61.2", | |
| 1334 | + | ] | |
| 1335 | + | ||
| 1336 | + | [[package]] | |
| 1337 | + | name = "ml-kem" | |
| 1338 | + | version = "0.3.2" | |
| 1339 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1340 | + | checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" | |
| 1341 | + | dependencies = [ | |
| 1342 | + | "hybrid-array", | |
| 1343 | + | "kem", | |
| 1344 | + | "module-lattice", | |
| 1345 | + | "pkcs8", | |
| 1346 | + | "rand_core", | |
| 1347 | + | "sha3 0.11.0", | |
| 1348 | + | ] | |
| 1349 | + | ||
| 1350 | + | [[package]] | |
| 1351 | + | name = "module-lattice" | |
| 1352 | + | version = "0.2.3" | |
| 1353 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1354 | + | checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" | |
| 1355 | + | dependencies = [ | |
| 1356 | + | "ctutils", | |
| 1357 | + | "hybrid-array", | |
| 1358 | + | "num-traits", | |
| 1359 | + | ] | |
| 1360 | + | ||
| 1361 | + | [[package]] | |
| 1362 | + | name = "multiversion_no_op" | |
| 1363 | + | version = "1.0.0" | |
| 1364 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1365 | + | checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" | |
| 1366 | + | ||
| 1367 | + | [[package]] | |
| 1368 | + | name = "nix" | |
| 1369 | + | version = "0.31.3" | |
| 1370 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1371 | + | checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" | |
| 1372 | + | dependencies = [ | |
| 1373 | + | "bitflags", | |
| 1374 | + | "cfg-if", | |
| 1375 | + | "cfg_aliases", | |
| 1376 | + | "libc", | |
| 1377 | + | ] | |
| 1378 | + | ||
| 1379 | + | [[package]] | |
| 1380 | + | name = "num-bigint" | |
| 1381 | + | version = "0.5.1" | |
| 1382 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1383 | + | checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0" | |
| 1384 | + | dependencies = [ | |
| 1385 | + | "num-integer", | |
| 1386 | + | "num-traits", | |
| 1387 | + | "rand", | |
| 1388 | + | "rand_core", | |
| 1389 | + | ] | |
| 1390 | + | ||
| 1391 | + | [[package]] | |
| 1392 | + | name = "num-integer" | |
| 1393 | + | version = "0.1.47" | |
| 1394 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1395 | + | checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" | |
| 1396 | + | dependencies = [ | |
| 1397 | + | "num-traits", | |
| 1398 | + | ] | |
| 1399 | + | ||
| 1400 | + | [[package]] | |
| 1401 | + | name = "num-traits" | |
| 1402 | + | version = "0.2.19" | |
| 1403 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1404 | + | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" | |
| 1405 | + | dependencies = [ | |
| 1406 | + | "autocfg", | |
| 1407 | + | ] | |
| 1408 | + | ||
| 1409 | + | [[package]] | |
| 1410 | + | name = "once_cell" | |
| 1411 | + | version = "1.21.4" | |
| 1412 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1413 | + | checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" | |
| 1414 | + | ||
| 1415 | + | [[package]] | |
| 1416 | + | name = "openssl-probe" | |
| 1417 | + | version = "0.2.1" | |
| 1418 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1419 | + | checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" | |
| 1420 | + | ||
| 1421 | + | [[package]] | |
| 1422 | + | name = "p256" | |
| 1423 | + | version = "0.14.0" | |
| 1424 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1425 | + | checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" | |
| 1426 | + | dependencies = [ | |
| 1427 | + | "ecdsa", | |
| 1428 | + | "elliptic-curve", | |
| 1429 | + | "primefield", | |
| 1430 | + | "primeorder", | |
| 1431 | + | "sha2", | |
| 1432 | + | ] | |
| 1433 | + | ||
| 1434 | + | [[package]] | |
| 1435 | + | name = "p384" | |
| 1436 | + | version = "0.14.0" | |
| 1437 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1438 | + | checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" | |
| 1439 | + | dependencies = [ | |
| 1440 | + | "ecdsa", | |
| 1441 | + | "elliptic-curve", | |
| 1442 | + | "fiat-crypto", | |
| 1443 | + | "primefield", | |
| 1444 | + | "primeorder", | |
| 1445 | + | "sha2", | |
| 1446 | + | ] | |
| 1447 | + | ||
| 1448 | + | [[package]] | |
| 1449 | + | name = "p521" | |
| 1450 | + | version = "0.14.0" | |
| 1451 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1452 | + | checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449" | |
| 1453 | + | dependencies = [ | |
| 1454 | + | "base16ct", | |
| 1455 | + | "ecdsa", | |
| 1456 | + | "elliptic-curve", | |
| 1457 | + | "primefield", | |
| 1458 | + | "primeorder", | |
| 1459 | + | "sha2", | |
| 1460 | + | ] | |
| 1461 | + | ||
| 1462 | + | [[package]] | |
| 1463 | + | name = "pageant" | |
| 1464 | + | version = "0.2.3" | |
| 1465 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1466 | + | checksum = "6d8eab09a361a4afe0b1668be978cd97e4f052e927a92b0b608cf902965d49ce" | |
| 1467 | + | dependencies = [ | |
| 1468 | + | "base16ct", | |
| 1469 | + | "byteorder", | |
| 1470 | + | "bytes", | |
| 1471 | + | "delegate", | |
| 1472 | + | "futures", | |
| 1473 | + | "log", | |
| 1474 | + | "rand", | |
| 1475 | + | "sha2", | |
| 1476 | + | "thiserror", | |
| 1477 | + | "tokio", | |
| 1478 | + | "windows", | |
| 1479 | + | "windows-strings", | |
| 1480 | + | ] | |
| 1481 | + | ||
| 1482 | + | [[package]] | |
| 1483 | + | name = "parking_lot" | |
| 1484 | + | version = "0.12.5" | |
| 1485 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1486 | + | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" | |
| 1487 | + | dependencies = [ | |
| 1488 | + | "lock_api", | |
| 1489 | + | "parking_lot_core", | |
| 1490 | + | ] | |
| 1491 | + | ||
| 1492 | + | [[package]] | |
| 1493 | + | name = "parking_lot_core" | |
| 1494 | + | version = "0.9.12" | |
| 1495 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1496 | + | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" | |
| 1497 | + | dependencies = [ | |
| 1498 | + | "cfg-if", | |
| 1499 | + | "libc", | |
| 1500 | + | "redox_syscall", | |
| 1501 | + | "smallvec", | |
| 1502 | + | "windows-link", | |
| 1503 | + | ] | |
| 1504 | + | ||
| 1505 | + | [[package]] | |
| 1506 | + | name = "password-hash" | |
| 1507 | + | version = "0.6.1" | |
| 1508 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1509 | + | checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" | |
| 1510 | + | dependencies = [ | |
| 1511 | + | "phc", | |
| 1512 | + | ] | |
| 1513 | + | ||
| 1514 | + | [[package]] | |
| 1515 | + | name = "pbkdf2" | |
| 1516 | + | version = "0.13.0" | |
| 1517 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1518 | + | checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" | |
| 1519 | + | dependencies = [ | |
| 1520 | + | "digest", | |
| 1521 | + | "hmac", | |
| 1522 | + | ] | |
| 1523 | + | ||
| 1524 | + | [[package]] | |
| 1525 | + | name = "pem-rfc7468" | |
| 1526 | + | version = "1.0.0" | |
| 1527 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1528 | + | checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" | |
| 1529 | + | dependencies = [ | |
| 1530 | + | "base64ct", | |
| 1531 | + | ] | |
| 1532 | + | ||
| 1533 | + | [[package]] | |
| 1534 | + | name = "percent-encoding" | |
| 1535 | + | version = "2.3.2" | |
| 1536 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1537 | + | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" | |
| 1538 | + | ||
| 1539 | + | [[package]] | |
| 1540 | + | name = "phc" | |
| 1541 | + | version = "0.6.1" | |
| 1542 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1543 | + | checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" | |
| 1544 | + | dependencies = [ | |
| 1545 | + | "base64ct", | |
| 1546 | + | "ctutils", | |
| 1547 | + | ] | |
| 1548 | + | ||
| 1549 | + | [[package]] | |
| 1550 | + | name = "pin-project-lite" | |
| 1551 | + | version = "0.2.17" | |
| 1552 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1553 | + | checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" | |
| 1554 | + | ||
| 1555 | + | [[package]] | |
| 1556 | + | name = "pkcs1" | |
| 1557 | + | version = "0.8.0-rc.4" | |
| 1558 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1559 | + | checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" | |
| 1560 | + | dependencies = [ | |
| 1561 | + | "der", | |
| 1562 | + | "spki", | |
| 1563 | + | ] | |
| 1564 | + | ||
| 1565 | + | [[package]] | |
| 1566 | + | name = "pkcs5" | |
| 1567 | + | version = "0.8.1" | |
| 1568 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1569 | + | checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca" | |
| 1570 | + | dependencies = [ | |
| 1571 | + | "aes", | |
| 1572 | + | "aes-gcm", | |
| 1573 | + | "cbc", | |
| 1574 | + | "der", | |
| 1575 | + | "pbkdf2", | |
| 1576 | + | "rand_core", | |
| 1577 | + | "scrypt", | |
| 1578 | + | "sha2", | |
| 1579 | + | "spki", | |
| 1580 | + | ] | |
| 1581 | + | ||
| 1582 | + | [[package]] | |
| 1583 | + | name = "pkcs8" | |
| 1584 | + | version = "0.11.0" | |
| 1585 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1586 | + | checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" | |
| 1587 | + | dependencies = [ | |
| 1588 | + | "der", | |
| 1589 | + | "pkcs5", | |
| 1590 | + | "rand_core", | |
| 1591 | + | "spki", | |
| 1592 | + | ] | |
| 1593 | + | ||
| 1594 | + | [[package]] | |
| 1595 | + | name = "pkg-config" | |
| 1596 | + | version = "0.3.34" | |
| 1597 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1598 | + | checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" | |
| 1599 | + | ||
| 1600 | + | [[package]] | |
| 1601 | + | name = "poly1305" | |
| 1602 | + | version = "0.9.1" | |
| 1603 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1604 | + | checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" | |
| 1605 | + | dependencies = [ | |
| 1606 | + | "cpufeatures", | |
| 1607 | + | "universal-hash", | |
| 1608 | + | "zeroize", | |
| 1609 | + | ] | |
| 1610 | + | ||
| 1611 | + | [[package]] | |
| 1612 | + | name = "polyval" | |
| 1613 | + | version = "0.7.3" | |
| 1614 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1615 | + | checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" | |
| 1616 | + | dependencies = [ | |
| 1617 | + | "cpubits", | |
| 1618 | + | "cpufeatures", | |
| 1619 | + | "universal-hash", | |
| 1620 | + | "zeroize", | |
| 1621 | + | ] | |
| 1622 | + | ||
| 1623 | + | [[package]] | |
| 1624 | + | name = "potential_utf" | |
| 1625 | + | version = "0.1.6" | |
| 1626 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1627 | + | checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" | |
| 1628 | + | dependencies = [ | |
| 1629 | + | "zerovec", | |
| 1630 | + | ] | |
| 1631 | + | ||
| 1632 | + | [[package]] | |
| 1633 | + | name = "primefield" | |
| 1634 | + | version = "0.14.0" | |
| 1635 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1636 | + | checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" | |
| 1637 | + | dependencies = [ | |
| 1638 | + | "crypto-bigint", | |
| 1639 | + | "crypto-common", | |
| 1640 | + | "ff", | |
| 1641 | + | "rand_core", | |
| 1642 | + | "subtle", | |
| 1643 | + | "zeroize", | |
| 1644 | + | ] | |
| 1645 | + | ||
| 1646 | + | [[package]] | |
| 1647 | + | name = "primeorder" | |
| 1648 | + | version = "0.14.0" | |
| 1649 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1650 | + | checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" | |
| 1651 | + | dependencies = [ | |
| 1652 | + | "elliptic-curve", | |
| 1653 | + | "once_cell", | |
| 1654 | + | "primefield", | |
| 1655 | + | "serdect", | |
| 1656 | + | "wnaf", | |
| 1657 | + | ] | |
| 1658 | + | ||
| 1659 | + | [[package]] | |
| 1660 | + | name = "proc-macro2" | |
| 1661 | + | version = "1.0.107" | |
| 1662 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1663 | + | checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" | |
| 1664 | + | dependencies = [ | |
| 1665 | + | "unicode-ident", | |
| 1666 | + | ] | |
| 1667 | + | ||
| 1668 | + | [[package]] | |
| 1669 | + | name = "quinn" | |
| 1670 | + | version = "0.11.12" | |
| 1671 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1672 | + | checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" | |
| 1673 | + | dependencies = [ | |
| 1674 | + | "bytes", | |
| 1675 | + | "cfg_aliases", | |
| 1676 | + | "pin-project-lite", | |
| 1677 | + | "quinn-proto", | |
| 1678 | + | "quinn-udp", | |
| 1679 | + | "rustc-hash", | |
| 1680 | + | "rustls", | |
| 1681 | + | "socket2", | |
| 1682 | + | "thiserror", | |
| 1683 | + | "tokio", | |
| 1684 | + | "tracing", | |
| 1685 | + | "web-time", | |
| 1686 | + | ] | |
| 1687 | + | ||
| 1688 | + | [[package]] | |
| 1689 | + | name = "quinn-proto" | |
| 1690 | + | version = "0.11.19" | |
| 1691 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1692 | + | checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" | |
| 1693 | + | dependencies = [ | |
| 1694 | + | "aws-lc-rs", | |
| 1695 | + | "bytes", | |
| 1696 | + | "getrandom 0.4.3", | |
| 1697 | + | "lru-slab", | |
| 1698 | + | "rand", | |
| 1699 | + | "rand_pcg", | |
| 1700 | + | "ring", | |
| 1701 | + | "rustc-hash", | |
| 1702 | + | "rustls", | |
| 1703 | + | "rustls-pki-types", | |
| 1704 | + | "slab", | |
| 1705 | + | "thiserror", | |
| 1706 | + | "tinyvec", | |
| 1707 | + | "tracing", | |
| 1708 | + | "web-time", | |
| 1709 | + | ] | |
| 1710 | + | ||
| 1711 | + | [[package]] | |
| 1712 | + | name = "quinn-udp" | |
| 1713 | + | version = "0.5.16" | |
| 1714 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1715 | + | checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" | |
| 1716 | + | dependencies = [ | |
| 1717 | + | "cfg_aliases", | |
| 1718 | + | "libc", | |
| 1719 | + | "once_cell", | |
| 1720 | + | "socket2", | |
| 1721 | + | "tracing", | |
| 1722 | + | "windows-sys 0.61.2", | |
| 1723 | + | ] | |
| 1724 | + | ||
| 1725 | + | [[package]] | |
| 1726 | + | name = "quote" | |
| 1727 | + | version = "1.0.47" | |
| 1728 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1729 | + | checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" | |
| 1730 | + | dependencies = [ | |
| 1731 | + | "proc-macro2", | |
| 1732 | + | ] | |
| 1733 | + | ||
| 1734 | + | [[package]] | |
| 1735 | + | name = "r-efi" | |
| 1736 | + | version = "6.0.0" | |
| 1737 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1738 | + | checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" | |
| 1739 | + | ||
| 1740 | + | [[package]] | |
| 1741 | + | name = "rand" | |
| 1742 | + | version = "0.10.3" | |
| 1743 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1744 | + | checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" | |
| 1745 | + | dependencies = [ | |
| 1746 | + | "chacha20", | |
| 1747 | + | "getrandom 0.4.3", | |
| 1748 | + | "rand_core", | |
| 1749 | + | ] | |
| 1750 | + | ||
| 1751 | + | [[package]] | |
| 1752 | + | name = "rand_core" | |
| 1753 | + | version = "0.10.1" | |
| 1754 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1755 | + | checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" | |
| 1756 | + | ||
| 1757 | + | [[package]] | |
| 1758 | + | name = "rand_pcg" | |
| 1759 | + | version = "0.10.2" | |
| 1760 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1761 | + | checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" | |
| 1762 | + | dependencies = [ | |
| 1763 | + | "rand_core", | |
| 1764 | + | ] | |
| 1765 | + | ||
| 1766 | + | [[package]] | |
| 1767 | + | name = "redox_syscall" | |
| 1768 | + | version = "0.5.18" | |
| 1769 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1770 | + | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" | |
| 1771 | + | dependencies = [ | |
| 1772 | + | "bitflags", | |
| 1773 | + | ] | |
| 1774 | + | ||
| 1775 | + | [[package]] | |
| 1776 | + | name = "reqwest" | |
| 1777 | + | version = "0.13.5" | |
| 1778 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1779 | + | checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" | |
| 1780 | + | dependencies = [ | |
| 1781 | + | "base64", | |
| 1782 | + | "bytes", | |
| 1783 | + | "encoding_rs", | |
| 1784 | + | "futures-core", | |
| 1785 | + | "futures-util", | |
| 1786 | + | "h2", | |
| 1787 | + | "http", | |
| 1788 | + | "http-body", | |
| 1789 | + | "http-body-util", | |
| 1790 | + | "hyper", | |
| 1791 | + | "hyper-rustls", | |
| 1792 | + | "hyper-util", | |
| 1793 | + | "js-sys", | |
| 1794 | + | "log", | |
| 1795 | + | "mime", | |
| 1796 | + | "percent-encoding", | |
| 1797 | + | "pin-project-lite", | |
| 1798 | + | "quinn", | |
| 1799 | + | "rustls", | |
| 1800 | + | "rustls-pki-types", | |
| 1801 | + | "rustls-platform-verifier", | |
| 1802 | + | "serde", | |
| 1803 | + | "serde_json", | |
| 1804 | + | "sync_wrapper", | |
| 1805 | + | "tokio", | |
| 1806 | + | "tokio-rustls", | |
| 1807 | + | "tokio-util", | |
| 1808 | + | "tower", | |
| 1809 | + | "tower-http", | |
| 1810 | + | "tower-service", | |
| 1811 | + | "url", | |
| 1812 | + | "wasm-bindgen", | |
| 1813 | + | "wasm-bindgen-futures", | |
| 1814 | + | "wasm-streams", | |
| 1815 | + | "web-sys", | |
| 1816 | + | ] | |
| 1817 | + | ||
| 1818 | + | [[package]] | |
| 1819 | + | name = "rfc6979" | |
| 1820 | + | version = "0.6.0" | |
| 1821 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1822 | + | checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" | |
| 1823 | + | dependencies = [ | |
| 1824 | + | "crypto-bigint", | |
| 1825 | + | "hmac", | |
| 1826 | + | ] | |
| 1827 | + | ||
| 1828 | + | [[package]] | |
| 1829 | + | name = "ring" | |
| 1830 | + | version = "0.17.14" | |
| 1831 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1832 | + | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | |
| 1833 | + | dependencies = [ | |
| 1834 | + | "cc", | |
| 1835 | + | "cfg-if", | |
| 1836 | + | "getrandom 0.2.17", | |
| 1837 | + | "libc", | |
| 1838 | + | "untrusted 0.9.0", | |
| 1839 | + | "windows-sys 0.52.0", | |
| 1840 | + | ] | |
| 1841 | + | ||
| 1842 | + | [[package]] | |
| 1843 | + | name = "rsa" | |
| 1844 | + | version = "0.10.0-rc.18" | |
| 1845 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1846 | + | checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" | |
| 1847 | + | dependencies = [ | |
| 1848 | + | "const-oid", | |
| 1849 | + | "crypto-bigint", | |
| 1850 | + | "crypto-primes", | |
| 1851 | + | "digest", | |
| 1852 | + | "pkcs1", | |
| 1853 | + | "pkcs8", | |
| 1854 | + | "rand_core", | |
| 1855 | + | "sha2", | |
| 1856 | + | "signature", | |
| 1857 | + | "spki", | |
| 1858 | + | "zeroize", | |
| 1859 | + | ] | |
| 1860 | + | ||
| 1861 | + | [[package]] | |
| 1862 | + | name = "russh" | |
| 1863 | + | version = "0.63.3" | |
| 1864 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1865 | + | checksum = "036204edbd199552a5b3832f63c60dcdf395dc44c7f06b4af1c0e8139cc11bce" | |
| 1866 | + | dependencies = [ | |
| 1867 | + | "aes", | |
| 1868 | + | "aws-lc-rs", | |
| 1869 | + | "bitflags", | |
| 1870 | + | "block-padding", | |
| 1871 | + | "byteorder", | |
| 1872 | + | "bytes", | |
| 1873 | + | "cbc", | |
| 1874 | + | "cipher", | |
| 1875 | + | "crypto-bigint", | |
| 1876 | + | "ctr", | |
| 1877 | + | "curve25519-dalek", | |
| 1878 | + | "data-encoding", | |
| 1879 | + | "delegate", | |
| 1880 | + | "der", | |
| 1881 | + | "digest", | |
| 1882 | + | "ecdsa", | |
| 1883 | + | "ed25519-dalek", | |
| 1884 | + | "elliptic-curve", | |
| 1885 | + | "enum_dispatch", | |
| 1886 | + | "flate2", | |
| 1887 | + | "futures", | |
| 1888 | + | "generic-array 1.4.5", | |
| 1889 | + | "getrandom 0.4.3", | |
| 1890 | + | "ghash", | |
| 1891 | + | "hex-literal", | |
| 1892 | + | "hmac", | |
| 1893 | + | "inout", | |
| 1894 | + | "keccak", | |
| 1895 | + | "log", | |
| 1896 | + | "md5", | |
| 1897 | + | "ml-kem", | |
| 1898 | + | "module-lattice", | |
| 1899 | + | "num-bigint", | |
| 1900 | + | "p256", | |
| 1901 | + | "p384", | |
| 1902 | + | "p521", | |
| 1903 | + | "pageant", | |
| 1904 | + | "pbkdf2", | |
| 1905 | + | "pkcs1", | |
| 1906 | + | "pkcs5", | |
| 1907 | + | "pkcs8", | |
| 1908 | + | "polyval", | |
| 1909 | + | "rand", | |
| 1910 | + | "rand_core", | |
| 1911 | + | "rsa", | |
| 1912 | + | "russh-cryptovec", | |
| 1913 | + | "russh-util", | |
| 1914 | + | "salsa20", | |
| 1915 | + | "scrypt", | |
| 1916 | + | "sec1", | |
| 1917 | + | "sha1", | |
| 1918 | + | "sha2", | |
| 1919 | + | "sha3 0.12.0", | |
| 1920 | + | "signature", | |
| 1921 | + | "spki", | |
| 1922 | + | "ssh-encoding", | |
| 1923 | + | "ssh-key", | |
| 1924 | + | "subtle", | |
| 1925 | + | "thiserror", | |
| 1926 | + | "tokio", | |
| 1927 | + | "typenum", | |
| 1928 | + | "universal-hash", | |
| 1929 | + | "zeroize", | |
| 1930 | + | ] | |
| 1931 | + | ||
| 1932 | + | [[package]] | |
| 1933 | + | name = "russh-cryptovec" | |
| 1934 | + | version = "0.62.0" | |
| 1935 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1936 | + | checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438" | |
| 1937 | + | dependencies = [ | |
| 1938 | + | "log", | |
| 1939 | + | "nix", | |
| 1940 | + | "ssh-encoding", | |
| 1941 | + | "windows-sys 0.61.2", | |
| 1942 | + | ] | |
| 1943 | + | ||
| 1944 | + | [[package]] | |
| 1945 | + | name = "russh-util" | |
| 1946 | + | version = "0.52.0" | |
| 1947 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1948 | + | checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6" | |
| 1949 | + | dependencies = [ | |
| 1950 | + | "chrono", | |
| 1951 | + | "tokio", | |
| 1952 | + | "wasm-bindgen", | |
| 1953 | + | "wasm-bindgen-futures", | |
| 1954 | + | ] | |
| 1955 | + | ||
| 1956 | + | [[package]] | |
| 1957 | + | name = "rustc-hash" | |
| 1958 | + | version = "2.1.3" | |
| 1959 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1960 | + | checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" | |
| 1961 | + | ||
| 1962 | + | [[package]] | |
| 1963 | + | name = "rustc_version" | |
| 1964 | + | version = "0.4.1" | |
| 1965 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1966 | + | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" | |
| 1967 | + | dependencies = [ | |
| 1968 | + | "semver", | |
| 1969 | + | ] | |
| 1970 | + | ||
| 1971 | + | [[package]] | |
| 1972 | + | name = "rustls" | |
| 1973 | + | version = "0.23.45" | |
| 1974 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1975 | + | checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" | |
| 1976 | + | dependencies = [ | |
| 1977 | + | "aws-lc-rs", | |
| 1978 | + | "once_cell", | |
| 1979 | + | "rustls-pki-types", | |
| 1980 | + | "rustls-webpki", | |
| 1981 | + | "subtle", | |
| 1982 | + | "zeroize", | |
| 1983 | + | ] | |
| 1984 | + | ||
| 1985 | + | [[package]] | |
| 1986 | + | name = "rustls-native-certs" | |
| 1987 | + | version = "0.8.4" | |
| 1988 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1989 | + | checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" | |
| 1990 | + | dependencies = [ | |
| 1991 | + | "openssl-probe", | |
| 1992 | + | "rustls-pki-types", | |
| 1993 | + | "schannel", | |
| 1994 | + | "security-framework", | |
| 1995 | + | ] | |
| 1996 | + | ||
| 1997 | + | [[package]] | |
| 1998 | + | name = "rustls-pki-types" | |
| 1999 | + | version = "1.15.1" | |
| 2000 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2001 | + | checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" | |
| 2002 | + | dependencies = [ | |
| 2003 | + | "web-time", | |
| 2004 | + | "zeroize", | |
| 2005 | + | ] | |
| 2006 | + | ||
| 2007 | + | [[package]] | |
| 2008 | + | name = "rustls-platform-verifier" | |
| 2009 | + | version = "0.7.1" | |
| 2010 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2011 | + | checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" | |
| 2012 | + | dependencies = [ | |
| 2013 | + | "core-foundation 0.10.1", | |
| 2014 | + | "core-foundation-sys", | |
| 2015 | + | "jni", | |
| 2016 | + | "log", | |
| 2017 | + | "once_cell", | |
| 2018 | + | "rustls", | |
| 2019 | + | "rustls-native-certs", | |
| 2020 | + | "rustls-platform-verifier-android", | |
| 2021 | + | "rustls-webpki", | |
| 2022 | + | "security-framework", | |
| 2023 | + | "security-framework-sys", | |
| 2024 | + | "webpki-root-certs", | |
| 2025 | + | "windows-sys 0.61.2", | |
| 2026 | + | ] | |
| 2027 | + | ||
| 2028 | + | [[package]] | |
| 2029 | + | name = "rustls-platform-verifier-android" | |
| 2030 | + | version = "0.2.0" | |
| 2031 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2032 | + | checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" | |
| 2033 | + | ||
| 2034 | + | [[package]] | |
| 2035 | + | name = "rustls-webpki" | |
| 2036 | + | version = "0.103.15" | |
| 2037 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2038 | + | checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" | |
| 2039 | + | dependencies = [ | |
| 2040 | + | "aws-lc-rs", | |
| 2041 | + | "ring", | |
| 2042 | + | "rustls-pki-types", | |
| 2043 | + | "untrusted 0.9.0", | |
| 2044 | + | ] | |
| 2045 | + | ||
| 2046 | + | [[package]] | |
| 2047 | + | name = "rustversion" | |
| 2048 | + | version = "1.0.23" | |
| 2049 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2050 | + | checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" | |
| 2051 | + | ||
| 2052 | + | [[package]] | |
| 2053 | + | name = "salsa20" | |
| 2054 | + | version = "0.11.0" | |
| 2055 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2056 | + | checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" | |
| 2057 | + | dependencies = [ | |
| 2058 | + | "cfg-if", | |
| 2059 | + | "cipher", | |
| 2060 | + | ] | |
| 2061 | + | ||
| 2062 | + | [[package]] | |
| 2063 | + | name = "same-file" | |
| 2064 | + | version = "1.0.6" | |
| 2065 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2066 | + | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" | |
| 2067 | + | dependencies = [ | |
| 2068 | + | "winapi-util", | |
| 2069 | + | ] | |
| 2070 | + | ||
| 2071 | + | [[package]] | |
| 2072 | + | name = "schannel" | |
| 2073 | + | version = "0.1.29" | |
| 2074 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2075 | + | checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" | |
| 2076 | + | dependencies = [ | |
| 2077 | + | "windows-sys 0.61.2", | |
| 2078 | + | ] | |
| 2079 | + | ||
| 2080 | + | [[package]] | |
| 2081 | + | name = "scopeguard" | |
| 2082 | + | version = "1.2.0" | |
| 2083 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2084 | + | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" | |
| 2085 | + | ||
| 2086 | + | [[package]] | |
| 2087 | + | name = "scrypt" | |
| 2088 | + | version = "0.12.0" | |
| 2089 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2090 | + | checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" | |
| 2091 | + | dependencies = [ | |
| 2092 | + | "cfg-if", | |
| 2093 | + | "pbkdf2", | |
| 2094 | + | "salsa20", | |
| 2095 | + | "sha2", | |
| 2096 | + | ] | |
| 2097 | + | ||
| 2098 | + | [[package]] | |
| 2099 | + | name = "sec1" | |
| 2100 | + | version = "0.8.1" | |
| 2101 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2102 | + | checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" | |
| 2103 | + | dependencies = [ | |
| 2104 | + | "base16ct", | |
| 2105 | + | "ctutils", | |
| 2106 | + | "der", | |
| 2107 | + | "hybrid-array", | |
| 2108 | + | "subtle", | |
| 2109 | + | "zeroize", | |
| 2110 | + | ] | |
| 2111 | + | ||
| 2112 | + | [[package]] | |
| 2113 | + | name = "security-framework" | |
| 2114 | + | version = "3.7.0" | |
| 2115 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2116 | + | checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" | |
| 2117 | + | dependencies = [ | |
| 2118 | + | "bitflags", | |
| 2119 | + | "core-foundation 0.10.1", | |
| 2120 | + | "core-foundation-sys", | |
| 2121 | + | "libc", | |
| 2122 | + | "security-framework-sys", | |
| 2123 | + | ] | |
| 2124 | + | ||
| 2125 | + | [[package]] | |
| 2126 | + | name = "security-framework-sys" | |
| 2127 | + | version = "2.17.0" | |
| 2128 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2129 | + | checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" | |
| 2130 | + | dependencies = [ | |
| 2131 | + | "core-foundation-sys", | |
| 2132 | + | "libc", | |
| 2133 | + | ] | |
| 2134 | + | ||
| 2135 | + | [[package]] | |
| 2136 | + | name = "semver" | |
| 2137 | + | version = "1.0.28" | |
| 2138 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2139 | + | checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" | |
| 2140 | + | ||
| 2141 | + | [[package]] | |
| 2142 | + | name = "serde" | |
| 2143 | + | version = "1.0.229" | |
| 2144 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2145 | + | checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" | |
| 2146 | + | dependencies = [ | |
| 2147 | + | "serde_core", | |
| 2148 | + | "serde_derive", | |
| 2149 | + | ] | |
| 2150 | + | ||
| 2151 | + | [[package]] | |
| 2152 | + | name = "serde_core" | |
| 2153 | + | version = "1.0.229" | |
| 2154 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2155 | + | checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" | |
| 2156 | + | dependencies = [ | |
| 2157 | + | "serde_derive", | |
| 2158 | + | ] | |
| 2159 | + | ||
| 2160 | + | [[package]] | |
| 2161 | + | name = "serde_derive" | |
| 2162 | + | version = "1.0.229" | |
| 2163 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2164 | + | checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" | |
| 2165 | + | dependencies = [ | |
| 2166 | + | "proc-macro2", | |
| 2167 | + | "quote", | |
| 2168 | + | "syn 3.0.6", | |
| 2169 | + | ] | |
| 2170 | + | ||
| 2171 | + | [[package]] | |
| 2172 | + | name = "serde_json" | |
| 2173 | + | version = "1.0.151" | |
| 2174 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2175 | + | checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" | |
| 2176 | + | dependencies = [ | |
| 2177 | + | "itoa", | |
| 2178 | + | "memchr", | |
| 2179 | + | "serde", | |
| 2180 | + | "serde_core", | |
| 2181 | + | "zmij", | |
| 2182 | + | ] | |
| 2183 | + | ||
| 2184 | + | [[package]] | |
| 2185 | + | name = "serdect" | |
| 2186 | + | version = "0.4.3" | |
| 2187 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2188 | + | checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" | |
| 2189 | + | dependencies = [ | |
| 2190 | + | "base16ct", | |
| 2191 | + | "serde", | |
| 2192 | + | ] | |
| 2193 | + | ||
| 2194 | + | [[package]] | |
| 2195 | + | name = "sha1" | |
| 2196 | + | version = "0.11.0" | |
| 2197 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2198 | + | checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" | |
| 2199 | + | dependencies = [ | |
| 2200 | + | "cfg-if", | |
| 2201 | + | "cpufeatures", | |
| 2202 | + | "digest", | |
| 2203 | + | ] | |
| 2204 | + | ||
| 2205 | + | [[package]] | |
| 2206 | + | name = "sha2" | |
| 2207 | + | version = "0.11.0" | |
| 2208 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2209 | + | checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" | |
| 2210 | + | dependencies = [ | |
| 2211 | + | "cfg-if", | |
| 2212 | + | "cpufeatures", | |
| 2213 | + | "digest", | |
| 2214 | + | ] | |
| 2215 | + | ||
| 2216 | + | [[package]] | |
| 2217 | + | name = "sha3" | |
| 2218 | + | version = "0.11.0" | |
| 2219 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2220 | + | checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" | |
| 2221 | + | dependencies = [ | |
| 2222 | + | "digest", | |
| 2223 | + | "keccak", | |
| 2224 | + | ] | |
| 2225 | + | ||
| 2226 | + | [[package]] | |
| 2227 | + | name = "sha3" | |
| 2228 | + | version = "0.12.0" | |
| 2229 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2230 | + | checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" | |
| 2231 | + | dependencies = [ | |
| 2232 | + | "digest", | |
| 2233 | + | "keccak", | |
| 2234 | + | "sponge-cursor", | |
| 2235 | + | ] | |
| 2236 | + | ||
| 2237 | + | [[package]] | |
| 2238 | + | name = "shlex" | |
| 2239 | + | version = "2.0.1" | |
| 2240 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2241 | + | checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" | |
| 2242 | + | ||
| 2243 | + | [[package]] | |
| 2244 | + | name = "signal-hook-registry" | |
| 2245 | + | version = "1.4.8" | |
| 2246 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2247 | + | checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" | |
| 2248 | + | dependencies = [ | |
| 2249 | + | "errno", | |
| 2250 | + | "libc", | |
| 2251 | + | ] | |
| 2252 | + | ||
| 2253 | + | [[package]] | |
| 2254 | + | name = "signature" | |
| 2255 | + | version = "3.0.0" | |
| 2256 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2257 | + | checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" | |
| 2258 | + | dependencies = [ | |
| 2259 | + | "digest", | |
| 2260 | + | "rand_core", | |
| 2261 | + | ] | |
| 2262 | + | ||
| 2263 | + | [[package]] | |
| 2264 | + | name = "simd-adler32" | |
| 2265 | + | version = "0.3.10" | |
| 2266 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2267 | + | checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" | |
| 2268 | + | ||
| 2269 | + | [[package]] | |
| 2270 | + | name = "simd_cesu8" | |
| 2271 | + | version = "1.2.0" | |
| 2272 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2273 | + | checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" | |
| 2274 | + | dependencies = [ | |
| 2275 | + | "rustc_version", | |
| 2276 | + | "simdutf8", | |
| 2277 | + | ] | |
| 2278 | + | ||
| 2279 | + | [[package]] | |
| 2280 | + | name = "simdutf8" | |
| 2281 | + | version = "0.1.5" | |
| 2282 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2283 | + | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" | |
| 2284 | + | ||
| 2285 | + | [[package]] | |
| 2286 | + | name = "slab" | |
| 2287 | + | version = "0.4.12" | |
| 2288 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2289 | + | checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" | |
| 2290 | + | ||
| 2291 | + | [[package]] | |
| 2292 | + | name = "smallvec" | |
| 2293 | + | version = "1.16.2" | |
| 2294 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2295 | + | checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" | |
| 2296 | + | ||
| 2297 | + | [[package]] | |
| 2298 | + | name = "socket2" | |
| 2299 | + | version = "0.6.5" | |
| 2300 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2301 | + | checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" | |
| 2302 | + | dependencies = [ | |
| 2303 | + | "libc", | |
| 2304 | + | "windows-sys 0.61.2", | |
| 2305 | + | ] | |
| 2306 | + | ||
| 2307 | + | [[package]] | |
| 2308 | + | name = "spki" | |
| 2309 | + | version = "0.8.0" | |
| 2310 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2311 | + | checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" | |
| 2312 | + | dependencies = [ | |
| 2313 | + | "base64ct", | |
| 2314 | + | "der", | |
| 2315 | + | ] | |
| 2316 | + | ||
| 2317 | + | [[package]] | |
| 2318 | + | name = "sponge-cursor" | |
| 2319 | + | version = "0.1.0" | |
| 2320 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2321 | + | checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" | |
| 2322 | + | ||
| 2323 | + | [[package]] | |
| 2324 | + | name = "ssh-cipher" | |
| 2325 | + | version = "0.3.0" | |
| 2326 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2327 | + | checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597" | |
| 2328 | + | dependencies = [ | |
| 2329 | + | "aead", | |
| 2330 | + | "aes", | |
| 2331 | + | "aes-gcm", | |
| 2332 | + | "chacha20", | |
| 2333 | + | "cipher", | |
| 2334 | + | "ctutils", | |
| 2335 | + | "des", | |
| 2336 | + | "poly1305", | |
| 2337 | + | "ssh-encoding", | |
| 2338 | + | "zeroize", | |
| 2339 | + | ] | |
| 2340 | + | ||
| 2341 | + | [[package]] | |
| 2342 | + | name = "ssh-encoding" | |
| 2343 | + | version = "0.3.0" | |
| 2344 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2345 | + | checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e" | |
| 2346 | + | dependencies = [ | |
| 2347 | + | "base64ct", | |
| 2348 | + | "bytes", | |
| 2349 | + | "crypto-bigint", | |
| 2350 | + | "ctutils", | |
| 2351 | + | "digest", | |
| 2352 | + | "pem-rfc7468", | |
| 2353 | + | "zeroize", | |
| 2354 | + | ] | |
| 2355 | + | ||
| 2356 | + | [[package]] | |
| 2357 | + | name = "ssh-key" | |
| 2358 | + | version = "0.7.0-rc.11" | |
| 2359 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2360 | + | checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3" | |
| 2361 | + | dependencies = [ | |
| 2362 | + | "argon2", | |
| 2363 | + | "bcrypt-pbkdf", | |
| 2364 | + | "ctutils", | |
| 2365 | + | "ed25519-dalek", | |
| 2366 | + | "hex", | |
| 2367 | + | "hmac", | |
| 2368 | + | "p256", | |
| 2369 | + | "p384", | |
| 2370 | + | "p521", | |
| 2371 | + | "rand_core", | |
| 2372 | + | "rsa", | |
| 2373 | + | "sec1", | |
| 2374 | + | "sha1", | |
| 2375 | + | "sha2", | |
| 2376 | + | "signature", | |
| 2377 | + | "ssh-cipher", | |
| 2378 | + | "ssh-encoding", | |
| 2379 | + | "zeroize", | |
| 2380 | + | ] | |
| 2381 | + | ||
| 2382 | + | [[package]] | |
| 2383 | + | name = "stable_deref_trait" | |
| 2384 | + | version = "1.2.1" | |
| 2385 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2386 | + | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" | |
| 2387 | + | ||
| 2388 | + | [[package]] | |
| 2389 | + | name = "subtle" | |
| 2390 | + | version = "2.6.1" | |
| 2391 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2392 | + | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | |
| 2393 | + | ||
| 2394 | + | [[package]] | |
| 2395 | + | name = "syn" | |
| 2396 | + | version = "2.0.119" | |
| 2397 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2398 | + | checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" | |
| 2399 | + | dependencies = [ | |
| 2400 | + | "proc-macro2", | |
| 2401 | + | "quote", | |
| 2402 | + | "unicode-ident", | |
| 2403 | + | ] | |
| 2404 | + | ||
| 2405 | + | [[package]] | |
| 2406 | + | name = "syn" | |
| 2407 | + | version = "3.0.6" | |
| 2408 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2409 | + | checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" | |
| 2410 | + | dependencies = [ | |
| 2411 | + | "proc-macro2", | |
| 2412 | + | "quote", | |
| 2413 | + | "unicode-ident", | |
| 2414 | + | ] | |
| 2415 | + | ||
| 2416 | + | [[package]] | |
| 2417 | + | name = "sync_wrapper" | |
| 2418 | + | version = "1.0.2" | |
| 2419 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2420 | + | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" | |
| 2421 | + | dependencies = [ | |
| 2422 | + | "futures-core", | |
| 2423 | + | ] | |
| 2424 | + | ||
| 2425 | + | [[package]] | |
| 2426 | + | name = "synstructure" | |
| 2427 | + | version = "0.14.0" | |
| 2428 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2429 | + | checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" | |
| 2430 | + | dependencies = [ | |
| 2431 | + | "proc-macro2", | |
| 2432 | + | "quote", | |
| 2433 | + | "syn 3.0.6", | |
| 2434 | + | ] | |
| 2435 | + | ||
| 2436 | + | [[package]] | |
| 2437 | + | name = "system-configuration" | |
| 2438 | + | version = "0.7.0" | |
| 2439 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2440 | + | checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" | |
| 2441 | + | dependencies = [ | |
| 2442 | + | "bitflags", | |
| 2443 | + | "core-foundation 0.9.4", | |
| 2444 | + | "system-configuration-sys", | |
| 2445 | + | ] | |
| 2446 | + | ||
| 2447 | + | [[package]] | |
| 2448 | + | name = "system-configuration-sys" | |
| 2449 | + | version = "0.6.0" | |
| 2450 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2451 | + | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" | |
| 2452 | + | dependencies = [ | |
| 2453 | + | "core-foundation-sys", | |
| 2454 | + | "libc", | |
| 2455 | + | ] | |
| 2456 | + | ||
| 2457 | + | [[package]] | |
| 2458 | + | name = "thiserror" | |
| 2459 | + | version = "2.0.21" | |
| 2460 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2461 | + | checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" | |
| 2462 | + | dependencies = [ | |
| 2463 | + | "thiserror-impl", | |
| 2464 | + | ] | |
| 2465 | + | ||
| 2466 | + | [[package]] | |
| 2467 | + | name = "thiserror-impl" | |
| 2468 | + | version = "2.0.21" | |
| 2469 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2470 | + | checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" | |
| 2471 | + | dependencies = [ | |
| 2472 | + | "proc-macro2", | |
| 2473 | + | "quote", | |
| 2474 | + | "syn 3.0.6", | |
| 2475 | + | ] | |
| 2476 | + | ||
| 2477 | + | [[package]] | |
| 2478 | + | name = "tinystr" | |
| 2479 | + | version = "0.8.4" | |
| 2480 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2481 | + | checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" | |
| 2482 | + | dependencies = [ | |
| 2483 | + | "displaydoc", | |
| 2484 | + | "zerovec", | |
| 2485 | + | ] | |
| 2486 | + | ||
| 2487 | + | [[package]] | |
| 2488 | + | name = "tinyvec" | |
| 2489 | + | version = "1.13.3" | |
| 2490 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2491 | + | checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" | |
| 2492 | + | ||
| 2493 | + | [[package]] | |
| 2494 | + | name = "tokio" | |
| 2495 | + | version = "1.53.1" | |
| 2496 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2497 | + | checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" | |
| 2498 | + | dependencies = [ | |
| 2499 | + | "bytes", | |
| 2500 | + | "libc", | |
| 2501 | + | "mio", | |
| 2502 | + | "parking_lot", | |
| 2503 | + | "pin-project-lite", | |
| 2504 | + | "signal-hook-registry", | |
| 2505 | + | "socket2", | |
| 2506 | + | "tokio-macros", | |
| 2507 | + | "windows-sys 0.61.2", | |
| 2508 | + | ] | |
| 2509 | + | ||
| 2510 | + | [[package]] | |
| 2511 | + | name = "tokio-macros" | |
| 2512 | + | version = "2.7.2" | |
| 2513 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2514 | + | checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" | |
| 2515 | + | dependencies = [ | |
| 2516 | + | "proc-macro2", | |
| 2517 | + | "quote", | |
| 2518 | + | "syn 3.0.6", | |
| 2519 | + | ] | |
| 2520 | + | ||
| 2521 | + | [[package]] | |
| 2522 | + | name = "tokio-rustls" | |
| 2523 | + | version = "0.26.6" | |
| 2524 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2525 | + | checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" | |
| 2526 | + | dependencies = [ | |
| 2527 | + | "rustls", | |
| 2528 | + | "tokio", | |
| 2529 | + | ] | |
| 2530 | + | ||
| 2531 | + | [[package]] | |
| 2532 | + | name = "tokio-tungstenite" | |
| 2533 | + | version = "0.30.0" | |
| 2534 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2535 | + | checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71" | |
| 2536 | + | dependencies = [ | |
| 2537 | + | "futures-util", | |
| 2538 | + | "log", | |
| 2539 | + | "tokio", | |
| 2540 | + | "tungstenite", | |
| 2541 | + | ] | |
| 2542 | + | ||
| 2543 | + | [[package]] | |
| 2544 | + | name = "tokio-util" | |
| 2545 | + | version = "0.7.19" | |
| 2546 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2547 | + | checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" | |
| 2548 | + | dependencies = [ | |
| 2549 | + | "bytes", | |
| 2550 | + | "futures-core", | |
| 2551 | + | "futures-sink", | |
| 2552 | + | "libc", | |
| 2553 | + | "pin-project-lite", | |
| 2554 | + | "tokio", | |
| 2555 | + | ] | |
| 2556 | + | ||
| 2557 | + | [[package]] | |
| 2558 | + | name = "tower" | |
| 2559 | + | version = "0.5.3" | |
| 2560 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2561 | + | checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" | |
| 2562 | + | dependencies = [ | |
| 2563 | + | "futures-core", | |
| 2564 | + | "futures-util", | |
| 2565 | + | "pin-project-lite", | |
| 2566 | + | "sync_wrapper", | |
| 2567 | + | "tokio", | |
| 2568 | + | "tower-layer", | |
| 2569 | + | "tower-service", | |
| 2570 | + | ] | |
| 2571 | + | ||
| 2572 | + | [[package]] | |
| 2573 | + | name = "tower-http" | |
| 2574 | + | version = "0.6.11" | |
| 2575 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2576 | + | checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" | |
| 2577 | + | dependencies = [ | |
| 2578 | + | "bitflags", | |
| 2579 | + | "bytes", | |
| 2580 | + | "futures-util", | |
| 2581 | + | "http", | |
| 2582 | + | "http-body", | |
| 2583 | + | "pin-project-lite", | |
| 2584 | + | "tower", | |
| 2585 | + | "tower-layer", | |
| 2586 | + | "tower-service", | |
| 2587 | + | "url", | |
| 2588 | + | ] | |
| 2589 | + | ||
| 2590 | + | [[package]] | |
| 2591 | + | name = "tower-layer" | |
| 2592 | + | version = "0.3.3" | |
| 2593 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2594 | + | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" | |
| 2595 | + | ||
| 2596 | + | [[package]] | |
| 2597 | + | name = "tower-service" | |
| 2598 | + | version = "0.3.3" | |
| 2599 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2600 | + | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" | |
| 2601 | + | ||
| 2602 | + | [[package]] | |
| 2603 | + | name = "tracing" | |
| 2604 | + | version = "0.1.44" | |
| 2605 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2606 | + | checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" | |
| 2607 | + | dependencies = [ | |
| 2608 | + | "pin-project-lite", | |
| 2609 | + | "tracing-core", | |
| 2610 | + | ] | |
| 2611 | + | ||
| 2612 | + | [[package]] | |
| 2613 | + | name = "tracing-core" | |
| 2614 | + | version = "0.1.36" | |
| 2615 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2616 | + | checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" | |
| 2617 | + | dependencies = [ | |
| 2618 | + | "once_cell", | |
| 2619 | + | ] | |
| 2620 | + | ||
| 2621 | + | [[package]] | |
| 2622 | + | name = "try-lock" | |
| 2623 | + | version = "0.2.5" | |
| 2624 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2625 | + | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" | |
| 2626 | + | ||
| 2627 | + | [[package]] | |
| 2628 | + | name = "tungstenite" | |
| 2629 | + | version = "0.30.0" | |
| 2630 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2631 | + | checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" | |
| 2632 | + | dependencies = [ | |
| 2633 | + | "bytes", | |
| 2634 | + | "data-encoding", | |
| 2635 | + | "http", | |
| 2636 | + | "httparse", | |
| 2637 | + | "log", | |
| 2638 | + | "rand", | |
| 2639 | + | "sha1", | |
| 2640 | + | "thiserror", | |
| 2641 | + | ] | |
| 2642 | + | ||
| 2643 | + | [[package]] | |
| 2644 | + | name = "typenum" | |
| 2645 | + | version = "1.20.1" | |
| 2646 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2647 | + | checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" | |
| 2648 | + | ||
| 2649 | + | [[package]] | |
| 2650 | + | name = "unicode-ident" | |
| 2651 | + | version = "1.0.26" | |
| 2652 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2653 | + | checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" | |
| 2654 | + | ||
| 2655 | + | [[package]] | |
| 2656 | + | name = "universal-hash" | |
| 2657 | + | version = "0.6.1" | |
| 2658 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2659 | + | checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" | |
| 2660 | + | dependencies = [ | |
| 2661 | + | "crypto-common", | |
| 2662 | + | "ctutils", | |
| 2663 | + | ] | |
| 2664 | + | ||
| 2665 | + | [[package]] | |
| 2666 | + | name = "untrusted" | |
| 2667 | + | version = "0.7.1" | |
| 2668 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2669 | + | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" | |
| 2670 | + | ||
| 2671 | + | [[package]] | |
| 2672 | + | name = "untrusted" | |
| 2673 | + | version = "0.9.0" | |
| 2674 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2675 | + | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" | |
| 2676 | + | ||
| 2677 | + | [[package]] | |
| 2678 | + | name = "url" | |
| 2679 | + | version = "2.5.8" | |
| 2680 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2681 | + | checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" | |
| 2682 | + | dependencies = [ | |
| 2683 | + | "form_urlencoded", | |
| 2684 | + | "idna", | |
| 2685 | + | "percent-encoding", | |
| 2686 | + | "serde", | |
| 2687 | + | ] | |
| 2688 | + | ||
| 2689 | + | [[package]] | |
| 2690 | + | name = "utf8_iter" | |
| 2691 | + | version = "1.0.4" | |
| 2692 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2693 | + | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" | |
| 2694 | + | ||
| 2695 | + | [[package]] | |
| 2696 | + | name = "version_check" | |
| 2697 | + | version = "0.9.5" | |
| 2698 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2699 | + | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | |
| 2700 | + | ||
| 2701 | + | [[package]] | |
| 2702 | + | name = "walkdir" | |
| 2703 | + | version = "2.5.0" | |
| 2704 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2705 | + | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" | |
| 2706 | + | dependencies = [ | |
| 2707 | + | "same-file", | |
| 2708 | + | "winapi-util", | |
| 2709 | + | ] | |
| 2710 | + | ||
| 2711 | + | [[package]] | |
| 2712 | + | name = "want" | |
| 2713 | + | version = "0.3.1" | |
| 2714 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2715 | + | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" | |
| 2716 | + | dependencies = [ | |
| 2717 | + | "try-lock", | |
| 2718 | + | ] | |
| 2719 | + | ||
| 2720 | + | [[package]] | |
| 2721 | + | name = "wasi" | |
| 2722 | + | version = "0.11.1+wasi-snapshot-preview1" | |
| 2723 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2724 | + | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" | |
| 2725 | + | ||
| 2726 | + | [[package]] | |
| 2727 | + | name = "wasm-bindgen" | |
| 2728 | + | version = "0.2.129" | |
| 2729 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2730 | + | checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" | |
| 2731 | + | dependencies = [ | |
| 2732 | + | "cfg-if", | |
| 2733 | + | "once_cell", | |
| 2734 | + | "rustversion", | |
| 2735 | + | "wasm-bindgen-macro", | |
| 2736 | + | "wasm-bindgen-shared", | |
| 2737 | + | ] | |
| 2738 | + | ||
| 2739 | + | [[package]] | |
| 2740 | + | name = "wasm-bindgen-futures" | |
| 2741 | + | version = "0.4.79" | |
| 2742 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2743 | + | checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" | |
| 2744 | + | dependencies = [ | |
| 2745 | + | "js-sys", | |
| 2746 | + | "tokio", | |
| 2747 | + | "wasm-bindgen", | |
| 2748 | + | ] | |
| 2749 | + | ||
| 2750 | + | [[package]] | |
| 2751 | + | name = "wasm-bindgen-macro" | |
| 2752 | + | version = "0.2.129" | |
| 2753 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2754 | + | checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" | |
| 2755 | + | dependencies = [ | |
| 2756 | + | "quote", | |
| 2757 | + | "wasm-bindgen-macro-support", | |
| 2758 | + | ] | |
| 2759 | + | ||
| 2760 | + | [[package]] | |
| 2761 | + | name = "wasm-bindgen-macro-support" | |
| 2762 | + | version = "0.2.129" | |
| 2763 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2764 | + | checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" | |
| 2765 | + | dependencies = [ | |
| 2766 | + | "bumpalo", | |
| 2767 | + | "proc-macro2", | |
| 2768 | + | "quote", | |
| 2769 | + | "syn 3.0.6", | |
| 2770 | + | "wasm-bindgen-shared", | |
| 2771 | + | ] | |
| 2772 | + | ||
| 2773 | + | [[package]] | |
| 2774 | + | name = "wasm-bindgen-shared" | |
| 2775 | + | version = "0.2.129" | |
| 2776 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2777 | + | checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" | |
| 2778 | + | dependencies = [ | |
| 2779 | + | "unicode-ident", | |
| 2780 | + | ] | |
| 2781 | + | ||
| 2782 | + | [[package]] | |
| 2783 | + | name = "wasm-streams" | |
| 2784 | + | version = "0.5.0" | |
| 2785 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2786 | + | checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" | |
| 2787 | + | dependencies = [ | |
| 2788 | + | "futures-util", | |
| 2789 | + | "js-sys", | |
| 2790 | + | "wasm-bindgen", | |
| 2791 | + | "wasm-bindgen-futures", | |
| 2792 | + | "web-sys", | |
| 2793 | + | ] | |
| 2794 | + | ||
| 2795 | + | [[package]] | |
| 2796 | + | name = "web-sys" | |
| 2797 | + | version = "0.3.106" | |
| 2798 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2799 | + | checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" | |
| 2800 | + | dependencies = [ | |
| 2801 | + | "js-sys", | |
| 2802 | + | "wasm-bindgen", | |
| 2803 | + | ] | |
| 2804 | + | ||
| 2805 | + | [[package]] | |
| 2806 | + | name = "web-time" | |
| 2807 | + | version = "1.1.0" | |
| 2808 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2809 | + | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" | |
| 2810 | + | dependencies = [ | |
| 2811 | + | "js-sys", | |
| 2812 | + | "wasm-bindgen", | |
| 2813 | + | ] | |
| 2814 | + | ||
| 2815 | + | [[package]] | |
| 2816 | + | name = "webpki-root-certs" | |
| 2817 | + | version = "1.0.9" | |
| 2818 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2819 | + | checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" | |
| 2820 | + | dependencies = [ | |
| 2821 | + | "rustls-pki-types", | |
| 2822 | + | ] | |
| 2823 | + | ||
| 2824 | + | [[package]] | |
| 2825 | + | name = "winapi-util" | |
| 2826 | + | version = "0.1.11" | |
| 2827 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2828 | + | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" | |
| 2829 | + | dependencies = [ | |
| 2830 | + | "windows-sys 0.61.2", | |
| 2831 | + | ] | |
| 2832 | + | ||
| 2833 | + | [[package]] | |
| 2834 | + | name = "windows" | |
| 2835 | + | version = "0.62.2" | |
| 2836 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2837 | + | checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" | |
| 2838 | + | dependencies = [ | |
| 2839 | + | "windows-collections", | |
| 2840 | + | "windows-core", | |
| 2841 | + | "windows-future", | |
| 2842 | + | "windows-numerics", | |
| 2843 | + | ] | |
| 2844 | + | ||
| 2845 | + | [[package]] | |
| 2846 | + | name = "windows-collections" | |
| 2847 | + | version = "0.3.2" | |
| 2848 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2849 | + | checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" | |
| 2850 | + | dependencies = [ | |
| 2851 | + | "windows-core", | |
| 2852 | + | ] | |
| 2853 | + | ||
| 2854 | + | [[package]] | |
| 2855 | + | name = "windows-core" | |
| 2856 | + | version = "0.62.2" | |
| 2857 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2858 | + | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" | |
| 2859 | + | dependencies = [ | |
| 2860 | + | "windows-implement", | |
| 2861 | + | "windows-interface", | |
| 2862 | + | "windows-link", | |
| 2863 | + | "windows-result", | |
| 2864 | + | "windows-strings", | |
| 2865 | + | ] | |
| 2866 | + | ||
| 2867 | + | [[package]] | |
| 2868 | + | name = "windows-future" | |
| 2869 | + | version = "0.3.2" | |
| 2870 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2871 | + | checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" | |
| 2872 | + | dependencies = [ | |
| 2873 | + | "windows-core", | |
| 2874 | + | "windows-link", | |
| 2875 | + | "windows-threading", | |
| 2876 | + | ] | |
| 2877 | + | ||
| 2878 | + | [[package]] | |
| 2879 | + | name = "windows-implement" | |
| 2880 | + | version = "0.60.2" | |
| 2881 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2882 | + | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" | |
| 2883 | + | dependencies = [ | |
| 2884 | + | "proc-macro2", | |
| 2885 | + | "quote", | |
| 2886 | + | "syn 2.0.119", | |
| 2887 | + | ] | |
| 2888 | + | ||
| 2889 | + | [[package]] | |
| 2890 | + | name = "windows-interface" | |
| 2891 | + | version = "0.59.3" | |
| 2892 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2893 | + | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" | |
| 2894 | + | dependencies = [ | |
| 2895 | + | "proc-macro2", | |
| 2896 | + | "quote", | |
| 2897 | + | "syn 2.0.119", | |
| 2898 | + | ] | |
| 2899 | + | ||
| 2900 | + | [[package]] | |
| 2901 | + | name = "windows-link" | |
| 2902 | + | version = "0.2.1" | |
| 2903 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2904 | + | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | |
| 2905 | + | ||
| 2906 | + | [[package]] | |
| 2907 | + | name = "windows-numerics" | |
| 2908 | + | version = "0.3.1" | |
| 2909 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2910 | + | checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" | |
| 2911 | + | dependencies = [ | |
| 2912 | + | "windows-core", | |
| 2913 | + | "windows-link", | |
| 2914 | + | ] | |
| 2915 | + | ||
| 2916 | + | [[package]] | |
| 2917 | + | name = "windows-registry" | |
| 2918 | + | version = "0.6.1" | |
| 2919 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2920 | + | checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" | |
| 2921 | + | dependencies = [ | |
| 2922 | + | "windows-link", | |
| 2923 | + | "windows-result", | |
| 2924 | + | "windows-strings", | |
| 2925 | + | ] | |
| 2926 | + | ||
| 2927 | + | [[package]] | |
| 2928 | + | name = "windows-result" | |
| 2929 | + | version = "0.4.1" | |
| 2930 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2931 | + | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" | |
| 2932 | + | dependencies = [ | |
| 2933 | + | "windows-link", | |
| 2934 | + | ] | |
| 2935 | + | ||
| 2936 | + | [[package]] | |
| 2937 | + | name = "windows-strings" | |
| 2938 | + | version = "0.5.1" | |
| 2939 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2940 | + | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" | |
| 2941 | + | dependencies = [ | |
| 2942 | + | "windows-link", | |
| 2943 | + | ] | |
| 2944 | + | ||
| 2945 | + | [[package]] | |
| 2946 | + | name = "windows-sys" | |
| 2947 | + | version = "0.52.0" | |
| 2948 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2949 | + | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | |
| 2950 | + | dependencies = [ | |
| 2951 | + | "windows-targets", | |
| 2952 | + | ] | |
| 2953 | + | ||
| 2954 | + | [[package]] | |
| 2955 | + | name = "windows-sys" | |
| 2956 | + | version = "0.61.2" | |
| 2957 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2958 | + | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" | |
| 2959 | + | dependencies = [ | |
| 2960 | + | "windows-link", | |
| 2961 | + | ] | |
| 2962 | + | ||
| 2963 | + | [[package]] | |
| 2964 | + | name = "windows-targets" | |
| 2965 | + | version = "0.52.6" | |
| 2966 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2967 | + | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | |
| 2968 | + | dependencies = [ | |
| 2969 | + | "windows_aarch64_gnullvm", | |
| 2970 | + | "windows_aarch64_msvc", | |
| 2971 | + | "windows_i686_gnu", | |
| 2972 | + | "windows_i686_gnullvm", | |
| 2973 | + | "windows_i686_msvc", | |
| 2974 | + | "windows_x86_64_gnu", | |
| 2975 | + | "windows_x86_64_gnullvm", | |
| 2976 | + | "windows_x86_64_msvc", | |
| 2977 | + | ] | |
| 2978 | + | ||
| 2979 | + | [[package]] | |
| 2980 | + | name = "windows-threading" | |
| 2981 | + | version = "0.2.1" | |
| 2982 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2983 | + | checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" | |
| 2984 | + | dependencies = [ | |
| 2985 | + | "windows-link", | |
| 2986 | + | ] | |
| 2987 | + | ||
| 2988 | + | [[package]] | |
| 2989 | + | name = "windows_aarch64_gnullvm" | |
| 2990 | + | version = "0.52.6" | |
| 2991 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2992 | + | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | |
| 2993 | + | ||
| 2994 | + | [[package]] | |
| 2995 | + | name = "windows_aarch64_msvc" | |
| 2996 | + | version = "0.52.6" | |
| 2997 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2998 | + | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | |
| 2999 | + | ||
| 3000 | + | [[package]] | |
| 3001 | + | name = "windows_i686_gnu" | |
| 3002 | + | version = "0.52.6" | |
| 3003 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3004 | + | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | |
| 3005 | + | ||
| 3006 | + | [[package]] | |
| 3007 | + | name = "windows_i686_gnullvm" | |
| 3008 | + | version = "0.52.6" | |
| 3009 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3010 | + | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | |
| 3011 | + | ||
| 3012 | + | [[package]] | |
| 3013 | + | name = "windows_i686_msvc" | |
| 3014 | + | version = "0.52.6" | |
| 3015 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3016 | + | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | |
| 3017 | + | ||
| 3018 | + | [[package]] | |
| 3019 | + | name = "windows_x86_64_gnu" | |
| 3020 | + | version = "0.52.6" | |
| 3021 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3022 | + | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | |
| 3023 | + | ||
| 3024 | + | [[package]] | |
| 3025 | + | name = "windows_x86_64_gnullvm" | |
| 3026 | + | version = "0.52.6" | |
| 3027 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3028 | + | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | |
| 3029 | + | ||
| 3030 | + | [[package]] | |
| 3031 | + | name = "windows_x86_64_msvc" | |
| 3032 | + | version = "0.52.6" | |
| 3033 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3034 | + | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | |
| 3035 | + | ||
| 3036 | + | [[package]] | |
| 3037 | + | name = "wnaf" | |
| 3038 | + | version = "0.14.1" | |
| 3039 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3040 | + | checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" | |
| 3041 | + | dependencies = [ | |
| 3042 | + | "ff", | |
| 3043 | + | "group", | |
| 3044 | + | "hybrid-array", | |
| 3045 | + | "primefield", | |
| 3046 | + | ] | |
| 3047 | + | ||
| 3048 | + | [[package]] | |
| 3049 | + | name = "writeable" | |
| 3050 | + | version = "0.6.4" | |
| 3051 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3052 | + | checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" | |
| 3053 | + | ||
| 3054 | + | [[package]] | |
| 3055 | + | name = "yoke" | |
| 3056 | + | version = "0.8.3" | |
| 3057 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3058 | + | checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" | |
| 3059 | + | dependencies = [ | |
| 3060 | + | "stable_deref_trait", | |
| 3061 | + | "yoke-derive", | |
| 3062 | + | "zerofrom", | |
| 3063 | + | ] | |
| 3064 | + | ||
| 3065 | + | [[package]] | |
| 3066 | + | name = "yoke-derive" | |
| 3067 | + | version = "0.8.4" | |
| 3068 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3069 | + | checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" | |
| 3070 | + | dependencies = [ | |
| 3071 | + | "proc-macro2", | |
| 3072 | + | "quote", | |
| 3073 | + | "syn 3.0.6", | |
| 3074 | + | "synstructure", | |
| 3075 | + | ] | |
| 3076 | + | ||
| 3077 | + | [[package]] | |
| 3078 | + | name = "zerofrom" | |
| 3079 | + | version = "0.1.8" | |
| 3080 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3081 | + | checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" | |
| 3082 | + | dependencies = [ | |
| 3083 | + | "zerofrom-derive", | |
| 3084 | + | ] | |
| 3085 | + | ||
| 3086 | + | [[package]] | |
| 3087 | + | name = "zerofrom-derive" | |
| 3088 | + | version = "0.1.8" | |
| 3089 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3090 | + | checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" | |
| 3091 | + | dependencies = [ | |
| 3092 | + | "proc-macro2", | |
| 3093 | + | "quote", | |
| 3094 | + | "syn 3.0.6", | |
| 3095 | + | "synstructure", | |
| 3096 | + | ] | |
| 3097 | + | ||
| 3098 | + | [[package]] | |
| 3099 | + | name = "zeroize" | |
| 3100 | + | version = "1.9.0" | |
| 3101 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3102 | + | checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" | |
| 3103 | + | ||
| 3104 | + | [[package]] | |
| 3105 | + | name = "zerotrie" | |
| 3106 | + | version = "0.2.5" | |
| 3107 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3108 | + | checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" | |
| 3109 | + | dependencies = [ | |
| 3110 | + | "displaydoc", | |
| 3111 | + | "yoke", | |
| 3112 | + | "zerofrom", | |
| 3113 | + | ] | |
| 3114 | + | ||
| 3115 | + | [[package]] | |
| 3116 | + | name = "zerovec" | |
| 3117 | + | version = "0.11.8" | |
| 3118 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3119 | + | checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" | |
| 3120 | + | dependencies = [ | |
| 3121 | + | "yoke", | |
| 3122 | + | "zerofrom", | |
| 3123 | + | "zerovec-derive", | |
| 3124 | + | ] | |
| 3125 | + | ||
| 3126 | + | [[package]] | |
| 3127 | + | name = "zerovec-derive" | |
| 3128 | + | version = "0.11.6" | |
| 3129 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3130 | + | checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" | |
| 3131 | + | dependencies = [ | |
| 3132 | + | "proc-macro2", | |
| 3133 | + | "quote", | |
| 3134 | + | "syn 3.0.6", | |
| 3135 | + | ] | |
| 3136 | + | ||
| 3137 | + | [[package]] | |
| 3138 | + | name = "zlib-rs" | |
| 3139 | + | version = "0.6.8" | |
| 3140 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3141 | + | checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" | |
| 3142 | + | ||
| 3143 | + | [[package]] | |
| 3144 | + | name = "zmij" | |
| 3145 | + | version = "1.0.23" | |
| 3146 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3147 | + | checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" |
| 1 | + | [workspace] | |
| 2 | + | resolver = "3" | |
| 3 | + | members = ["crates/*"] | |
| 4 | + | ||
| 5 | + | [workspace.package] | |
| 6 | + | edition = "2024" | |
| 7 | + | license = "MIT" | |
| 8 | + | repository = "https://g1t.sh/syntaqx/g1t" | |
| 9 | + | ||
| 10 | + | [profile.release] | |
| 11 | + | strip = true | |
| 12 | + | lto = "thin" |
| 1 | + | MIT License | |
| 2 | + | ||
| 3 | + | Copyright (c) 2026 Chase Pierce | |
| 4 | + | ||
| 5 | + | Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | + | of this software and associated documentation files (the "Software"), to deal | |
| 7 | + | in the Software without restriction, including without limitation the rights | |
| 8 | + | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | + | copies of the Software, and to permit persons to whom the Software is | |
| 10 | + | furnished to do so, subject to the following conditions: | |
| 11 | + | ||
| 12 | + | The above copyright notice and this permission notice shall be included in all | |
| 13 | + | copies or substantial portions of the Software. | |
| 14 | + | ||
| 15 | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | + | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | + | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | + | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | + | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | + | SOFTWARE. |
| 1 | + | --- | |
| 2 | + | name: react-router | |
| 3 | + | description: Build applications with React Router in Framework, Data, Declarative, and unstable RSC modes. Use when configuring routes, route modules, loaders, actions, forms, fetchers, navigation, pending UI, SSR/SPA/pre-rendering, middleware, URL params/search params, or React Router upgrades. | |
| 4 | + | license: MIT | |
| 5 | + | --- | |
| 6 | + | ||
| 7 | + | # React Router | |
| 8 | + | ||
| 9 | + | React Router is mode-specific. Before changing an app, identify the mode, load the matching reference, then read the installed docs for the installed package version. | |
| 10 | + | ||
| 11 | + | ## Identify the Mode | |
| 12 | + | ||
| 13 | + | Do not apply Framework/Data patterns to a Declarative app unless you are intentionally migrating modes. | |
| 14 | + | ||
| 15 | + | ### Framework Mode | |
| 16 | + | ||
| 17 | + | Use Framework Mode guidance when you see: | |
| 18 | + | ||
| 19 | + | - `@react-router/dev` in dependencies | |
| 20 | + | - `react-router.config.ts` | |
| 21 | + | - `app/routes.ts` | |
| 22 | + | - `app/entry.server.tsx` and/or `app/entry.client.tsx` files | |
| 23 | + | - route modules under `app/routes/` | |
| 24 | + | - route exports like `loader`, `action`, `clientLoader`, `clientAction`, `ErrorBoundary`, `meta`, `links`, or `headers` | |
| 25 | + | - imports from `./+types/...` | |
| 26 | + | - the React Router Vite plugin from `@react-router/dev/vite` | |
| 27 | + | ||
| 28 | + | Framework examples usually use the default `app/` directory, but check `react-router.config.ts` for a custom `appDirectory` before assuming exact paths. | |
| 29 | + | ||
| 30 | + | Then read `references/framework-mode.md`. | |
| 31 | + | ||
| 32 | + | ### Data Mode | |
| 33 | + | ||
| 34 | + | Use Data Mode guidance when you see: | |
| 35 | + | ||
| 36 | + | - `createBrowserRouter`, `createHashRouter`, `createMemoryRouter`, or `createStaticRouter` | |
| 37 | + | - `<RouterProvider router={router}>` | |
| 38 | + | - route objects with properties like `path`, `children`, `loader`, `action`, `Component`, `ErrorBoundary`, or `lazy` | |
| 39 | + | - data APIs without the Framework Vite plugin | |
| 40 | + | ||
| 41 | + | Then read `references/data-mode.md`. | |
| 42 | + | ||
| 43 | + | ### Declarative Mode | |
| 44 | + | ||
| 45 | + | Use Declarative Mode guidance when you see: | |
| 46 | + | ||
| 47 | + | - `<BrowserRouter>`, `<HashRouter>`, or `<MemoryRouter>` | |
| 48 | + | - `<Routes>` and `<Route>` JSX route configuration | |
| 49 | + | - route components passed with `element={<Component />}` | |
| 50 | + | - no data router, no route module convention, and no loaders/actions | |
| 51 | + | ||
| 52 | + | Then read `references/declarative-mode.md`. | |
| 53 | + | ||
| 54 | + | ### RSC Framework and RSC Data Modes | |
| 55 | + | ||
| 56 | + | React Server Components support is unstable and exists in both Framework and Data variants. Use RSC guidance when you see: | |
| 57 | + | ||
| 58 | + | - `unstable_reactRouterRSC` | |
| 59 | + | - `@vitejs/plugin-rsc` | |
| 60 | + | - `unstable_RSCRouteConfig` | |
| 61 | + | - RSC entry files such as `entry.rsc` | |
| 62 | + | - `ServerComponent`, `ServerErrorBoundary`, `ServerLayout`, or `ServerHydrateFallback` | |
| 63 | + | - React directives or boundary packages such as `"use client"`, `"server-only"`, or `"client-only"` | |
| 64 | + | ||
| 65 | + | For RSC Framework, read both `references/framework-mode.md` and `references/rsc.md`. | |
| 66 | + | For RSC Data, read both `references/data-mode.md` and `references/rsc.md`. | |
| 67 | + | ||
| 68 | + | ## Use Installed Docs as Source of Truth | |
| 69 | + | ||
| 70 | + | React Router ships markdown docs in the package so guidance can match the installed version: | |
| 71 | + | ||
| 72 | + | ```txt | |
| 73 | + | node_modules/react-router/docs/ | |
| 74 | + | ``` | |
| 75 | + | ||
| 76 | + | Key docs paths: | |
| 77 | + | ||
| 78 | + | ```txt | |
| 79 | + | node_modules/react-router/docs/index.md | |
| 80 | + | node_modules/react-router/docs/start/ | |
| 81 | + | node_modules/react-router/docs/how-to/ | |
| 82 | + | node_modules/react-router/docs/explanation/ | |
| 83 | + | node_modules/react-router/docs/upgrading/ | |
| 84 | + | ``` | |
| 85 | + | ||
| 86 | + | When this skill references `react-router/docs/...`, read the matching file under `node_modules/react-router/docs/`. If the installed version does not include local docs, use the repo `docs/` directory when working inside the React Router repository; in a consuming app, fall back to version-matched website docs. | |
| 87 | + | ||
| 88 | + | Most docs include a mode marker near the top: | |
| 89 | + | ||
| 90 | + | ```txt | |
| 91 | + | [MODES: framework, data, declarative] | |
| 92 | + | ``` | |
| 93 | + | ||
| 94 | + | Only apply a doc when its mode marker matches the app mode. If a task spans modes, prefer the section or file that matches the current app. | |
| 95 | + | ||
| 96 | + | RSC is documented primarily in: | |
| 97 | + | ||
| 98 | + | ```txt | |
| 99 | + | node_modules/react-router/docs/how-to/react-server-components.md | |
| 100 | + | ``` | |
| 101 | + | ||
| 102 | + | ## Skill References | |
| 103 | + | ||
| 104 | + | Load the relevant reference after identifying the mode: | |
| 105 | + | ||
| 106 | + | | Reference | Use When | | |
| 107 | + | | -------------------------------- | --------------------------------------------- | | |
| 108 | + | | `references/framework-mode.md` | Framework Mode or RSC Framework base behavior | | |
| 109 | + | | `references/data-mode.md` | Data Mode or RSC Data base behavior | | |
| 110 | + | | `references/declarative-mode.md` | Declarative Mode | | |
| 111 | + | | `references/rsc.md` | Any unstable RSC app | | |
| 112 | + | ||
| 113 | + | ## Mode Migration Doc Index | |
| 114 | + | ||
| 115 | + | If the user explicitly asks to switch modes, read the target mode reference plus the migration-relevant docs: | |
| 116 | + | ||
| 117 | + | | Migration | Docs to read | | |
| 118 | + | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | |
| 119 | + | | Declarative → Data | `react-router/docs/start/modes.md`, `react-router/docs/start/data/routing.md`, `react-router/docs/start/data/data-loading.md`, `react-router/docs/start/data/actions.md` | | |
| 120 | + | | Declarative/Data → Framework | `react-router/docs/start/modes.md`, `react-router/docs/start/framework/routing.md`, `react-router/docs/start/framework/route-module.md`, `react-router/docs/how-to/route-module-type-safety.md` | | |
| 121 | + | | Framework SPA/SSR/pre-render changes | `react-router/docs/start/framework/rendering.md`, `react-router/docs/how-to/spa.md`, `react-router/docs/how-to/pre-rendering.md`, `react-router/docs/start/framework/data-loading.md`, `react-router/docs/start/framework/actions.md` | | |
| 122 | + | | Future flags/upgrades | `react-router/docs/upgrading/future.md` and relevant files under `react-router/docs/upgrading/` | |
| 1 | + | # Data Mode | |
| 2 | + | ||
| 3 | + | Data Mode uses data routers such as `createBrowserRouter` and renders with `<RouterProvider>`. It gives an app route objects, loaders, actions, pending UI, fetchers, and SSR primitives without adopting the Framework Vite plugin or route-module file conventions. | |
| 4 | + | ||
| 5 | + | Use this reference after the main skill identifies a Data Mode app. | |
| 6 | + | ||
| 7 | + | ## Read the Local Docs by Mode | |
| 8 | + | ||
| 9 | + | Start with: | |
| 10 | + | ||
| 11 | + | ```txt | |
| 12 | + | react-router/docs/start/modes.md | |
| 13 | + | react-router/docs/start/data/index.md | |
| 14 | + | ``` | |
| 15 | + | ||
| 16 | + | Then use the Data docs under: | |
| 17 | + | ||
| 18 | + | ```txt | |
| 19 | + | react-router/docs/start/data/ | |
| 20 | + | ``` | |
| 21 | + | ||
| 22 | + | Those files cover installation, route objects, routing, data loading, actions, navigation, pending UI, and testing. For task-specific details, read relevant files in: | |
| 23 | + | ||
| 24 | + | ```txt | |
| 25 | + | react-router/docs/how-to/ | |
| 26 | + | react-router/docs/explanation/ | |
| 27 | + | ``` | |
| 28 | + | ||
| 29 | + | Always check the `[MODES: data, ...]` marker in a doc before applying it. | |
| 30 | + | ||
| 31 | + | ## Data Router Shape | |
| 32 | + | ||
| 33 | + | Typical setup: | |
| 34 | + | ||
| 35 | + | ```tsx | |
| 36 | + | import { createBrowserRouter, RouterProvider } from "react-router"; | |
| 37 | + | ||
| 38 | + | const router = createBrowserRouter([ | |
| 39 | + | { | |
| 40 | + | path: "/", | |
| 41 | + | Component: Root, | |
| 42 | + | loader: rootLoader, | |
| 43 | + | children: [ | |
| 44 | + | { index: true, Component: Home }, | |
| 45 | + | { | |
| 46 | + | path: "projects/:projectId", | |
| 47 | + | Component: Project, | |
| 48 | + | loader: projectLoader, | |
| 49 | + | }, | |
| 50 | + | ], | |
| 51 | + | }, | |
| 52 | + | ]); | |
| 53 | + | ||
| 54 | + | root.render(<RouterProvider router={router} />); | |
| 55 | + | ``` | |
| 56 | + | ||
| 57 | + | Look for route object arrays and APIs such as: | |
| 58 | + | ||
| 59 | + | - `createBrowserRouter` | |
| 60 | + | - `createHashRouter` | |
| 61 | + | - `createMemoryRouter` | |
| 62 | + | - `RouterProvider` | |
| 63 | + | - `loader` | |
| 64 | + | - `action` | |
| 65 | + | - `Component` | |
| 66 | + | - `ErrorBoundary` | |
| 67 | + | - `lazy` | |
| 68 | + | - `children` | |
| 69 | + | ||
| 70 | + | ## Route Objects and Routing | |
| 71 | + | ||
| 72 | + | Before editing route configuration, read: | |
| 73 | + | ||
| 74 | + | ```txt | |
| 75 | + | react-router/docs/start/data/routing.md | |
| 76 | + | react-router/docs/start/data/route-object.md | |
| 77 | + | ``` | |
| 78 | + | ||
| 79 | + | Rules: | |
| 80 | + | ||
| 81 | + | - Keep route objects outside render when possible. | |
| 82 | + | - Use nested routes for shared layouts and data boundaries. | |
| 83 | + | - Use index routes for default child content. | |
| 84 | + | - Use dynamic segments and splats according to route-object docs. | |
| 85 | + | - Prefer `Component`/`ErrorBoundary` route object properties in Data Mode examples unless the existing app uses `element` consistently. | |
| 86 | + | ||
| 87 | + | ## Data and Mutations | |
| 88 | + | ||
| 89 | + | Before working on data loading or mutations, read: | |
| 90 | + | ||
| 91 | + | ```txt | |
| 92 | + | react-router/docs/start/data/data-loading.md | |
| 93 | + | react-router/docs/start/data/actions.md | |
| 94 | + | ``` | |
| 95 | + | ||
| 96 | + | Rules: | |
| 97 | + | ||
| 98 | + | - Load route data with route `loader` functions. | |
| 99 | + | - Mutate route data with route `action` functions. | |
| 100 | + | - Prefer loaders/actions over route-level `useEffect` fetching. | |
| 101 | + | - Use `request`, `params`, and returned/throwable Responses as described in the docs. | |
| 102 | + | - Let React Router revalidate after actions unless there is a documented reason to customize revalidation. | |
| 103 | + | ||
| 104 | + | Common patterns: | |
| 105 | + | ||
| 106 | + | - Validation failure from an action: return `data({ errors, values }, { status: 400 })`, then render errors with `useActionData()` or `fetcher.data`. | |
| 107 | + | - Missing record in a loader: throw `data("Not Found", { status: 404 })` and render the route `ErrorBoundary`. | |
| 108 | + | - Search/filter data: parse `new URL(request.url).searchParams` in the loader so the URL is shareable and bookmarkable. | |
| 109 | + | ||
| 110 | + | ## Forms, Fetchers, and Pending UI | |
| 111 | + | ||
| 112 | + | For forms and pending UI, read: | |
| 113 | + | ||
| 114 | + | ```txt | |
| 115 | + | react-router/docs/start/data/actions.md | |
| 116 | + | react-router/docs/start/data/pending-ui.md | |
| 117 | + | react-router/docs/how-to/fetchers.md | |
| 118 | + | react-router/docs/explanation/form-vs-fetcher.md | |
| 119 | + | ``` | |
| 120 | + | ||
| 121 | + | Rules of thumb: | |
| 122 | + | ||
| 123 | + | - Search/filter form that updates the URL: `<Form method="get">`. | |
| 124 | + | - Mutation that should change URL/history or redirect after completion: `<Form method="post">`. | |
| 125 | + | - Mutation that should keep the user on the same page: `useFetcher` / `<fetcher.Form>`. | |
| 126 | + | - Optimistic UI: derive from `fetcher.formData` or `navigation.formData`. | |
| 127 | + | ||
| 128 | + | ## Navigation and URL State | |
| 129 | + | ||
| 130 | + | Before changing navigation or search params, read: | |
| 131 | + | ||
| 132 | + | ```txt | |
| 133 | + | react-router/docs/start/data/navigating.md | |
| 134 | + | react-router/docs/how-to/search-params.md | |
| 135 | + | react-router/docs/explanation/location.md | |
| 136 | + | ``` | |
| 137 | + | ||
| 138 | + | Rules: | |
| 139 | + | ||
| 140 | + | - Use `<Link>`/`<NavLink>` for user-initiated internal navigation. | |
| 141 | + | - Use `redirect` in loaders/actions when navigation follows data loading or mutations. | |
| 142 | + | - Use `useNavigate` for imperative client-side event navigation. | |
| 143 | + | - Treat URL params as strings and validate/parse them. | |
| 144 | + | - Preserve unrelated search params unless intentionally resetting them. | |
| 145 | + | ||
| 146 | + | ## SSR in Data Mode | |
| 147 | + | ||
| 148 | + | Data Mode SSR is manual and lower-level than Framework Mode. Before implementing or changing SSR, read the Data Mode custom/SSR docs and match existing server abstractions. | |
| 149 | + | ||
| 150 | + | Start with: | |
| 151 | + | ||
| 152 | + | ```txt | |
| 153 | + | react-router/docs/start/data/custom.md | |
| 154 | + | ``` | |
| 155 | + | ||
| 156 | + | Look for APIs like `createStaticHandler`, `createStaticRouter`, `StaticRouterProvider`, and hydration data handling in the current app before changing anything. | |
| 157 | + | ||
| 158 | + | ## RSC Data | |
| 159 | + | ||
| 160 | + | If this Data Mode app uses `unstable_RSCRouteConfig`, RSC route config, or low-level RSC server APIs, also read: | |
| 161 | + | ||
| 162 | + | ```txt | |
| 163 | + | references/rsc.md | |
| 164 | + | react-router/docs/how-to/react-server-components.md | |
| 165 | + | ``` |
| 1 | + | # Declarative Mode | |
| 2 | + | ||
| 3 | + | Declarative Mode is React Router's simplest mode. It uses router components like `<BrowserRouter>` and JSX routes with `<Routes>`/`<Route>`. It does not provide loaders, actions, fetchers, or data-router pending UI. | |
| 4 | + | ||
| 5 | + | Use this reference after the main skill identifies a Declarative Mode app. | |
| 6 | + | ||
| 7 | + | ## Read the Local Docs by Mode | |
| 8 | + | ||
| 9 | + | Start with: | |
| 10 | + | ||
| 11 | + | ```txt | |
| 12 | + | react-router/docs/start/modes.md | |
| 13 | + | react-router/docs/start/declarative/index.md | |
| 14 | + | ``` | |
| 15 | + | ||
| 16 | + | Then use the Declarative docs under: | |
| 17 | + | ||
| 18 | + | ```txt | |
| 19 | + | react-router/docs/start/declarative/ | |
| 20 | + | ``` | |
| 21 | + | ||
| 22 | + | Those files cover installation, routing, navigation, and URL values. For conceptual details, read relevant files in: | |
| 23 | + | ||
| 24 | + | ```txt | |
| 25 | + | react-router/docs/explanation/ | |
| 26 | + | ``` | |
| 27 | + | ||
| 28 | + | Always check the `[MODES: declarative, ...]` marker in a doc before applying it. | |
| 29 | + | ||
| 30 | + | ## Declarative Router Shape | |
| 31 | + | ||
| 32 | + | Typical setup: | |
| 33 | + | ||
| 34 | + | ```tsx | |
| 35 | + | import { BrowserRouter, Routes, Route } from "react-router"; | |
| 36 | + | ||
| 37 | + | function App() { | |
| 38 | + | return ( | |
| 39 | + | <BrowserRouter> | |
| 40 | + | <Routes> | |
| 41 | + | <Route path="/" element={<Home />} /> | |
| 42 | + | <Route path="about" element={<About />} /> | |
| 43 | + | <Route path="dashboard" element={<DashboardLayout />}> | |
| 44 | + | <Route index element={<DashboardHome />} /> | |
| 45 | + | <Route path="settings" element={<Settings />} /> | |
| 46 | + | </Route> | |
| 47 | + | </Routes> | |
| 48 | + | </BrowserRouter> | |
| 49 | + | ); | |
| 50 | + | } | |
| 51 | + | ``` | |
| 52 | + | ||
| 53 | + | Look for APIs such as: | |
| 54 | + | ||
| 55 | + | - `<BrowserRouter>` | |
| 56 | + | - `<HashRouter>` | |
| 57 | + | - `<MemoryRouter>` | |
| 58 | + | - `<Routes>` | |
| 59 | + | - `<Route>` | |
| 60 | + | - `element={<Component />}` | |
| 61 | + | - `useRoutes` | |
| 62 | + | ||
| 63 | + | ## Routing | |
| 64 | + | ||
| 65 | + | Before editing routes, read: | |
| 66 | + | ||
| 67 | + | ```txt | |
| 68 | + | react-router/docs/start/declarative/routing.md | |
| 69 | + | ``` | |
| 70 | + | ||
| 71 | + | Rules: | |
| 72 | + | ||
| 73 | + | - Use `<Routes>` and `<Route>` for route configuration. | |
| 74 | + | - Use nested routes with `<Outlet>` for shared layout. | |
| 75 | + | - Use index routes for default child UI. | |
| 76 | + | - Use route params and splats according to the declarative routing docs. | |
| 77 | + | - Do not add route object loaders/actions to a Declarative router. | |
| 78 | + | ||
| 79 | + | ## Navigation | |
| 80 | + | ||
| 81 | + | Before changing navigation, read: | |
| 82 | + | ||
| 83 | + | ```txt | |
| 84 | + | react-router/docs/start/declarative/navigating.md | |
| 85 | + | ``` | |
| 86 | + | ||
| 87 | + | Rules: | |
| 88 | + | ||
| 89 | + | - Use `<Link>` or `<NavLink>` for user-initiated internal navigation. | |
| 90 | + | - Use `NavLink` when active styling matters. | |
| 91 | + | - Use `useNavigate` for imperative navigation from event handlers or effects. | |
| 92 | + | - Do not use plain `<a href>` for internal navigation unless intentionally forcing a full document navigation. | |
| 93 | + | ||
| 94 | + | ## URL Values | |
| 95 | + | ||
| 96 | + | Before changing params, search params, or location state, read: | |
| 97 | + | ||
| 98 | + | ```txt | |
| 99 | + | react-router/docs/start/declarative/url-values.md | |
| 100 | + | react-router/docs/explanation/location.md | |
| 101 | + | ``` | |
| 102 | + | ||
| 103 | + | Rules: | |
| 104 | + | ||
| 105 | + | - Use `useParams` for dynamic route params. | |
| 106 | + | - Use `useSearchParams` for query string state. | |
| 107 | + | - Use `useLocation` for the current location object and navigation state. | |
| 108 | + | - Validate and parse URL params; they are strings and can be absent. | |
| 109 | + | - Preserve unrelated search params unless intentionally resetting them. | |
| 110 | + | ||
| 111 | + | ## Mode Boundary | |
| 112 | + | ||
| 113 | + | Declarative Mode does not have Data/Framework APIs such as: | |
| 114 | + | ||
| 115 | + | - `loader` | |
| 116 | + | - `action` | |
| 117 | + | - `<Form>` | |
| 118 | + | - `useFetcher` | |
| 119 | + | - `useNavigation` | |
| 120 | + | - route module exports | |
| 121 | + | - generated `./+types` route types | |
| 122 | + | ||
| 123 | + | If the user asks for route data loading, DB/API-backed data, CRUD, form mutations, validation returned from submissions, revalidation, pending UI, optimistic UI, or fetchers, recommend Data Mode or Framework Mode depending on how much structure they want. Ask before migrating unless they already requested it. |
| 1 | + | # Framework Mode | |
| 2 | + | ||
| 3 | + | Framework Mode is React Router's full-stack mode. It uses the React Router Vite plugin, route config in `app/routes.ts`, route modules, generated route types, and rendering strategies such as SSR, SPA mode, and pre-rendering. | |
| 4 | + | ||
| 5 | + | Use this reference after the main skill identifies a Framework Mode app. | |
| 6 | + | ||
| 7 | + | ## Read the Local Docs by Mode | |
| 8 | + | ||
| 9 | + | Start with: | |
| 10 | + | ||
| 11 | + | ```txt | |
| 12 | + | react-router/docs/start/modes.md | |
| 13 | + | react-router/docs/start/framework/index.md | |
| 14 | + | ``` | |
| 15 | + | ||
| 16 | + | Then use the Framework docs under: | |
| 17 | + | ||
| 18 | + | ```txt | |
| 19 | + | react-router/docs/start/framework/ | |
| 20 | + | ``` | |
| 21 | + | ||
| 22 | + | Those files cover installation, routing, route modules, data loading, actions, navigation, pending UI, rendering, deploying, and testing. For task-specific details, read relevant files in: | |
| 23 | + | ||
| 24 | + | ```txt | |
| 25 | + | react-router/docs/how-to/ | |
| 26 | + | react-router/docs/explanation/ | |
| 27 | + | ``` | |
| 28 | + | ||
| 29 | + | Always check the `[MODES: framework, ...]` marker in a doc before applying it. | |
| 30 | + | ||
| 31 | + | ## Framework Shape | |
| 32 | + | ||
| 33 | + | Examples usually assume the default `appDirectory` of `app`. Check `react-router.config.ts` before assuming exact paths. | |
| 34 | + | ||
| 35 | + | Look for these files and conventions: | |
| 36 | + | ||
| 37 | + | ```txt | |
| 38 | + | react-router.config.ts | |
| 39 | + | app/root.tsx | |
| 40 | + | app/routes.ts | |
| 41 | + | app/routes/**/*.tsx | |
| 42 | + | route modules importing from ./+types/... | |
| 43 | + | ``` | |
| 44 | + | ||
| 45 | + | Typical route module: | |
| 46 | + | ||
| 47 | + | ```tsx | |
| 48 | + | import type { Route } from "./+types/product"; | |
| 49 | + | ||
| 50 | + | export async function loader({ params }: Route.LoaderArgs) { | |
| 51 | + | return { product: await getProduct(params.productId) }; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | export default function Product({ loaderData }: Route.ComponentProps) { | |
| 55 | + | return <h1>{loaderData.product.name}</h1>; | |
| 56 | + | } | |
| 57 | + | ``` | |
| 58 | + | ||
| 59 | + | ## Route Configuration | |
| 60 | + | ||
| 61 | + | Framework apps use `app/routes.ts`. Many apps use file-system routing via `flatRoutes()`, but manual route config is also supported. | |
| 62 | + | ||
| 63 | + | Before editing routes, read: | |
| 64 | + | ||
| 65 | + | ```txt | |
| 66 | + | react-router/docs/start/framework/routing.md | |
| 67 | + | ``` | |
| 68 | + | ||
| 69 | + | If the app uses file-route conventions, read: | |
| 70 | + | ||
| 71 | + | ```txt | |
| 72 | + | react-router/docs/how-to/file-route-conventions.md | |
| 73 | + | ``` | |
| 74 | + | ||
| 75 | + | ## Route Modules | |
| 76 | + | ||
| 77 | + | Route modules are the main unit of Framework Mode. Before adding or changing route exports, read: | |
| 78 | + | ||
| 79 | + | ```txt | |
| 80 | + | react-router/docs/start/framework/route-module.md | |
| 81 | + | ``` | |
| 82 | + | ||
| 83 | + | Common exports include: | |
| 84 | + | ||
| 85 | + | | Export | Use | | |
| 86 | + | | --------------------------------- | ------------------------------------------------------------------- | | |
| 87 | + | | `default` | Route component rendered for the match | | |
| 88 | + | | `loader` | Server data loading for SSR/pre-rendering/server data requests | | |
| 89 | + | | `clientLoader` | Browser-only data loading or supplementing server loader data | | |
| 90 | + | | `action` | Server mutation called by `<Form>`, `useSubmit`, or fetchers | | |
| 91 | + | | `clientAction` | Browser-only mutation or client-side wrapper around a server action | | |
| 92 | + | | `ErrorBoundary` | UI for errors thrown by this route's loaders/actions/component | | |
| 93 | + | | `HydrateFallback` | Initial fallback while client loader hydration runs | | |
| 94 | + | | `links` / `meta` | Route document links and metadata | | |
| 95 | + | | `handle` | Arbitrary route metadata consumed via `useMatches` | | |
| 96 | + | | `shouldRevalidate` | Overrides default loader revalidation behavior | | |
| 97 | + | | `middleware` / `clientMiddleware` | Server/client request pipeline hooks when enabled | | |
| 98 | + | ||
| 99 | + | Use generated `Route.*` types from `./+types/<route>` for route module args and props. | |
| 100 | + | ||
| 101 | + | ## Layout and Root Route Rules | |
| 102 | + | ||
| 103 | + | - `app/root.tsx` is the root route and should contain global document/app shell concerns. | |
| 104 | + | - Put global providers, app-wide nav, app-wide footer, scripts/meta/links, and document structure in `root.tsx` when appropriate. | |
| 105 | + | - Use nested routes/layout routes for section-specific layouts. | |
| 106 | + | - Do not flatten routes that should share UI or data boundaries. | |
| 107 | + | ||
| 108 | + | Useful docs: | |
| 109 | + | ||
| 110 | + | ```txt | |
| 111 | + | react-router/docs/explanation/special-files.md | |
| 112 | + | react-router/docs/start/framework/routing.md | |
| 113 | + | ``` | |
| 114 | + | ||
| 115 | + | ## Data and Mutations | |
| 116 | + | ||
| 117 | + | Before working on route data: | |
| 118 | + | ||
| 119 | + | ```txt | |
| 120 | + | react-router/docs/start/framework/data-loading.md | |
| 121 | + | react-router/docs/start/framework/actions.md | |
| 122 | + | ``` | |
| 123 | + | ||
| 124 | + | Framework rules: | |
| 125 | + | ||
| 126 | + | - Load route data with `loader` or `clientLoader`. | |
| 127 | + | - Mutate route data with `action` or `clientAction`. | |
| 128 | + | - Prefer route loaders/actions over ad hoc `useEffect` fetching for route data. | |
| 129 | + | - Use `data()`/Responses and redirects according to the docs. | |
| 130 | + | - Let React Router revalidate after actions unless the docs point you to `shouldRevalidate`. | |
| 131 | + | - In SSR/server data routes, keep Node-only/database code in server-only modules and call it from `loader`/`action`, not from browser-rendered component code. | |
| 132 | + | ||
| 133 | + | Common patterns: | |
| 134 | + | ||
| 135 | + | - Validation failure from an action: return `data({ errors, values }, { status: 400 })`, then render errors from `Route.ComponentProps["actionData"]` or `fetcher.data`. | |
| 136 | + | - Missing record in a loader: throw `data("Not Found", { status: 404 })` and render the route `ErrorBoundary`. | |
| 137 | + | - Search/filter data: parse the route request URL/search params in the loader so the URL is shareable and bookmarkable. | |
| 138 | + | ||
| 139 | + | ## Forms, Fetchers, and Pending UI | |
| 140 | + | ||
| 141 | + | For forms and pending UI, read: | |
| 142 | + | ||
| 143 | + | ```txt | |
| 144 | + | react-router/docs/start/framework/actions.md | |
| 145 | + | react-router/docs/start/framework/pending-ui.md | |
| 146 | + | react-router/docs/how-to/fetchers.md | |
| 147 | + | react-router/docs/explanation/form-vs-fetcher.md | |
| 148 | + | ``` | |
| 149 | + | ||
| 150 | + | Rules of thumb: | |
| 151 | + | ||
| 152 | + | - Search/filter form that updates the URL: `<Form method="get">`. | |
| 153 | + | - Mutation that should change URL/history or redirect after completion: `<Form method="post">`. | |
| 154 | + | - Mutation that should keep the user on the same page: `useFetcher` / `<fetcher.Form>`. | |
| 155 | + | - Optimistic UI: derive from `fetcher.formData` or `navigation.formData`. | |
| 156 | + | ||
| 157 | + | ## Type Safety | |
| 158 | + | ||
| 159 | + | Before changing generated route types or typed URL behavior, read: | |
| 160 | + | ||
| 161 | + | ```txt | |
| 162 | + | react-router/docs/how-to/route-module-type-safety.md | |
| 163 | + | react-router/docs/explanation/type-safety.md | |
| 164 | + | ``` | |
| 165 | + | ||
| 166 | + | Rules: | |
| 167 | + | ||
| 168 | + | - Import types from `./+types/<route>`. | |
| 169 | + | - Use `Route.LoaderArgs`, `Route.ActionArgs`, `Route.ComponentProps`, etc. | |
| 170 | + | - Use type-only imports where appropriate. | |
| 171 | + | - Do not edit generated `.react-router/types` files. | |
| 172 | + | ||
| 173 | + | ## Metadata | |
| 174 | + | ||
| 175 | + | Before changing `meta`, read: | |
| 176 | + | ||
| 177 | + | ```txt | |
| 178 | + | react-router/docs/how-to/meta.md | |
| 179 | + | react-router/docs/start/framework/route-module.md | |
| 180 | + | ``` | |
| 181 | + | ||
| 182 | + | Important: `meta` receives `loaderData`; do not use deprecated `data` args. | |
| 183 | + | ||
| 184 | + | ## Rendering Strategy | |
| 185 | + | ||
| 186 | + | Framework Mode can be SSR, SPA, pre-rendered, or mixed depending on config and route behavior. Before changing rendering behavior, read: | |
| 187 | + | ||
| 188 | + | ```txt | |
| 189 | + | react-router/docs/start/framework/rendering.md | |
| 190 | + | react-router/docs/how-to/spa.md | |
| 191 | + | react-router/docs/how-to/pre-rendering.md | |
| 192 | + | react-router/docs/explanation/hydration.md | |
| 193 | + | ``` | |
| 194 | + | ||
| 195 | + | ## Middleware, Sessions, and Auth | |
| 196 | + | ||
| 197 | + | Before implementing middleware or auth/session flows, read: | |
| 198 | + | ||
| 199 | + | ```txt | |
| 200 | + | react-router/docs/how-to/middleware.md | |
| 201 | + | react-router/docs/explanation/sessions-and-cookies.md | |
| 202 | + | ``` | |
| 203 | + | ||
| 204 | + | Middleware and context APIs are version/config sensitive. Check the installed React Router version and the app's `react-router.config.ts` before implementing. | |
| 205 | + | ||
| 206 | + | ## RSC Framework | |
| 207 | + | ||
| 208 | + | If this Framework app uses `unstable_reactRouterRSC` or `@vitejs/plugin-rsc`, also read: | |
| 209 | + | ||
| 210 | + | ```txt | |
| 211 | + | references/rsc.md | |
| 212 | + | react-router/docs/how-to/react-server-components.md | |
| 213 | + | ``` |
| 1 | + | # React Server Components (RSC) | |
| 2 | + | ||
| 3 | + | React Router's RSC support is unstable and exists in two variants: | |
| 4 | + | ||
| 5 | + | - **RSC Framework Mode**: Framework Mode with the unstable RSC Vite plugin. | |
| 6 | + | - **RSC Data Mode**: lower-level RSC runtime APIs and manual bundler/server integration. | |
| 7 | + | ||
| 8 | + | Use this reference in addition to `framework-mode.md` or `data-mode.md` after the main skill identifies an RSC app. | |
| 9 | + | ||
| 10 | + | ## Read the Local RSC Docs | |
| 11 | + | ||
| 12 | + | Start with: | |
| 13 | + | ||
| 14 | + | ```txt | |
| 15 | + | react-router/docs/how-to/react-server-components.md | |
| 16 | + | ``` | |
| 17 | + | ||
| 18 | + | Then read the relevant base mode docs: | |
| 19 | + | ||
| 20 | + | ```txt | |
| 21 | + | react-router/docs/start/framework/ | |
| 22 | + | react-router/docs/start/data/ | |
| 23 | + | ``` | |
| 24 | + | ||
| 25 | + | RSC docs may describe differences from non-RSC mode rather than repeating every Framework/Data concept, so keep both layers in mind. | |
| 26 | + | ||
| 27 | + | ## Detect RSC Framework Mode | |
| 28 | + | ||
| 29 | + | Look for: | |
| 30 | + | ||
| 31 | + | - `unstable_reactRouterRSC` imported from `@react-router/dev/vite` | |
| 32 | + | - `@vitejs/plugin-rsc` | |
| 33 | + | - `vite.config.ts` with `plugins: [reactRouterRSC(), rsc()]` | |
| 34 | + | - Framework route modules plus RSC route exports | |
| 35 | + | - RSC entry files such as `entry.rsc` | |
| 36 | + | ||
| 37 | + | RSC Framework Mode uses a different Vite plugin from non-RSC Framework Mode. Do not swap it for the regular `reactRouter()` plugin. | |
| 38 | + | ||
| 39 | + | ## Detect RSC Data Mode | |
| 40 | + | ||
| 41 | + | Look for: | |
| 42 | + | ||
| 43 | + | - `unstable_RSCRouteConfig` | |
| 44 | + | - route config passed to lower-level RSC APIs | |
| 45 | + | - APIs such as `unstable_matchRSCServerRequest`, `unstable_routeRSCServerRequest`, `unstable_RSCHydratedRouter`, or `unstable_RSCStaticRouter` | |
| 46 | + | - custom bundler/server setup around RSC | |
| 47 | + | ||
| 48 | + | RSC Data Mode is more manual than RSC Framework Mode. Match the app's bundler and server abstractions before changing routes or entries. | |
| 49 | + | ||
| 50 | + | ## RSC Route Module Differences | |
| 51 | + | ||
| 52 | + | In RSC Framework Mode, many normal Framework Mode concepts still apply, but routes can use server component exports. | |
| 53 | + | ||
| 54 | + | Important route-module concepts from the RSC docs include: | |
| 55 | + | ||
| 56 | + | - `ServerComponent` instead of the usual client `default` component | |
| 57 | + | - `ServerErrorBoundary` paired with `ErrorBoundary` | |
| 58 | + | - `ServerLayout` paired with `Layout` | |
| 59 | + | - `ServerHydrateFallback` paired with `HydrateFallback` | |
| 60 | + | - server-rendered React elements returned from loaders/actions | |
| 61 | + | ||
| 62 | + | A route module cannot export both the normal client component and its server component counterpart for the same role. Read the RSC docs before adding these exports. | |
| 63 | + | ||
| 64 | + | ## Client/Server Boundaries | |
| 65 | + | ||
| 66 | + | RSC code must respect React's client/server split: | |
| 67 | + | ||
| 68 | + | - Use `"use client"` for components that need hooks, browser APIs, or event handlers. | |
| 69 | + | - Use server-only modules for server data access and secrets. | |
| 70 | + | - In RSC Framework Mode, prefer the `server-only` and `client-only` boundary imports described in the docs. | |
| 71 | + | - Do not assume `.server`/`.client` file naming works the same way in RSC Framework Mode; read the RSC docs before relying on those conventions. | |
| 72 | + | ||
| 73 | + | ## Data Loading in RSC | |
| 74 | + | ||
| 75 | + | RSC changes where data can be loaded: | |
| 76 | + | ||
| 77 | + | - Server Components can fetch data directly on the server. | |
| 78 | + | - Loaders/actions may still exist and can have RSC-specific behavior. | |
| 79 | + | - Client components still need client-safe data and cannot directly access server-only modules. | |
| 80 | + | ||
| 81 | + | When choosing between a server component fetch, a loader, and a client loader/action, follow the RSC docs and match existing app patterns. | |
| 82 | + | ||
| 83 | + | ## Stability | |
| 84 | + | ||
| 85 | + | RSC APIs are explicitly unstable. Before implementing or refactoring RSC code: | |
| 86 | + | ||
| 87 | + | - Check the installed React Router version. | |
| 88 | + | - Check the installed `@vitejs/plugin-rsc` version. | |
| 89 | + | - Read the app's existing RSC entry/config files. | |
| 90 | + | - Prefer minimal changes that match current patterns. |
| 1 | + | .DS_Store | |
| 2 | + | .env | |
| 3 | + | /node_modules/ | |
| 4 | + | *.tsbuildinfo | |
| 5 | + | ||
| 6 | + | # React Router | |
| 7 | + | /.react-router/ | |
| 8 | + | /build/ | |
| 9 | + | ||
| 10 | + | # Cloudflare | |
| 11 | + | .mf | |
| 12 | + | .wrangler | |
| 13 | + | .dev.vars* | |
| 14 | + | worker-configuration.d.ts | |
| 15 | + | ||
| 16 | + | !.dev.vars.example | |
| 17 | + | !.env.example |
| 1 | + | { | |
| 2 | + | "files.associations": { | |
| 3 | + | "wrangler.json": "jsonc" | |
| 4 | + | } | |
| 5 | + | } |
| 1 | + | # Welcome to React Router! | |
| 2 | + | ||
| 3 | + | A modern, production-ready template for building full-stack React applications using React Router. | |
| 4 | + | ||
| 5 | + | ## Features | |
| 6 | + | ||
| 7 | + | - 🚀 Server-side rendering | |
| 8 | + | - ⚡️ Hot Module Replacement (HMR) | |
| 9 | + | - 📦 Asset bundling and optimization | |
| 10 | + | - 🔄 Data loading and mutations | |
| 11 | + | - 🔒 TypeScript by default | |
| 12 | + | - 🎉 TailwindCSS for styling | |
| 13 | + | - 📖 [React Router docs](https://reactrouter.com/) | |
| 14 | + | ||
| 15 | + | ## Getting Started | |
| 16 | + | ||
| 17 | + | ### Installation | |
| 18 | + | ||
| 19 | + | Install the dependencies: | |
| 20 | + | ||
| 21 | + | ```bash | |
| 22 | + | npm install | |
| 23 | + | ``` | |
| 24 | + | ||
| 25 | + | ### Development | |
| 26 | + | ||
| 27 | + | Start the development server with HMR: | |
| 28 | + | ||
| 29 | + | ```bash | |
| 30 | + | npm run dev | |
| 31 | + | ``` | |
| 32 | + | ||
| 33 | + | Your application will be available at `http://localhost:5173`. | |
| 34 | + | ||
| 35 | + | ## Previewing the Production Build | |
| 36 | + | ||
| 37 | + | Preview the production build locally: | |
| 38 | + | ||
| 39 | + | ```bash | |
| 40 | + | npm run preview | |
| 41 | + | ``` | |
| 42 | + | ||
| 43 | + | ## Building for Production | |
| 44 | + | ||
| 45 | + | Create a production build: | |
| 46 | + | ||
| 47 | + | ```bash | |
| 48 | + | npm run build | |
| 49 | + | ``` | |
| 50 | + | ||
| 51 | + | ## Deployment | |
| 52 | + | ||
| 53 | + | Deployment is done using the Wrangler CLI. | |
| 54 | + | ||
| 55 | + | To build and deploy directly to production: | |
| 56 | + | ||
| 57 | + | ```sh | |
| 58 | + | npm run deploy | |
| 59 | + | ``` | |
| 60 | + | ||
| 61 | + | To deploy a preview URL: | |
| 62 | + | ||
| 63 | + | ```sh | |
| 64 | + | npx wrangler versions upload | |
| 65 | + | ``` | |
| 66 | + | ||
| 67 | + | You can then promote a version to production after verification or roll it out progressively. | |
| 68 | + | ||
| 69 | + | ```sh | |
| 70 | + | npx wrangler versions deploy | |
| 71 | + | ``` | |
| 72 | + | ||
| 73 | + | ## Styling | |
| 74 | + | ||
| 75 | + | This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. | |
| 76 | + | ||
| 77 | + | --- | |
| 78 | + | ||
| 79 | + | Built with ❤️ using React Router. |
| 1 | + | @import "tailwindcss" source("."); | |
| 2 | + | ||
| 3 | + | @theme { | |
| 4 | + | --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, | |
| 5 | + | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; | |
| 6 | + | --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, | |
| 7 | + | monospace; | |
| 8 | + | ||
| 9 | + | --color-bg: #12110d; | |
| 10 | + | --color-surface: #1b1a15; | |
| 11 | + | --color-line: #2a2822; | |
| 12 | + | --color-fg: #edece6; | |
| 13 | + | --color-muted: #8f8c82; | |
| 14 | + | --color-accent: #b6f24a; | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | html, | |
| 18 | + | body { | |
| 19 | + | @apply bg-bg text-fg font-sans antialiased; | |
| 20 | + | color-scheme: dark; | |
| 21 | + | } |
| 1 | + | /** Three lanes converging on one node: many attempts, one ships. */ | |
| 2 | + | export function Mark({ className }: { className?: string }) { | |
| 3 | + | return ( | |
| 4 | + | <svg | |
| 5 | + | viewBox="0 0 32 32" | |
| 6 | + | fill="none" | |
| 7 | + | strokeWidth="2.5" | |
| 8 | + | strokeLinecap="round" | |
| 9 | + | className={className} | |
| 10 | + | aria-hidden="true" | |
| 11 | + | > | |
| 12 | + | <path d="M4 7h6c7 0 7 9 14 9" className="stroke-muted" /> | |
| 13 | + | <path d="M4 25h6c7 0 7-9 14-9" className="stroke-muted" /> | |
| 14 | + | <path d="M4 16h20" className="stroke-accent" /> | |
| 15 | + | <circle cx="25" cy="16" r="3.5" className="fill-accent" stroke="none" /> | |
| 16 | + | </svg> | |
| 17 | + | ); | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | export function Logo() { | |
| 21 | + | return ( | |
| 22 | + | <span className="inline-flex items-center gap-2"> | |
| 23 | + | <Mark className="size-6" /> | |
| 24 | + | <span className="font-mono text-lg font-semibold tracking-tight"> | |
| 25 | + | g<span className="text-accent">1</span>t | |
| 26 | + | </span> | |
| 27 | + | </span> | |
| 28 | + | ); | |
| 29 | + | } |
| 1 | + | import { Link } from "react-router"; | |
| 2 | + | ||
| 3 | + | import type { BlobView as Blob, TreeView as Tree } from "@g1t/contracts"; | |
| 4 | + | ||
| 5 | + | function encodePath(path: string): string { | |
| 6 | + | return path.split("/").map(encodeURIComponent).join("/"); | |
| 7 | + | } | |
| 8 | + | ||
| 9 | + | function Breadcrumbs({ | |
| 10 | + | base, | |
| 11 | + | repo, | |
| 12 | + | gitRef, | |
| 13 | + | path, | |
| 14 | + | }: { | |
| 15 | + | base: string; | |
| 16 | + | repo: string; | |
| 17 | + | gitRef: string; | |
| 18 | + | path: string; | |
| 19 | + | }) { | |
| 20 | + | const segments = path.split("/").filter(Boolean); | |
| 21 | + | if (segments.length === 0) return null; | |
| 22 | + | return ( | |
| 23 | + | <p className="mb-4 font-mono text-sm text-muted"> | |
| 24 | + | <Link to={`${base}/tree/${gitRef}`} className="hover:text-fg"> | |
| 25 | + | {repo} | |
| 26 | + | </Link> | |
| 27 | + | {segments.map((segment, i) => { | |
| 28 | + | const to = encodePath(segments.slice(0, i + 1).join("/")); | |
| 29 | + | const last = i === segments.length - 1; | |
| 30 | + | return ( | |
| 31 | + | <span key={to}> | |
| 32 | + | {" / "} | |
| 33 | + | {last ? ( | |
| 34 | + | <span className="text-fg">{segment}</span> | |
| 35 | + | ) : ( | |
| 36 | + | <Link to={`${base}/tree/${gitRef}/${to}`} className="hover:text-fg"> | |
| 37 | + | {segment} | |
| 38 | + | </Link> | |
| 39 | + | )} | |
| 40 | + | </span> | |
| 41 | + | ); | |
| 42 | + | })} | |
| 43 | + | </p> | |
| 44 | + | ); | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | export function TreeView({ tree }: { tree: Tree }) { | |
| 48 | + | const { repo, ref, path, head, entries, readme } = tree; | |
| 49 | + | const base = `/${repo.namespace}/${repo.name}`; | |
| 50 | + | const prefix = path ? `${encodePath(path)}/` : ""; | |
| 51 | + | const cloneUrl = `https://g1t.sh${base}.git`; | |
| 52 | + | ||
| 53 | + | if (!head) { | |
| 54 | + | return ( | |
| 55 | + | <div className="mt-6 rounded-md border border-line bg-surface p-6"> | |
| 56 | + | <p className="text-muted">This repository is empty. Push to it:</p> | |
| 57 | + | <pre className="mt-3 overflow-x-auto font-mono text-sm"> | |
| 58 | + | git remote add origin {cloneUrl} | |
| 59 | + | {"\n"}git push -u origin {ref} | |
| 60 | + | </pre> | |
| 61 | + | </div> | |
| 62 | + | ); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | return ( | |
| 66 | + | <div className="mt-6"> | |
| 67 | + | <Breadcrumbs base={base} repo={repo.name} gitRef={ref} path={path} /> | |
| 68 | + | <div className="overflow-hidden rounded-md border border-line"> | |
| 69 | + | <div className="flex items-baseline gap-3 border-b border-line bg-surface px-4 py-2 text-sm"> | |
| 70 | + | <span className="font-mono text-accent">{ref}</span> | |
| 71 | + | <span className="truncate">{head.message.split("\n")[0]}</span> | |
| 72 | + | <span className="ml-auto shrink-0 font-mono text-muted"> | |
| 73 | + | {head.author.name} · {head.hash.slice(0, 7)} | |
| 74 | + | </span> | |
| 75 | + | </div> | |
| 76 | + | <ul className="divide-y divide-line font-mono text-sm"> | |
| 77 | + | {entries.map((entry) => { | |
| 78 | + | const isTree = entry.kind === "tree"; | |
| 79 | + | const kind = isTree ? "tree" : "blob"; | |
| 80 | + | return ( | |
| 81 | + | <li key={entry.name}> | |
| 82 | + | <Link | |
| 83 | + | to={`${base}/${kind}/${ref}/${prefix}${encodeURIComponent(entry.name)}`} | |
| 84 | + | className="block px-4 py-1.5 hover:bg-surface" | |
| 85 | + | > | |
| 86 | + | {entry.name} | |
| 87 | + | {isTree && <span className="text-muted">/</span>} | |
| 88 | + | </Link> | |
| 89 | + | </li> | |
| 90 | + | ); | |
| 91 | + | })} | |
| 92 | + | </ul> | |
| 93 | + | </div> | |
| 94 | + | ||
| 95 | + | {!path && ( | |
| 96 | + | <pre className="mt-4 overflow-x-auto rounded-md border border-line bg-surface px-4 py-2 font-mono text-sm"> | |
| 97 | + | git clone {cloneUrl} | |
| 98 | + | </pre> | |
| 99 | + | )} | |
| 100 | + | ||
| 101 | + | {readme?.text != null && ( | |
| 102 | + | <section className="mt-6 rounded-md border border-line"> | |
| 103 | + | <h2 className="border-b border-line bg-surface px-4 py-2 font-mono text-sm text-muted"> | |
| 104 | + | {readme.name} | |
| 105 | + | </h2> | |
| 106 | + | <pre className="whitespace-pre-wrap p-4 text-sm">{readme.text}</pre> | |
| 107 | + | </section> | |
| 108 | + | )} | |
| 109 | + | </div> | |
| 110 | + | ); | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | export function BlobView({ blob }: { blob: Blob }) { | |
| 114 | + | const { repo, ref, path, size, text } = blob; | |
| 115 | + | const lines = text?.replace(/\n$/, "").split("\n"); | |
| 116 | + | return ( | |
| 117 | + | <div className="mt-6"> | |
| 118 | + | <Breadcrumbs | |
| 119 | + | base={`/${repo.namespace}/${repo.name}`} | |
| 120 | + | repo={repo.name} | |
| 121 | + | gitRef={ref} | |
| 122 | + | path={path} | |
| 123 | + | /> | |
| 124 | + | <div className="overflow-hidden rounded-md border border-line"> | |
| 125 | + | <div className="border-b border-line bg-surface px-4 py-2 font-mono text-sm text-muted"> | |
| 126 | + | {size.toLocaleString("en-US")} bytes | |
| 127 | + | </div> | |
| 128 | + | {lines ? ( | |
| 129 | + | <div className="flex overflow-x-auto font-mono text-sm leading-6"> | |
| 130 | + | <pre | |
| 131 | + | aria-hidden="true" | |
| 132 | + | className="select-none px-4 py-3 text-right text-muted" | |
| 133 | + | > | |
| 134 | + | {lines.map((_, i) => i + 1).join("\n")} | |
| 135 | + | </pre> | |
| 136 | + | <pre className="py-3 pr-4">{lines.join("\n")}</pre> | |
| 137 | + | </div> | |
| 138 | + | ) : ( | |
| 139 | + | <p className="p-4 text-sm text-muted"> | |
| 140 | + | Binary or large file not shown. | |
| 141 | + | </p> | |
| 142 | + | )} | |
| 143 | + | </div> | |
| 144 | + | </div> | |
| 145 | + | ); | |
| 146 | + | } |
| 1 | + | import type { ComponentProps, ReactNode } from "react"; | |
| 2 | + | ||
| 3 | + | export function Field({ | |
| 4 | + | label, | |
| 5 | + | hint, | |
| 6 | + | children, | |
| 7 | + | }: { | |
| 8 | + | label: string; | |
| 9 | + | hint?: string; | |
| 10 | + | children: ReactNode; | |
| 11 | + | }) { | |
| 12 | + | return ( | |
| 13 | + | <label className="block"> | |
| 14 | + | <span className="mb-1.5 block text-sm font-medium text-muted"> | |
| 15 | + | {label} | |
| 16 | + | </span> | |
| 17 | + | {children} | |
| 18 | + | {hint && <span className="mt-1.5 block text-xs text-muted">{hint}</span>} | |
| 19 | + | </label> | |
| 20 | + | ); | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | const CONTROL = | |
| 24 | + | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none focus:border-muted"; | |
| 25 | + | ||
| 26 | + | export function Input(props: ComponentProps<"input">) { | |
| 27 | + | return <input {...props} className={CONTROL} />; | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | export function Textarea(props: ComponentProps<"textarea">) { | |
| 31 | + | return <textarea {...props} className={`${CONTROL} font-mono`} />; | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | export function Button({ | |
| 35 | + | variant = "primary", | |
| 36 | + | ...props | |
| 37 | + | }: ComponentProps<"button"> & { variant?: "primary" | "quiet" }) { | |
| 38 | + | const styles = | |
| 39 | + | variant === "primary" | |
| 40 | + | ? "bg-fg text-bg hover:bg-white" | |
| 41 | + | : "border border-line text-muted hover:text-fg"; | |
| 42 | + | return ( | |
| 43 | + | <button | |
| 44 | + | {...props} | |
| 45 | + | className={`rounded-md px-3 py-2 text-sm font-medium ${styles}`} | |
| 46 | + | /> | |
| 47 | + | ); | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | export function ErrorText({ children }: { children: ReactNode }) { | |
| 51 | + | return children ? <p className="text-sm text-red-400">{children}</p> : null; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | const STATUS_STYLES: Record<string, string> = { | |
| 55 | + | open: "border-accent/40 text-accent", | |
| 56 | + | working: "border-accent/40 text-accent", | |
| 57 | + | submitted: "border-sky-400/40 text-sky-300", | |
| 58 | + | shipped: "border-violet-400/40 text-violet-300", | |
| 59 | + | }; | |
| 60 | + | ||
| 61 | + | /** A status pill; unknown statuses render muted. */ | |
| 62 | + | export function Status({ value }: { value: string }) { | |
| 63 | + | const style = STATUS_STYLES[value] ?? "border-line text-muted"; | |
| 64 | + | return ( | |
| 65 | + | <span | |
| 66 | + | className={`inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs ${style}`} | |
| 67 | + | > | |
| 68 | + | {value === "working" && ( | |
| 69 | + | <span className="size-1.5 animate-pulse rounded-full bg-accent" /> | |
| 70 | + | )} | |
| 71 | + | {value} | |
| 72 | + | </span> | |
| 73 | + | ); | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | const UNITS: [string, number][] = [ | |
| 77 | + | ["d", 86_400_000], | |
| 78 | + | ["h", 3_600_000], | |
| 79 | + | ["m", 60_000], | |
| 80 | + | ]; | |
| 81 | + | ||
| 82 | + | /** Compact relative time such as "5m ago". */ | |
| 83 | + | export function TimeAgo({ at }: { at: number }) { | |
| 84 | + | const elapsed = Date.now() - at; | |
| 85 | + | const unit = UNITS.find(([, size]) => elapsed >= size); | |
| 86 | + | const label = unit ? `${Math.floor(elapsed / unit[1])}${unit[0]} ago` : "just now"; | |
| 87 | + | return ( | |
| 88 | + | // The server and the browser render at slightly different times. | |
| 89 | + | <time dateTime={new Date(at).toISOString()} suppressHydrationWarning> | |
| 90 | + | {label} | |
| 91 | + | </time> | |
| 92 | + | ); | |
| 93 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { | |
| 3 | + | type MiddlewareFunction, | |
| 4 | + | type RouterContextProvider, | |
| 5 | + | createContext, | |
| 6 | + | data, | |
| 7 | + | redirect, | |
| 8 | + | } from "react-router"; | |
| 9 | + | ||
| 10 | + | import { type Result, type User, type Viewer, httpStatus } from "@g1t/contracts"; | |
| 11 | + | ||
| 12 | + | const SESSION_COOKIE = "g1t_session"; | |
| 13 | + | const SESSION_TTL_SECONDS = 30 * 24 * 60 * 60; | |
| 14 | + | ||
| 15 | + | const viewerContext = createContext<Viewer>(null); | |
| 16 | + | ||
| 17 | + | function sessionToken(request: Request): string | null { | |
| 18 | + | const cookies = request.headers.get("cookie") ?? ""; | |
| 19 | + | const match = new RegExp(`(?:^|; )${SESSION_COOKIE}=([0-9a-f]{64})`).exec(cookies); | |
| 20 | + | return match ? match[1] : null; | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | function sessionCookie(value: string, maxAge: number): string { | |
| 24 | + | return `${SESSION_COOKIE}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`; | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | /** Root middleware: resolves the signed-in user once per request. */ | |
| 28 | + | export const viewerMiddleware: MiddlewareFunction<Response> = async ({ | |
| 29 | + | request, | |
| 30 | + | context, | |
| 31 | + | }) => { | |
| 32 | + | const token = sessionToken(request); | |
| 33 | + | if (token) { | |
| 34 | + | context.set(viewerContext, await env.IDENTITY.userForSession(token)); | |
| 35 | + | } | |
| 36 | + | }; | |
| 37 | + | ||
| 38 | + | type Context = Readonly<RouterContextProvider>; | |
| 39 | + | ||
| 40 | + | export function getViewer(context: Context): Viewer { | |
| 41 | + | return context.get(viewerContext); | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | export function requireUser(context: Context, request: Request): User { | |
| 45 | + | const viewer = getViewer(context); | |
| 46 | + | if (!viewer) { | |
| 47 | + | const next = new URL(request.url).pathname; | |
| 48 | + | throw redirect(`/login?next=${encodeURIComponent(next)}`); | |
| 49 | + | } | |
| 50 | + | return viewer; | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | /** `Set-Cookie` value that starts a session. */ | |
| 54 | + | export function startSession(token: string): string { | |
| 55 | + | return sessionCookie(token, SESSION_TTL_SECONDS); | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | /** Ends the session and returns the `Set-Cookie` value that clears it. */ | |
| 59 | + | export async function endSession(request: Request): Promise<string> { | |
| 60 | + | const token = sessionToken(request); | |
| 61 | + | if (token) await env.IDENTITY.signOut(token); | |
| 62 | + | return sessionCookie("", 0); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | /** Rejects cross-site form posts; call at the top of every action. */ | |
| 66 | + | export function assertSameOrigin(request: Request): void { | |
| 67 | + | const origin = request.headers.get("origin"); | |
| 68 | + | if (origin && origin !== new URL(request.url).origin) { | |
| 69 | + | throw new Response("Cross-origin request rejected", { status: 403 }); | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | /** The value of a service result, or the matching HTTP error. */ | |
| 74 | + | export function unwrap<T>(result: Result<T>): T { | |
| 75 | + | if (result.ok) return result.value; | |
| 76 | + | throw data(result.error.message, { status: httpStatus(result.error) }); | |
| 77 | + | } |
| 1 | + | import { | |
| 2 | + | Form, | |
| 3 | + | isRouteErrorResponse, | |
| 4 | + | Link, | |
| 5 | + | Links, | |
| 6 | + | Meta, | |
| 7 | + | Outlet, | |
| 8 | + | Scripts, | |
| 9 | + | ScrollRestoration, | |
| 10 | + | useRouteLoaderData, | |
| 11 | + | } from "react-router"; | |
| 12 | + | ||
| 13 | + | import type { Route } from "./+types/root"; | |
| 14 | + | import "./app.css"; | |
| 15 | + | import { Logo } from "./components/logo"; | |
| 16 | + | import { getViewer, viewerMiddleware } from "./lib/session.server"; | |
| 17 | + | ||
| 18 | + | export const links: Route.LinksFunction = () => [ | |
| 19 | + | { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" }, | |
| 20 | + | { rel: "preconnect", href: "https://fonts.googleapis.com" }, | |
| 21 | + | { | |
| 22 | + | rel: "preconnect", | |
| 23 | + | href: "https://fonts.gstatic.com", | |
| 24 | + | crossOrigin: "anonymous", | |
| 25 | + | }, | |
| 26 | + | { | |
| 27 | + | rel: "stylesheet", | |
| 28 | + | href: "https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400..700&family=JetBrains+Mono:wght@400;600&display=swap", | |
| 29 | + | }, | |
| 30 | + | ]; | |
| 31 | + | ||
| 32 | + | export const middleware: Route.MiddlewareFunction[] = [viewerMiddleware]; | |
| 33 | + | ||
| 34 | + | export function loader({ context }: Route.LoaderArgs) { | |
| 35 | + | return { user: getViewer(context) }; | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | export function Layout({ children }: { children: React.ReactNode }) { | |
| 39 | + | // Undefined when the root loader itself failed. | |
| 40 | + | const user = useRouteLoaderData<typeof loader>("root")?.user; | |
| 41 | + | return ( | |
| 42 | + | <html lang="en"> | |
| 43 | + | <head> | |
| 44 | + | <meta charSet="utf-8" /> | |
| 45 | + | <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| 46 | + | <Meta /> | |
| 47 | + | <Links /> | |
| 48 | + | </head> | |
| 49 | + | <body> | |
| 50 | + | <header className="border-b border-line"> | |
| 51 | + | <nav className="mx-auto flex max-w-5xl items-center px-4 py-3"> | |
| 52 | + | <Link to="/" aria-label="g1t home"> | |
| 53 | + | <Logo /> | |
| 54 | + | </Link> | |
| 55 | + | <div className="ml-auto flex items-center gap-4 text-sm text-muted"> | |
| 56 | + | {user ? ( | |
| 57 | + | <> | |
| 58 | + | <Link to="/new" className="hover:text-fg"> | |
| 59 | + | New repository | |
| 60 | + | </Link> | |
| 61 | + | <Link to="/settings" className="font-mono hover:text-fg"> | |
| 62 | + | {user.username} | |
| 63 | + | </Link> | |
| 64 | + | <Form method="post" action="/logout"> | |
| 65 | + | <button type="submit" className="hover:text-fg"> | |
| 66 | + | Sign out | |
| 67 | + | </button> | |
| 68 | + | </Form> | |
| 69 | + | </> | |
| 70 | + | ) : ( | |
| 71 | + | <Link to="/login" className="hover:text-fg"> | |
| 72 | + | Sign in | |
| 73 | + | </Link> | |
| 74 | + | )} | |
| 75 | + | </div> | |
| 76 | + | </nav> | |
| 77 | + | </header> | |
| 78 | + | {children} | |
| 79 | + | <ScrollRestoration /> | |
| 80 | + | <Scripts /> | |
| 81 | + | </body> | |
| 82 | + | </html> | |
| 83 | + | ); | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | export default function App() { | |
| 87 | + | return <Outlet />; | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { | |
| 91 | + | let message = "Oops!"; | |
| 92 | + | let details = "An unexpected error occurred."; | |
| 93 | + | let stack: string | undefined; | |
| 94 | + | ||
| 95 | + | if (isRouteErrorResponse(error)) { | |
| 96 | + | message = error.status === 404 ? "404" : "Error"; | |
| 97 | + | details = | |
| 98 | + | error.status === 404 | |
| 99 | + | ? "The requested page could not be found." | |
| 100 | + | : error.statusText || details; | |
| 101 | + | } else if (import.meta.env.DEV && error && error instanceof Error) { | |
| 102 | + | details = error.message; | |
| 103 | + | stack = error.stack; | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | return ( | |
| 107 | + | <main className="mx-auto max-w-5xl px-4 pt-16"> | |
| 108 | + | <h1 className="font-mono text-2xl font-semibold">{message}</h1> | |
| 109 | + | <p className="mt-2 text-muted">{details}</p> | |
| 110 | + | {stack && ( | |
| 111 | + | <pre className="mt-4 w-full overflow-x-auto p-4"> | |
| 112 | + | <code>{stack}</code> | |
| 113 | + | </pre> | |
| 114 | + | )} | |
| 115 | + | </main> | |
| 116 | + | ); | |
| 117 | + | } |
| 1 | + | import { type RouteConfig, index, route } from "@react-router/dev/routes"; | |
| 2 | + | ||
| 3 | + | export default [ | |
| 4 | + | index("routes/home.tsx"), | |
| 5 | + | route("login", "routes/login.tsx"), | |
| 6 | + | route("logout", "routes/logout.tsx"), | |
| 7 | + | route("new", "routes/new.tsx"), | |
| 8 | + | route("settings", "routes/settings.tsx"), | |
| 9 | + | route(":owner/:repo", "routes/repo/layout.tsx", [ | |
| 10 | + | index("routes/repo/code.tsx"), | |
| 11 | + | route("tree/:ref/*", "routes/repo/tree.tsx"), | |
| 12 | + | route("blob/:ref/*", "routes/repo/blob.tsx"), | |
| 13 | + | route("intents", "routes/repo/intents.tsx"), | |
| 14 | + | route("intents/new", "routes/repo/intent-new.tsx"), | |
| 15 | + | route("intents/:number", "routes/repo/intent.tsx"), | |
| 16 | + | route("attempts/:id", "routes/repo/attempt.tsx"), | |
| 17 | + | ]), | |
| 18 | + | ] satisfies RouteConfig; |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Link } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Repo } from "@g1t/contracts"; | |
| 5 | + | ||
| 6 | + | import type { Route } from "./+types/home"; | |
| 7 | + | import { Mark } from "../components/logo"; | |
| 8 | + | import { Status, TimeAgo } from "../components/ui"; | |
| 9 | + | import { getViewer } from "../lib/session.server"; | |
| 10 | + | ||
| 11 | + | export function meta({}: Route.MetaArgs) { | |
| 12 | + | return [ | |
| 13 | + | { title: "g1t — git for many agents at once" }, | |
| 14 | + | { | |
| 15 | + | name: "description", | |
| 16 | + | content: | |
| 17 | + | "g1t is a git platform built on Cloudflare where many agents attempt the same change in parallel and the best one ships.", | |
| 18 | + | }, | |
| 19 | + | ]; | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | export async function loader({ context }: Route.LoaderArgs) { | |
| 23 | + | const viewer = getViewer(context); | |
| 24 | + | const [repos, attempts] = await Promise.all([ | |
| 25 | + | env.REPOS.list(viewer), | |
| 26 | + | env.WORK.listActiveAttempts(viewer), | |
| 27 | + | ]); | |
| 28 | + | // Mission control links to each attempt under its repo. | |
| 29 | + | const attemptRepos = await Promise.all( | |
| 30 | + | attempts.map(({ attempt }) => env.REPOS.getById(attempt.repoId, viewer)), | |
| 31 | + | ); | |
| 32 | + | return { | |
| 33 | + | viewer, | |
| 34 | + | repos, | |
| 35 | + | active: attempts.flatMap((item, i) => { | |
| 36 | + | const repo = attemptRepos[i]; | |
| 37 | + | return repo.ok ? [{ ...item, repo: repo.value }] : []; | |
| 38 | + | }), | |
| 39 | + | }; | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | function RepoList({ repos }: { repos: Repo[] }) { | |
| 43 | + | if (repos.length === 0) { | |
| 44 | + | return <p className="mt-4 text-muted">Nothing here yet.</p>; | |
| 45 | + | } | |
| 46 | + | return ( | |
| 47 | + | <ul className="mt-4 divide-y divide-line rounded-md border border-line"> | |
| 48 | + | {repos.map((repo) => ( | |
| 49 | + | <li key={repo.id} className="px-4 py-3"> | |
| 50 | + | <Link | |
| 51 | + | to={`/${repo.namespace}/${repo.name}`} | |
| 52 | + | className="font-mono text-sm hover:text-accent" | |
| 53 | + | > | |
| 54 | + | {repo.namespace}/{repo.name} | |
| 55 | + | </Link> | |
| 56 | + | {repo.isPrivate && ( | |
| 57 | + | <span className="ml-2 text-xs text-muted">private</span> | |
| 58 | + | )} | |
| 59 | + | {repo.description && ( | |
| 60 | + | <p className="mt-1 text-sm text-muted">{repo.description}</p> | |
| 61 | + | )} | |
| 62 | + | </li> | |
| 63 | + | ))} | |
| 64 | + | </ul> | |
| 65 | + | ); | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | export default function Home({ loaderData }: Route.ComponentProps) { | |
| 69 | + | const { viewer, repos, active } = loaderData; | |
| 70 | + | ||
| 71 | + | if (viewer) { | |
| 72 | + | return ( | |
| 73 | + | <main className="mx-auto max-w-5xl space-y-12 px-4 py-12"> | |
| 74 | + | <section> | |
| 75 | + | <h1 className="text-sm font-medium text-muted">In progress</h1> | |
| 76 | + | {active.length === 0 ? ( | |
| 77 | + | <p className="mt-4 rounded-md border border-dashed border-line p-8 text-center text-sm text-muted"> | |
| 78 | + | No attempts running. Open an intent on a repository to start one. | |
| 79 | + | </p> | |
| 80 | + | ) : ( | |
| 81 | + | <ul className="mt-4 divide-y divide-line rounded-md border border-line"> | |
| 82 | + | {active.map(({ attempt, intent, repo }) => ( | |
| 83 | + | <li key={attempt.id}> | |
| 84 | + | <Link | |
| 85 | + | to={`/${repo.namespace}/${repo.name}/attempts/${attempt.id}`} | |
| 86 | + | className="flex items-center gap-4 px-4 py-3 hover:bg-surface" | |
| 87 | + | > | |
| 88 | + | <span className="min-w-0 grow"> | |
| 89 | + | <span className="block truncate">{intent.title}</span> | |
| 90 | + | <span className="font-mono text-xs text-muted"> | |
| 91 | + | {repo.namespace}/{repo.name} #{intent.number} · attempt{" "} | |
| 92 | + | {attempt.number} · {attempt.agent} | |
| 93 | + | </span> | |
| 94 | + | </span> | |
| 95 | + | <span className="shrink-0 text-xs text-muted"> | |
| 96 | + | <TimeAgo at={attempt.updatedAt} /> | |
| 97 | + | </span> | |
| 98 | + | <Status value={attempt.status} /> | |
| 99 | + | </Link> | |
| 100 | + | </li> | |
| 101 | + | ))} | |
| 102 | + | </ul> | |
| 103 | + | )} | |
| 104 | + | </section> | |
| 105 | + | <section> | |
| 106 | + | <h2 className="text-sm font-medium text-muted">Repositories</h2> | |
| 107 | + | <RepoList repos={repos} /> | |
| 108 | + | </section> | |
| 109 | + | </main> | |
| 110 | + | ); | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | return ( | |
| 114 | + | <main className="mx-auto max-w-5xl px-4 pb-24"> | |
| 115 | + | <section className="py-24"> | |
| 116 | + | <Mark className="size-14" /> | |
| 117 | + | <h1 className="mt-8 text-4xl font-semibold tracking-tight sm:text-5xl"> | |
| 118 | + | Many attempts. <span className="text-accent">One ships.</span> | |
| 119 | + | </h1> | |
| 120 | + | <p className="mt-4 max-w-xl text-lg text-muted"> | |
| 121 | + | g1t is a git platform for the age of agents. State an intent, let | |
| 122 | + | agents attempt it in parallel, compare the results, and ship the best | |
| 123 | + | one. | |
| 124 | + | </p> | |
| 125 | + | <pre className="mt-8 inline-block rounded-md border border-line bg-surface px-4 py-3 font-mono text-sm"> | |
| 126 | + | git clone https://g1t.sh/<span className="text-muted">owner</span>/ | |
| 127 | + | <span className="text-muted">repo</span>.git | |
| 128 | + | </pre> | |
| 129 | + | </section> | |
| 130 | + | <section> | |
| 131 | + | <h2 className="text-sm font-medium text-muted">Public repositories</h2> | |
| 132 | + | <RepoList repos={repos} /> | |
| 133 | + | </section> | |
| 134 | + | </main> | |
| 135 | + | ); | |
| 136 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Form, redirect } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/login"; | |
| 5 | + | import { Button, ErrorText, Field, Input } from "../components/ui"; | |
| 6 | + | import { | |
| 7 | + | assertSameOrigin, | |
| 8 | + | getViewer, | |
| 9 | + | startSession, | |
| 10 | + | } from "../lib/session.server"; | |
| 11 | + | ||
| 12 | + | export function meta({}: Route.MetaArgs) { | |
| 13 | + | return [{ title: "Sign in · g1t" }]; | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | /** Only same-site paths, so `next` cannot redirect off g1t. */ | |
| 17 | + | function nextPath(request: Request): string { | |
| 18 | + | const next = new URL(request.url).searchParams.get("next") ?? "/"; | |
| 19 | + | return next.startsWith("/") && !next.startsWith("//") ? next : "/"; | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | export function loader({ request, context }: Route.LoaderArgs) { | |
| 23 | + | if (getViewer(context)) throw redirect(nextPath(request)); | |
| 24 | + | return null; | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | export async function action({ request }: Route.ActionArgs) { | |
| 28 | + | assertSameOrigin(request); | |
| 29 | + | const form = await request.formData(); | |
| 30 | + | const username = String(form.get("username") ?? "").trim(); | |
| 31 | + | const password = String(form.get("password") ?? ""); | |
| 32 | + | const result = await env.IDENTITY.signIn(username, password); | |
| 33 | + | if (!result.ok) return { error: result.error.message }; | |
| 34 | + | throw redirect(nextPath(request), { | |
| 35 | + | headers: { "set-cookie": startSession(result.value.sessionToken) }, | |
| 36 | + | }); | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | export default function Login({ actionData }: Route.ComponentProps) { | |
| 40 | + | return ( | |
| 41 | + | <main className="mx-auto max-w-sm px-4 pt-32"> | |
| 42 | + | <h1 className="text-xl font-semibold">Welcome to g1t</h1> | |
| 43 | + | <p className="text-xl text-muted">Many attempts. One ships.</p> | |
| 44 | + | <Form method="post" className="mt-10 space-y-4"> | |
| 45 | + | <Field label="Username"> | |
| 46 | + | <Input name="username" autoComplete="username" required autoFocus /> | |
| 47 | + | </Field> | |
| 48 | + | <Field label="Password"> | |
| 49 | + | <Input | |
| 50 | + | name="password" | |
| 51 | + | type="password" | |
| 52 | + | autoComplete="current-password" | |
| 53 | + | required | |
| 54 | + | /> | |
| 55 | + | </Field> | |
| 56 | + | <ErrorText>{actionData?.error}</ErrorText> | |
| 57 | + | <div className="pt-2 *:w-full"> | |
| 58 | + | <Button type="submit">Sign in</Button> | |
| 59 | + | </div> | |
| 60 | + | </Form> | |
| 61 | + | </main> | |
| 62 | + | ); | |
| 63 | + | } |
| 1 | + | import { redirect } from "react-router"; | |
| 2 | + | ||
| 3 | + | import type { Route } from "./+types/logout"; | |
| 4 | + | import { assertSameOrigin, endSession } from "../lib/session.server"; | |
| 5 | + | ||
| 6 | + | export async function action({ request }: Route.ActionArgs) { | |
| 7 | + | assertSameOrigin(request); | |
| 8 | + | throw redirect("/", { | |
| 9 | + | headers: { "set-cookie": await endSession(request) }, | |
| 10 | + | }); | |
| 11 | + | } | |
| 12 | + | ||
| 13 | + | export function loader() { | |
| 14 | + | throw redirect("/"); | |
| 15 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Form, redirect } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/new"; | |
| 5 | + | import { Button, ErrorText, Field, Input } from "../components/ui"; | |
| 6 | + | import { assertSameOrigin, requireUser } from "../lib/session.server"; | |
| 7 | + | ||
| 8 | + | export function meta({}: Route.MetaArgs) { | |
| 9 | + | return [{ title: "New repository · g1t" }]; | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | export function loader({ request, context }: Route.LoaderArgs) { | |
| 13 | + | return { user: requireUser(context, request) }; | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | export async function action({ request, context }: Route.ActionArgs) { | |
| 17 | + | assertSameOrigin(request); | |
| 18 | + | const user = requireUser(context, request); | |
| 19 | + | const form = await request.formData(); | |
| 20 | + | const result = await env.REPOS.create(user, { | |
| 21 | + | name: String(form.get("name") ?? ""), | |
| 22 | + | description: String(form.get("description") ?? ""), | |
| 23 | + | isPrivate: form.get("visibility") === "private", | |
| 24 | + | }); | |
| 25 | + | if (!result.ok) return { error: result.error.message }; | |
| 26 | + | throw redirect(`/${result.value.namespace}/${result.value.name}`); | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | export default function NewRepo({ | |
| 30 | + | loaderData, | |
| 31 | + | actionData, | |
| 32 | + | }: Route.ComponentProps) { | |
| 33 | + | return ( | |
| 34 | + | <main className="mx-auto max-w-lg px-4 py-12"> | |
| 35 | + | <h1 className="text-xl font-semibold">New repository</h1> | |
| 36 | + | <Form method="post" className="mt-8 space-y-4"> | |
| 37 | + | <Field label="Name"> | |
| 38 | + | <div className="flex items-center gap-2 font-mono text-sm"> | |
| 39 | + | <span className="text-muted">{loaderData.user.username}/</span> | |
| 40 | + | <Input name="name" required autoFocus maxLength={100} /> | |
| 41 | + | </div> | |
| 42 | + | </Field> | |
| 43 | + | <Field label="Description (optional)"> | |
| 44 | + | <Input name="description" maxLength={200} /> | |
| 45 | + | </Field> | |
| 46 | + | <fieldset className="space-y-2 text-sm"> | |
| 47 | + | <label className="flex items-center gap-2"> | |
| 48 | + | <input type="radio" name="visibility" value="public" defaultChecked /> | |
| 49 | + | Public — anyone can see and clone it | |
| 50 | + | </label> | |
| 51 | + | <label className="flex items-center gap-2"> | |
| 52 | + | <input type="radio" name="visibility" value="private" /> | |
| 53 | + | Private — only you | |
| 54 | + | </label> | |
| 55 | + | </fieldset> | |
| 56 | + | <ErrorText>{actionData?.error}</ErrorText> | |
| 57 | + | <Button type="submit">Create repository</Button> | |
| 58 | + | </Form> | |
| 59 | + | </main> | |
| 60 | + | ); | |
| 61 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { useEffect } from "react"; | |
| 3 | + | import { Form, Link, useRevalidator } from "react-router"; | |
| 4 | + | ||
| 5 | + | import type { SessionEntry } from "@g1t/contracts"; | |
| 6 | + | ||
| 7 | + | import type { Route } from "./+types/attempt"; | |
| 8 | + | import { Button, ErrorText, Status, Textarea, TimeAgo } from "../../components/ui"; | |
| 9 | + | import { | |
| 10 | + | assertSameOrigin, | |
| 11 | + | getViewer, | |
| 12 | + | requireUser, | |
| 13 | + | unwrap, | |
| 14 | + | } from "../../lib/session.server"; | |
| 15 | + | ||
| 16 | + | const REFRESH_MS = 4000; | |
| 17 | + | ||
| 18 | + | export function meta({ loaderData: data }: Route.MetaArgs) { | |
| 19 | + | return [{ title: data ? `Attempt ${data.attempt.number} · ${data.intent.title} · g1t` : "g1t" }]; | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 23 | + | const viewer = getViewer(context); | |
| 24 | + | const [found, session] = await Promise.all([ | |
| 25 | + | env.WORK.getAttempt(params.id, viewer), | |
| 26 | + | env.WORK.readSession(params.id, viewer), | |
| 27 | + | ]); | |
| 28 | + | return { ...unwrap(found), session: unwrap(session), viewer }; | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | export async function action({ request, params, context }: Route.ActionArgs) { | |
| 32 | + | assertSameOrigin(request); | |
| 33 | + | const user = requireUser(context, request); | |
| 34 | + | const form = await request.formData(); | |
| 35 | + | const result = | |
| 36 | + | form.get("action") === "abandon" | |
| 37 | + | ? await env.WORK.abandonAttempt(user, params.id) | |
| 38 | + | : await env.WORK.submitAttempt( | |
| 39 | + | user, | |
| 40 | + | params.id, | |
| 41 | + | String(form.get("summary") ?? ""), | |
| 42 | + | ); | |
| 43 | + | return result.ok ? null : { error: result.error.message }; | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | const KIND_LABEL: Record<SessionEntry["kind"], string> = { | |
| 47 | + | prompt: "Prompt", | |
| 48 | + | message: "Agent", | |
| 49 | + | tool_call: "Tool", | |
| 50 | + | tool_result: "Result", | |
| 51 | + | note: "Note", | |
| 52 | + | }; | |
| 53 | + | ||
| 54 | + | function Entry({ entry }: { entry: SessionEntry }) { | |
| 55 | + | const isTool = entry.kind === "tool_call" || entry.kind === "tool_result"; | |
| 56 | + | return ( | |
| 57 | + | <li className="grid grid-cols-[4.5rem_1fr] gap-3 px-4 py-3"> | |
| 58 | + | <span className="pt-0.5 text-xs text-muted"> | |
| 59 | + | {KIND_LABEL[entry.kind]} | |
| 60 | + | </span> | |
| 61 | + | <div className="min-w-0"> | |
| 62 | + | {entry.tool && ( | |
| 63 | + | <p className="font-mono text-xs text-accent">{entry.tool}</p> | |
| 64 | + | )} | |
| 65 | + | <p | |
| 66 | + | className={`whitespace-pre-wrap break-words text-sm ${ | |
| 67 | + | isTool ? "font-mono text-muted" : "" | |
| 68 | + | } ${entry.kind === "prompt" ? "font-medium" : ""}`} | |
| 69 | + | > | |
| 70 | + | {entry.text} | |
| 71 | + | </p> | |
| 72 | + | {entry.commit && ( | |
| 73 | + | <p className="mt-1 font-mono text-xs text-muted"> | |
| 74 | + | at {entry.commit.slice(0, 7)} | |
| 75 | + | </p> | |
| 76 | + | )} | |
| 77 | + | </div> | |
| 78 | + | </li> | |
| 79 | + | ); | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | export default function AttemptPage({ | |
| 83 | + | loaderData, | |
| 84 | + | actionData, | |
| 85 | + | params, | |
| 86 | + | }: Route.ComponentProps) { | |
| 87 | + | const { attempt, intent, session, viewer } = loaderData; | |
| 88 | + | const base = `/${params.owner}/${params.repo}`; | |
| 89 | + | const remote = `https://g1t.sh/${attempt.fork.namespace}/${attempt.fork.name}.git`; | |
| 90 | + | const mine = viewer?.id === attempt.startedBy.id; | |
| 91 | + | const active = attempt.status === "working" || attempt.status === "submitted"; | |
| 92 | + | ||
| 93 | + | // Follow a running attempt without a manual reload. | |
| 94 | + | const revalidator = useRevalidator(); | |
| 95 | + | const working = attempt.status === "working"; | |
| 96 | + | useEffect(() => { | |
| 97 | + | if (!working) return; | |
| 98 | + | const timer = setInterval(() => { | |
| 99 | + | if (document.visibilityState === "visible") revalidator.revalidate(); | |
| 100 | + | }, REFRESH_MS); | |
| 101 | + | return () => clearInterval(timer); | |
| 102 | + | }, [working, revalidator]); | |
| 103 | + | ||
| 104 | + | return ( | |
| 105 | + | <div className="mt-6 grid gap-8 lg:grid-cols-[1fr_20rem]"> | |
| 106 | + | <div className="min-w-0"> | |
| 107 | + | <p className="text-sm text-muted"> | |
| 108 | + | <Link to={`${base}/intents/${intent.number}`} className="hover:text-fg"> | |
| 109 | + | {intent.title} #{intent.number} | |
| 110 | + | </Link> | |
| 111 | + | </p> | |
| 112 | + | <div className="mt-1 flex items-center gap-3"> | |
| 113 | + | <h2 className="text-2xl font-semibold tracking-tight"> | |
| 114 | + | Attempt {attempt.number} | |
| 115 | + | </h2> | |
| 116 | + | <span className="font-mono text-sm text-muted">{attempt.agent}</span> | |
| 117 | + | <Status value={attempt.status} /> | |
| 118 | + | </div> | |
| 119 | + | <p className="mt-1 text-sm text-muted"> | |
| 120 | + | Started by {attempt.startedBy.username}{" "} | |
| 121 | + | <TimeAgo at={attempt.createdAt} /> · head{" "} | |
| 122 | + | <span className="font-mono"> | |
| 123 | + | {attempt.headCommit?.slice(0, 7) ?? "none yet"} | |
| 124 | + | </span> | |
| 125 | + | </p> | |
| 126 | + | ||
| 127 | + | {attempt.summary && ( | |
| 128 | + | <p className="mt-6 whitespace-pre-wrap rounded-md border border-line bg-surface p-4 text-sm leading-relaxed"> | |
| 129 | + | {attempt.summary} | |
| 130 | + | </p> | |
| 131 | + | )} | |
| 132 | + | ||
| 133 | + | <h3 className="mt-10 text-sm font-medium text-muted">Session</h3> | |
| 134 | + | {session.length === 0 ? ( | |
| 135 | + | <p className="mt-3 rounded-md border border-dashed border-line p-6 text-center text-sm text-muted"> | |
| 136 | + | Nothing recorded yet. The agent's prompts, messages and tool calls | |
| 137 | + | appear here as it works. | |
| 138 | + | </p> | |
| 139 | + | ) : ( | |
| 140 | + | <ol className="mt-3 divide-y divide-line rounded-md border border-line"> | |
| 141 | + | {session.map((entry) => ( | |
| 142 | + | <Entry key={entry.seq} entry={entry} /> | |
| 143 | + | ))} | |
| 144 | + | </ol> | |
| 145 | + | )} | |
| 146 | + | </div> | |
| 147 | + | ||
| 148 | + | <aside className="space-y-6"> | |
| 149 | + | <section> | |
| 150 | + | <h3 className="text-sm font-medium text-muted">Working copy</h3> | |
| 151 | + | <pre className="mt-2 overflow-x-auto rounded-md border border-line bg-surface p-3 font-mono text-xs"> | |
| 152 | + | git clone {remote} | |
| 153 | + | </pre> | |
| 154 | + | <p className="mt-2 text-xs text-muted"> | |
| 155 | + | This fork belongs to the attempt. Pushes to it show up here. | |
| 156 | + | </p> | |
| 157 | + | </section> | |
| 158 | + | {mine && active && ( | |
| 159 | + | <section> | |
| 160 | + | <h3 className="text-sm font-medium text-muted"> | |
| 161 | + | {attempt.status === "submitted" ? "Update summary" : "Submit"} | |
| 162 | + | </h3> | |
| 163 | + | <Form method="post" className="mt-2 space-y-2"> | |
| 164 | + | <Textarea | |
| 165 | + | name="summary" | |
| 166 | + | rows={4} | |
| 167 | + | placeholder="What changed and why" | |
| 168 | + | defaultValue={attempt.summary ?? ""} | |
| 169 | + | /> | |
| 170 | + | <div className="flex gap-2"> | |
| 171 | + | <Button type="submit">Submit attempt</Button> | |
| 172 | + | <Button variant="quiet" type="submit" name="action" value="abandon"> | |
| 173 | + | Abandon | |
| 174 | + | </Button> | |
| 175 | + | </div> | |
| 176 | + | </Form> | |
| 177 | + | <ErrorText>{actionData?.error}</ErrorText> | |
| 178 | + | </section> | |
| 179 | + | )} | |
| 180 | + | </aside> | |
| 181 | + | </div> | |
| 182 | + | ); | |
| 183 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import type { Route } from "./+types/blob"; | |
| 4 | + | import { BlobView } from "../../components/repo-view"; | |
| 5 | + | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 6 | + | ||
| 7 | + | export function meta({ params }: Route.MetaArgs) { | |
| 8 | + | return [{ title: `${params["*"]} · ${params.owner}/${params.repo} · g1t` }]; | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 12 | + | const path = { namespace: params.owner, name: params.repo }; | |
| 13 | + | return unwrap( | |
| 14 | + | await env.REPOS.blob(path, getViewer(context), params.ref, params["*"] ?? ""), | |
| 15 | + | ); | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | export default function Blob({ loaderData }: Route.ComponentProps) { | |
| 19 | + | return <BlobView blob={loaderData} />; | |
| 20 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import type { Route } from "./+types/code"; | |
| 4 | + | import { TreeView } from "../../components/repo-view"; | |
| 5 | + | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 6 | + | ||
| 7 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 8 | + | const path = { namespace: params.owner, name: params.repo }; | |
| 9 | + | return unwrap(await env.REPOS.tree(path, getViewer(context), null, "")); | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | export default function Code({ loaderData }: Route.ComponentProps) { | |
| 13 | + | return <TreeView tree={loaderData} />; | |
| 14 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Form, redirect } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/intent-new"; | |
| 5 | + | import { Button, ErrorText, Field, Input, Textarea } from "../../components/ui"; | |
| 6 | + | import { assertSameOrigin, requireUser } from "../../lib/session.server"; | |
| 7 | + | ||
| 8 | + | export function loader({ request, context }: Route.LoaderArgs) { | |
| 9 | + | requireUser(context, request); | |
| 10 | + | return null; | |
| 11 | + | } | |
| 12 | + | ||
| 13 | + | export async function action({ request, params, context }: Route.ActionArgs) { | |
| 14 | + | assertSameOrigin(request); | |
| 15 | + | const user = requireUser(context, request); | |
| 16 | + | const form = await request.formData(); | |
| 17 | + | const result = await env.WORK.openIntent( | |
| 18 | + | user, | |
| 19 | + | { namespace: params.owner, name: params.repo }, | |
| 20 | + | { | |
| 21 | + | title: String(form.get("title") ?? ""), | |
| 22 | + | brief: String(form.get("brief") ?? ""), | |
| 23 | + | checks: String(form.get("checks") ?? "").split("\n"), | |
| 24 | + | }, | |
| 25 | + | ); | |
| 26 | + | if (!result.ok) return { error: result.error.message }; | |
| 27 | + | throw redirect( | |
| 28 | + | `/${params.owner}/${params.repo}/intents/${result.value.number}`, | |
| 29 | + | ); | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | export default function NewIntent({ actionData }: Route.ComponentProps) { | |
| 33 | + | return ( | |
| 34 | + | <Form method="post" className="mt-6 max-w-2xl space-y-4"> | |
| 35 | + | <Field label="Goal"> | |
| 36 | + | <Input | |
| 37 | + | name="title" | |
| 38 | + | required | |
| 39 | + | autoFocus | |
| 40 | + | maxLength={200} | |
| 41 | + | placeholder="Make the parser streaming" | |
| 42 | + | /> | |
| 43 | + | </Field> | |
| 44 | + | <Field | |
| 45 | + | label="Brief" | |
| 46 | + | hint="What an agent is given to work from: the outcome you want, constraints, and anything it should know." | |
| 47 | + | > | |
| 48 | + | <Textarea name="brief" rows={8} /> | |
| 49 | + | </Field> | |
| 50 | + | <Field | |
| 51 | + | label="Acceptance checks (optional)" | |
| 52 | + | hint="One command per line. An attempt is accepted when they all pass." | |
| 53 | + | > | |
| 54 | + | <Textarea name="checks" rows={3} placeholder="cargo test" /> | |
| 55 | + | </Field> | |
| 56 | + | <ErrorText>{actionData?.error}</ErrorText> | |
| 57 | + | <Button type="submit">Open intent</Button> | |
| 58 | + | </Form> | |
| 59 | + | ); | |
| 60 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Form, Link, redirect } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/intent"; | |
| 5 | + | import { Button, ErrorText, Input, Status, TimeAgo } from "../../components/ui"; | |
| 6 | + | import { | |
| 7 | + | assertSameOrigin, | |
| 8 | + | getViewer, | |
| 9 | + | requireUser, | |
| 10 | + | unwrap, | |
| 11 | + | } from "../../lib/session.server"; | |
| 12 | + | ||
| 13 | + | export function meta({ loaderData: data, params }: Route.MetaArgs) { | |
| 14 | + | const title = data ? `${data.intent.title} · ` : ""; | |
| 15 | + | return [{ title: `${title}${params.owner}/${params.repo} · g1t` }]; | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 19 | + | const viewer = getViewer(context); | |
| 20 | + | const detail = unwrap( | |
| 21 | + | await env.WORK.getIntent( | |
| 22 | + | { namespace: params.owner, name: params.repo }, | |
| 23 | + | Number(params.number), | |
| 24 | + | viewer, | |
| 25 | + | ), | |
| 26 | + | ); | |
| 27 | + | return { ...detail, viewer }; | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | export async function action({ request, params, context }: Route.ActionArgs) { | |
| 31 | + | assertSameOrigin(request); | |
| 32 | + | const user = requireUser(context, request); | |
| 33 | + | const form = await request.formData(); | |
| 34 | + | const intentId = String(form.get("intentId") ?? ""); | |
| 35 | + | const base = `/${params.owner}/${params.repo}`; | |
| 36 | + | ||
| 37 | + | if (form.get("action") === "withdraw") { | |
| 38 | + | const result = await env.WORK.withdrawIntent(user, intentId); | |
| 39 | + | return result.ok ? null : { error: result.error.message }; | |
| 40 | + | } | |
| 41 | + | const result = await env.WORK.startAttempt(user, intentId, { | |
| 42 | + | agent: String(form.get("agent") ?? ""), | |
| 43 | + | runtime: "external", | |
| 44 | + | }); | |
| 45 | + | if (!result.ok) return { error: result.error.message }; | |
| 46 | + | throw redirect(`${base}/attempts/${result.value.id}`); | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | export default function IntentPage({ | |
| 50 | + | loaderData, | |
| 51 | + | actionData, | |
| 52 | + | params, | |
| 53 | + | }: Route.ComponentProps) { | |
| 54 | + | const { intent, attempts, viewer } = loaderData; | |
| 55 | + | const base = `/${params.owner}/${params.repo}`; | |
| 56 | + | const open = intent.status === "open"; | |
| 57 | + | return ( | |
| 58 | + | <div className="mt-6 grid gap-8 lg:grid-cols-[1fr_20rem]"> | |
| 59 | + | <div className="min-w-0"> | |
| 60 | + | <div className="flex items-start gap-3"> | |
| 61 | + | <h2 className="grow text-2xl font-semibold tracking-tight"> | |
| 62 | + | {intent.title}{" "} | |
| 63 | + | <span className="font-mono font-normal text-muted"> | |
| 64 | + | #{intent.number} | |
| 65 | + | </span> | |
| 66 | + | </h2> | |
| 67 | + | <Status value={intent.status} /> | |
| 68 | + | </div> | |
| 69 | + | <p className="mt-1 text-sm text-muted"> | |
| 70 | + | Opened by {intent.author.username} <TimeAgo at={intent.createdAt} /> | |
| 71 | + | </p> | |
| 72 | + | ||
| 73 | + | {intent.brief && ( | |
| 74 | + | <p className="mt-6 whitespace-pre-wrap leading-relaxed"> | |
| 75 | + | {intent.brief} | |
| 76 | + | </p> | |
| 77 | + | )} | |
| 78 | + | ||
| 79 | + | <h3 className="mt-10 text-sm font-medium text-muted"> | |
| 80 | + | Attempts ({attempts.length}) | |
| 81 | + | </h3> | |
| 82 | + | {attempts.length === 0 ? ( | |
| 83 | + | <p className="mt-3 rounded-md border border-dashed border-line p-6 text-center text-sm text-muted"> | |
| 84 | + | No agent has attempted this yet. | |
| 85 | + | </p> | |
| 86 | + | ) : ( | |
| 87 | + | <ul className="mt-3 space-y-2"> | |
| 88 | + | {attempts.map((attempt) => ( | |
| 89 | + | <li key={attempt.id}> | |
| 90 | + | <Link | |
| 91 | + | to={`${base}/attempts/${attempt.id}`} | |
| 92 | + | className="block rounded-md border border-line px-4 py-3 hover:border-muted" | |
| 93 | + | > | |
| 94 | + | <div className="flex items-center gap-3"> | |
| 95 | + | <span className="font-mono text-sm text-muted"> | |
| 96 | + | {attempt.number} | |
| 97 | + | </span> | |
| 98 | + | <span className="font-mono text-sm">{attempt.agent}</span> | |
| 99 | + | <Status value={attempt.status} /> | |
| 100 | + | <span className="ml-auto font-mono text-xs text-muted"> | |
| 101 | + | {attempt.headCommit?.slice(0, 7) ?? "no commits"} ·{" "} | |
| 102 | + | <TimeAgo at={attempt.updatedAt} /> | |
| 103 | + | </span> | |
| 104 | + | </div> | |
| 105 | + | {attempt.summary && ( | |
| 106 | + | <p className="mt-2 line-clamp-2 text-sm text-muted"> | |
| 107 | + | {attempt.summary} | |
| 108 | + | </p> | |
| 109 | + | )} | |
| 110 | + | </Link> | |
| 111 | + | </li> | |
| 112 | + | ))} | |
| 113 | + | </ul> | |
| 114 | + | )} | |
| 115 | + | </div> | |
| 116 | + | ||
| 117 | + | <aside className="space-y-6"> | |
| 118 | + | {intent.checks.length > 0 && ( | |
| 119 | + | <section> | |
| 120 | + | <h3 className="text-sm font-medium text-muted">Acceptance checks</h3> | |
| 121 | + | <ul className="mt-2 space-y-1 font-mono text-sm"> | |
| 122 | + | {intent.checks.map((check) => ( | |
| 123 | + | <li key={check} className="rounded bg-surface px-2 py-1"> | |
| 124 | + | {check} | |
| 125 | + | </li> | |
| 126 | + | ))} | |
| 127 | + | </ul> | |
| 128 | + | </section> | |
| 129 | + | )} | |
| 130 | + | {open && viewer && ( | |
| 131 | + | <section> | |
| 132 | + | <h3 className="text-sm font-medium text-muted">Start an attempt</h3> | |
| 133 | + | <Form method="post" className="mt-2 space-y-2"> | |
| 134 | + | <input type="hidden" name="intentId" value={intent.id} /> | |
| 135 | + | <Input name="agent" placeholder="claude-code" maxLength={60} /> | |
| 136 | + | <Button type="submit">Start attempt</Button> | |
| 137 | + | </Form> | |
| 138 | + | <p className="mt-2 text-xs text-muted"> | |
| 139 | + | Creates a fork for the agent to push to. | |
| 140 | + | </p> | |
| 141 | + | </section> | |
| 142 | + | )} | |
| 143 | + | {open && !viewer && ( | |
| 144 | + | <p className="text-sm text-muted"> | |
| 145 | + | <Link to="/login" className="text-fg underline"> | |
| 146 | + | Sign in | |
| 147 | + | </Link>{" "} | |
| 148 | + | to start an attempt. | |
| 149 | + | </p> | |
| 150 | + | )} | |
| 151 | + | {open && viewer?.id === intent.author.id && ( | |
| 152 | + | <Form method="post"> | |
| 153 | + | <input type="hidden" name="intentId" value={intent.id} /> | |
| 154 | + | <input type="hidden" name="action" value="withdraw" /> | |
| 155 | + | <Button variant="quiet" type="submit"> | |
| 156 | + | Withdraw intent | |
| 157 | + | </Button> | |
| 158 | + | </Form> | |
| 159 | + | )} | |
| 160 | + | <ErrorText>{actionData?.error}</ErrorText> | |
| 161 | + | </aside> | |
| 162 | + | </div> | |
| 163 | + | ); | |
| 164 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Link } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/intents"; | |
| 5 | + | import { Status, TimeAgo } from "../../components/ui"; | |
| 6 | + | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 7 | + | ||
| 8 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 9 | + | const path = { namespace: params.owner, name: params.repo }; | |
| 10 | + | return { | |
| 11 | + | intents: unwrap(await env.WORK.listIntents(path, getViewer(context))), | |
| 12 | + | }; | |
| 13 | + | } | |
| 14 | + | ||
| 15 | + | export default function Intents({ loaderData, params }: Route.ComponentProps) { | |
| 16 | + | const { intents } = loaderData; | |
| 17 | + | const base = `/${params.owner}/${params.repo}/intents`; | |
| 18 | + | return ( | |
| 19 | + | <div className="mt-6"> | |
| 20 | + | <div className="flex items-center justify-between"> | |
| 21 | + | <p className="text-sm text-muted"> | |
| 22 | + | An intent is a goal for this repo. Agents attempt it in parallel and | |
| 23 | + | the best attempt ships. | |
| 24 | + | </p> | |
| 25 | + | <Link | |
| 26 | + | to={`${base}/new`} | |
| 27 | + | className="shrink-0 rounded-md bg-fg px-3 py-2 text-sm font-medium text-bg hover:bg-white" | |
| 28 | + | > | |
| 29 | + | New intent | |
| 30 | + | </Link> | |
| 31 | + | </div> | |
| 32 | + | {intents.length === 0 ? ( | |
| 33 | + | <p className="mt-10 text-center text-muted">No intents yet.</p> | |
| 34 | + | ) : ( | |
| 35 | + | <ul className="mt-4 divide-y divide-line rounded-md border border-line"> | |
| 36 | + | {intents.map((intent) => ( | |
| 37 | + | <li key={intent.id}> | |
| 38 | + | <Link | |
| 39 | + | to={`${base}/${intent.number}`} | |
| 40 | + | className="flex items-center gap-4 px-4 py-3 hover:bg-surface" | |
| 41 | + | > | |
| 42 | + | <span className="w-10 font-mono text-sm text-muted"> | |
| 43 | + | #{intent.number} | |
| 44 | + | </span> | |
| 45 | + | <span className="min-w-0 grow"> | |
| 46 | + | <span className="block truncate">{intent.title}</span> | |
| 47 | + | <span className="text-xs text-muted"> | |
| 48 | + | {intent.author.username} · <TimeAgo at={intent.createdAt} /> | |
| 49 | + | </span> | |
| 50 | + | </span> | |
| 51 | + | <span className="shrink-0 text-xs text-muted"> | |
| 52 | + | {intent.attemptCount}{" "} | |
| 53 | + | {intent.attemptCount === 1 ? "attempt" : "attempts"} | |
| 54 | + | </span> | |
| 55 | + | <Status value={intent.status} /> | |
| 56 | + | </Link> | |
| 57 | + | </li> | |
| 58 | + | ))} | |
| 59 | + | </ul> | |
| 60 | + | )} | |
| 61 | + | </div> | |
| 62 | + | ); | |
| 63 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Link, NavLink, Outlet } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/layout"; | |
| 5 | + | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 6 | + | ||
| 7 | + | export function meta({ params }: Route.MetaArgs) { | |
| 8 | + | return [{ title: `${params.owner}/${params.repo} · g1t` }]; | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 12 | + | const viewer = getViewer(context); | |
| 13 | + | const path = { namespace: params.owner, name: params.repo }; | |
| 14 | + | const [repo, intents] = await Promise.all([ | |
| 15 | + | env.REPOS.get(path, viewer), | |
| 16 | + | env.WORK.listIntents(path, viewer, "open"), | |
| 17 | + | ]); | |
| 18 | + | return { | |
| 19 | + | repo: unwrap(repo), | |
| 20 | + | openIntents: intents.ok ? intents.value.length : 0, | |
| 21 | + | }; | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | function Tab({ to, end, children }: { to: string; end?: boolean; children: React.ReactNode }) { | |
| 25 | + | return ( | |
| 26 | + | <NavLink | |
| 27 | + | to={to} | |
| 28 | + | end={end} | |
| 29 | + | className={({ isActive }) => | |
| 30 | + | `-mb-px border-b-2 px-1 pb-2.5 text-sm ${ | |
| 31 | + | isActive | |
| 32 | + | ? "border-accent text-fg" | |
| 33 | + | : "border-transparent text-muted hover:text-fg" | |
| 34 | + | }` | |
| 35 | + | } | |
| 36 | + | > | |
| 37 | + | {children} | |
| 38 | + | </NavLink> | |
| 39 | + | ); | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | export default function RepoLayout({ loaderData }: Route.ComponentProps) { | |
| 43 | + | const { repo, openIntents } = loaderData; | |
| 44 | + | const base = `/${repo.namespace}/${repo.name}`; | |
| 45 | + | return ( | |
| 46 | + | <div className="mx-auto max-w-5xl px-4 py-8"> | |
| 47 | + | <div className="flex items-center gap-3"> | |
| 48 | + | <h1 className="font-mono text-xl"> | |
| 49 | + | <span className="text-muted">{repo.namespace}/</span> | |
| 50 | + | <Link to={base} className="font-semibold"> | |
| 51 | + | {repo.name} | |
| 52 | + | </Link> | |
| 53 | + | </h1> | |
| 54 | + | {repo.isPrivate && ( | |
| 55 | + | <span className="rounded-full border border-line px-2 py-0.5 text-xs text-muted"> | |
| 56 | + | private | |
| 57 | + | </span> | |
| 58 | + | )} | |
| 59 | + | </div> | |
| 60 | + | {repo.description && ( | |
| 61 | + | <p className="mt-2 text-sm text-muted">{repo.description}</p> | |
| 62 | + | )} | |
| 63 | + | <nav className="mt-6 flex gap-6 border-b border-line"> | |
| 64 | + | <Tab to={base} end> | |
| 65 | + | Code | |
| 66 | + | </Tab> | |
| 67 | + | <Tab to={`${base}/intents`}> | |
| 68 | + | Intents | |
| 69 | + | {openIntents > 0 && ( | |
| 70 | + | <span className="ml-1.5 rounded-full bg-surface px-1.5 py-0.5 text-xs"> | |
| 71 | + | {openIntents} | |
| 72 | + | </span> | |
| 73 | + | )} | |
| 74 | + | </Tab> | |
| 75 | + | </nav> | |
| 76 | + | <Outlet /> | |
| 77 | + | </div> | |
| 78 | + | ); | |
| 79 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import type { Route } from "./+types/tree"; | |
| 4 | + | import { TreeView } from "../../components/repo-view"; | |
| 5 | + | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 6 | + | ||
| 7 | + | export function meta({ params }: Route.MetaArgs) { | |
| 8 | + | const path = params["*"] ? `${params["*"]} · ` : ""; | |
| 9 | + | return [{ title: `${path}${params.owner}/${params.repo} · g1t` }]; | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 13 | + | const path = { namespace: params.owner, name: params.repo }; | |
| 14 | + | return unwrap( | |
| 15 | + | await env.REPOS.tree(path, getViewer(context), params.ref, params["*"] ?? ""), | |
| 16 | + | ); | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | export default function Tree({ loaderData }: Route.ComponentProps) { | |
| 20 | + | return <TreeView tree={loaderData} />; | |
| 21 | + | } |
| 1 | + | import { env } from "cloudflare:workers"; | |
| 2 | + | import { Form } from "react-router"; | |
| 3 | + | ||
| 4 | + | import type { Route } from "./+types/settings"; | |
| 5 | + | import { Button, ErrorText, Field, Input } from "../components/ui"; | |
| 6 | + | import { assertSameOrigin, requireUser } from "../lib/session.server"; | |
| 7 | + | ||
| 8 | + | export function meta({}: Route.MetaArgs) { | |
| 9 | + | return [{ title: "Settings · g1t" }]; | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | export async function loader({ request, context }: Route.LoaderArgs) { | |
| 13 | + | const user = requireUser(context, request); | |
| 14 | + | const [keys, tokens] = await Promise.all([ | |
| 15 | + | env.IDENTITY.listSshKeys(user), | |
| 16 | + | env.IDENTITY.listAccessTokens(user), | |
| 17 | + | ]); | |
| 18 | + | return { user, keys, tokens }; | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | export async function action({ request, context }: Route.ActionArgs) { | |
| 22 | + | assertSameOrigin(request); | |
| 23 | + | const user = requireUser(context, request); | |
| 24 | + | const form = await request.formData(); | |
| 25 | + | const id = String(form.get("id") ?? ""); | |
| 26 | + | ||
| 27 | + | switch (form.get("intent")) { | |
| 28 | + | case "add-key": { | |
| 29 | + | const result = await env.IDENTITY.addSshKey( | |
| 30 | + | user, | |
| 31 | + | String(form.get("title") ?? ""), | |
| 32 | + | String(form.get("key") ?? ""), | |
| 33 | + | ); | |
| 34 | + | return result.ok ? null : { keyError: result.error.message }; | |
| 35 | + | } | |
| 36 | + | case "delete-key": | |
| 37 | + | await env.IDENTITY.removeSshKey(user, id); | |
| 38 | + | return null; | |
| 39 | + | case "add-token": { | |
| 40 | + | const created = await env.IDENTITY.createAccessToken( | |
| 41 | + | user, | |
| 42 | + | String(form.get("name") ?? ""), | |
| 43 | + | ); | |
| 44 | + | return { newToken: created.token }; | |
| 45 | + | } | |
| 46 | + | case "delete-token": | |
| 47 | + | await env.IDENTITY.removeAccessToken(user, id); | |
| 48 | + | return null; | |
| 49 | + | } | |
| 50 | + | return null; | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | function DeleteButton({ intent, id }: { intent: string; id: string }) { | |
| 54 | + | return ( | |
| 55 | + | <Form method="post" className="ml-auto"> | |
| 56 | + | <input type="hidden" name="intent" value={intent} /> | |
| 57 | + | <input type="hidden" name="id" value={id} /> | |
| 58 | + | <Button variant="quiet" type="submit"> | |
| 59 | + | Delete | |
| 60 | + | </Button> | |
| 61 | + | </Form> | |
| 62 | + | ); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | export default function Settings({ | |
| 66 | + | loaderData, | |
| 67 | + | actionData, | |
| 68 | + | }: Route.ComponentProps) { | |
| 69 | + | const { user, keys, tokens } = loaderData; | |
| 70 | + | return ( | |
| 71 | + | <main className="mx-auto max-w-2xl space-y-12 px-4 py-12"> | |
| 72 | + | <h1 className="text-xl font-semibold"> | |
| 73 | + | Settings <span className="font-mono text-muted">{user.username}</span> | |
| 74 | + | </h1> | |
| 75 | + | ||
| 76 | + | <section> | |
| 77 | + | <h2 className="font-medium">SSH keys</h2> | |
| 78 | + | <ul className="mt-4 divide-y divide-line rounded-md border border-line empty:hidden"> | |
| 79 | + | {keys.map((key) => ( | |
| 80 | + | <li key={key.id} className="flex items-center gap-4 px-4 py-3"> | |
| 81 | + | <div className="min-w-0"> | |
| 82 | + | <p className="text-sm">{key.title}</p> | |
| 83 | + | <p className="truncate font-mono text-xs text-muted"> | |
| 84 | + | {key.fingerprint} | |
| 85 | + | </p> | |
| 86 | + | </div> | |
| 87 | + | <DeleteButton intent="delete-key" id={key.id} /> | |
| 88 | + | </li> | |
| 89 | + | ))} | |
| 90 | + | </ul> | |
| 91 | + | <Form method="post" className="mt-4 space-y-3"> | |
| 92 | + | <input type="hidden" name="intent" value="add-key" /> | |
| 93 | + | <Field label="Title (optional)"> | |
| 94 | + | <Input name="title" maxLength={100} /> | |
| 95 | + | </Field> | |
| 96 | + | <Field label="Public key"> | |
| 97 | + | <Input name="key" placeholder="ssh-ed25519 AAAA…" required /> | |
| 98 | + | </Field> | |
| 99 | + | <ErrorText>{actionData?.keyError}</ErrorText> | |
| 100 | + | <Button type="submit">Add SSH key</Button> | |
| 101 | + | </Form> | |
| 102 | + | </section> | |
| 103 | + | ||
| 104 | + | <section> | |
| 105 | + | <h2 className="font-medium">Access tokens</h2> | |
| 106 | + | <p className="mt-1 text-sm text-muted"> | |
| 107 | + | Use a token as the password when git asks for one over HTTPS, and to | |
| 108 | + | authenticate agents and the API. | |
| 109 | + | </p> | |
| 110 | + | {actionData?.newToken && ( | |
| 111 | + | <div className="mt-4 rounded-md border border-accent/40 bg-surface p-4"> | |
| 112 | + | <p className="text-sm">Copy it now. It will not be shown again.</p> | |
| 113 | + | <pre className="mt-2 overflow-x-auto font-mono text-sm text-accent"> | |
| 114 | + | {actionData.newToken} | |
| 115 | + | </pre> | |
| 116 | + | </div> | |
| 117 | + | )} | |
| 118 | + | <ul className="mt-4 divide-y divide-line rounded-md border border-line empty:hidden"> | |
| 119 | + | {tokens.map((token) => ( | |
| 120 | + | <li key={token.id} className="flex items-center gap-4 px-4 py-3"> | |
| 121 | + | <p className="text-sm">{token.name}</p> | |
| 122 | + | <DeleteButton intent="delete-token" id={token.id} /> | |
| 123 | + | </li> | |
| 124 | + | ))} | |
| 125 | + | </ul> | |
| 126 | + | <Form method="post" className="mt-4 flex items-end gap-3"> | |
| 127 | + | <input type="hidden" name="intent" value="add-token" /> | |
| 128 | + | <div className="grow"> | |
| 129 | + | <Field label="Name"> | |
| 130 | + | <Input name="name" maxLength={100} placeholder="laptop" /> | |
| 131 | + | </Field> | |
| 132 | + | </div> | |
| 133 | + | <Button type="submit">Create token</Button> | |
| 134 | + | </Form> | |
| 135 | + | </section> | |
| 136 | + | </main> | |
| 137 | + | ); | |
| 138 | + | } |
| 1 | + | { | |
| 2 | + | "name": "@g1t/web", | |
| 3 | + | "private": true, | |
| 4 | + | "type": "module", | |
| 5 | + | "scripts": { | |
| 6 | + | "build": "react-router build", | |
| 7 | + | "dev": "react-router dev", | |
| 8 | + | "typecheck": "wrangler types --include-env=false && react-router typegen && tsc -b", | |
| 9 | + | "deploy": "npm run build && wrangler deploy", | |
| 10 | + | "preview": "npm run build && vite preview", | |
| 11 | + | "cf-typegen": "wrangler types --include-env=false", | |
| 12 | + | "postinstall": "wrangler types --include-env=false" | |
| 13 | + | }, | |
| 14 | + | "dependencies": { | |
| 15 | + | "isbot": "^5.1.36", | |
| 16 | + | "react": "^19.2.8", | |
| 17 | + | "react-dom": "^19.2.8", | |
| 18 | + | "react-router": "^8.4.0", | |
| 19 | + | "@g1t/contracts": "*" | |
| 20 | + | }, | |
| 21 | + | "devDependencies": { | |
| 22 | + | "@cloudflare/vite-plugin": "^1.62.4", | |
| 23 | + | "@react-router/dev": "^8.4.0", | |
| 24 | + | "@tailwindcss/vite": "^4.2.2", | |
| 25 | + | "@types/node": "^22.20.5", | |
| 26 | + | "@types/react": "^19.2.18", | |
| 27 | + | "@types/react-dom": "^19.2.7", | |
| 28 | + | "tailwindcss": "^4.2.2", | |
| 29 | + | "typescript": "^5.9.3", | |
| 30 | + | "vite": "^8.0.3", | |
| 31 | + | "wrangler": "^4.146.0" | |
| 32 | + | }, | |
| 33 | + | "license": "MIT" | |
| 34 | + | } |
| 1 | + | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke-width="2.5" stroke-linecap="round"> | |
| 2 | + | <rect width="32" height="32" rx="7" fill="#12110d"/> | |
| 3 | + | <path d="M4 7h6c7 0 7 9 14 9" stroke="#8f8c82"/> | |
| 4 | + | <path d="M4 25h6c7 0 7-9 14-9" stroke="#8f8c82"/> | |
| 5 | + | <path d="M4 16h20" stroke="#b6f24a"/> | |
| 6 | + | <circle cx="25" cy="16" r="3.5" fill="#b6f24a"/> | |
| 7 | + | </svg> |
| 1 | + | import type { Config } from "@react-router/dev/config"; | |
| 2 | + | ||
| 3 | + | export default { | |
| 4 | + | // Config options... | |
| 5 | + | // Server-side render by default, to enable SPA mode set this to `false` | |
| 6 | + | ssr: true, | |
| 7 | + | } satisfies Config; |
| 1 | + | { | |
| 2 | + | "extends": "./tsconfig.json", | |
| 3 | + | "include": [ | |
| 4 | + | ".react-router/types/**/*", | |
| 5 | + | "app/**/*", | |
| 6 | + | "app/**/.server/**/*", | |
| 7 | + | "app/**/.client/**/*", | |
| 8 | + | "workers/**/*", | |
| 9 | + | "worker-configuration.d.ts" | |
| 10 | + | ], | |
| 11 | + | "compilerOptions": { | |
| 12 | + | "composite": true, | |
| 13 | + | "strict": true, | |
| 14 | + | "lib": ["DOM", "DOM.Iterable", "ES2022", "ESNext.Disposable"], | |
| 15 | + | "types": ["vite/client"], | |
| 16 | + | "target": "ES2022", | |
| 17 | + | "module": "ES2022", | |
| 18 | + | "moduleResolution": "bundler", | |
| 19 | + | "jsx": "react-jsx", | |
| 20 | + | "rootDirs": [".", "./.react-router/types"], | |
| 21 | + | "paths": { | |
| 22 | + | "~/*": ["./app/*"] | |
| 23 | + | }, | |
| 24 | + | "esModuleInterop": true, | |
| 25 | + | "resolveJsonModule": true | |
| 26 | + | } | |
| 27 | + | } |
| 1 | + | { | |
| 2 | + | "files": [], | |
| 3 | + | "references": [ | |
| 4 | + | { "path": "./tsconfig.node.json" }, | |
| 5 | + | { "path": "./tsconfig.cloudflare.json" } | |
| 6 | + | ], | |
| 7 | + | "compilerOptions": { | |
| 8 | + | "checkJs": true, | |
| 9 | + | "verbatimModuleSyntax": true, | |
| 10 | + | "skipLibCheck": true, | |
| 11 | + | "strict": true, | |
| 12 | + | "noEmit": true | |
| 13 | + | } | |
| 14 | + | } |
| 1 | + | { | |
| 2 | + | "extends": "./tsconfig.json", | |
| 3 | + | "include": ["vite.config.ts"], | |
| 4 | + | "compilerOptions": { | |
| 5 | + | "composite": true, | |
| 6 | + | "strict": true, | |
| 7 | + | "types": ["node"], | |
| 8 | + | "lib": ["ES2022"], | |
| 9 | + | "target": "ES2022", | |
| 10 | + | "module": "ES2022", | |
| 11 | + | "moduleResolution": "bundler" | |
| 12 | + | } | |
| 13 | + | } |
| 1 | + | import { reactRouter } from "@react-router/dev/vite"; | |
| 2 | + | import { cloudflare } from "@cloudflare/vite-plugin"; | |
| 3 | + | import tailwindcss from "@tailwindcss/vite"; | |
| 4 | + | import { defineConfig } from "vite"; | |
| 5 | + | ||
| 6 | + | export default defineConfig({ | |
| 7 | + | plugins: [ | |
| 8 | + | cloudflare({ viteEnvironment: { name: "ssr" } }), | |
| 9 | + | tailwindcss(), | |
| 10 | + | reactRouter(), | |
| 11 | + | ], | |
| 12 | + | resolve: { | |
| 13 | + | tsconfigPaths: true, | |
| 14 | + | }, | |
| 15 | + | }); |
| 1 | + | import { createRequestHandler } from "react-router"; | |
| 2 | + | ||
| 3 | + | const requestHandler = createRequestHandler( | |
| 4 | + | () => import("virtual:react-router/server-build"), | |
| 5 | + | import.meta.env.MODE, | |
| 6 | + | ); | |
| 7 | + | ||
| 8 | + | const GIT_PATH = /\/(info\/refs|git-upload-pack|git-receive-pack)$/; | |
| 9 | + | ||
| 10 | + | export default { | |
| 11 | + | async fetch(request, env) { | |
| 12 | + | // Git over HTTPS shares this hostname but belongs to the repos service. | |
| 13 | + | if (GIT_PATH.test(new URL(request.url).pathname)) { | |
| 14 | + | return env.REPOS.fetch(request); | |
| 15 | + | } | |
| 16 | + | return requestHandler(request); | |
| 17 | + | }, | |
| 18 | + | } satisfies ExportedHandler<Env>; |
| 1 | + | import type { EventsApi, IdentityApi, ReposApi, WorkApi } from "@g1t/contracts"; | |
| 2 | + | ||
| 3 | + | declare global { | |
| 4 | + | namespace Cloudflare { | |
| 5 | + | interface Env { | |
| 6 | + | IDENTITY: IdentityApi; | |
| 7 | + | /** Also serves git over HTTPS through `fetch`. */ | |
| 8 | + | REPOS: ReposApi & { fetch(request: Request): Promise<Response> }; | |
| 9 | + | WORK: WorkApi; | |
| 10 | + | EVENTS: EventsApi; | |
| 11 | + | } | |
| 12 | + | } | |
| 13 | + | interface Env extends Cloudflare.Env {} | |
| 14 | + | } |
| 1 | + | { | |
| 2 | + | "$schema": "../../node_modules/wrangler/config-schema.json", | |
| 3 | + | "name": "g1t", | |
| 4 | + | "account_id": "1e6f2cffa3f445920836e8ebe446bb58", | |
| 5 | + | "compatibility_date": "2026-09-26", | |
| 6 | + | "main": "./workers/app.ts", | |
| 7 | + | "routes": [{ "pattern": "g1t.sh", "custom_domain": true }], | |
| 8 | + | // The site holds no data of its own; everything goes through services. | |
| 9 | + | "services": [ | |
| 10 | + | { "binding": "IDENTITY", "service": "g1t-identity" }, | |
| 11 | + | { "binding": "REPOS", "service": "g1t-repos" }, | |
| 12 | + | { "binding": "WORK", "service": "g1t-work" }, | |
| 13 | + | { "binding": "EVENTS", "service": "g1t-events" } | |
| 14 | + | ], | |
| 15 | + | "observability": { "enabled": true }, | |
| 16 | + | "upload_source_maps": true | |
| 17 | + | } |
| 1 | + | [package] | |
| 2 | + | name = "g1t" | |
| 3 | + | version = "0.1.0" | |
| 4 | + | edition.workspace = true | |
| 5 | + | license.workspace = true | |
| 6 | + | repository.workspace = true | |
| 7 | + | ||
| 8 | + | [dependencies] |
| 1 | + | fn main() { | |
| 2 | + | println!("Hello, world!"); | |
| 3 | + | } |
| 1 | + | [package] | |
| 2 | + | name = "g1t-sshd" | |
| 3 | + | version = "0.1.0" | |
| 4 | + | edition.workspace = true | |
| 5 | + | license.workspace = true | |
| 6 | + | repository.workspace = true | |
| 7 | + | ||
| 8 | + | [dependencies] | |
| 9 | + | anyhow = "1.0.104" | |
| 10 | + | flate2 = "1.1.10" | |
| 11 | + | futures-util = "0.3.34" | |
| 12 | + | reqwest = { version = "0.13.5", features = ["json", "stream"] } | |
| 13 | + | russh = "0.63.3" | |
| 14 | + | serde = { version = "1.0.229", features = ["derive"] } | |
| 15 | + | serde_json = "1.0.151" | |
| 16 | + | tokio = { version = "1.53.1", features = ["full"] } | |
| 17 | + | tokio-tungstenite = "0.30.0" |
| 1 | + | //! Client for the g1t Worker's internal endpoints, which own all | |
| 2 | + | //! authentication and authorization decisions. | |
| 3 | + | ||
| 4 | + | use anyhow::{Context, Result}; | |
| 5 | + | use serde::{Deserialize, Serialize}; | |
| 6 | + | ||
| 7 | + | #[derive(Clone, Debug, Deserialize)] | |
| 8 | + | pub struct User { | |
| 9 | + | pub id: u64, | |
| 10 | + | pub username: String, | |
| 11 | + | } | |
| 12 | + | ||
| 13 | + | /// An Artifacts remote and a short-lived token scoped to one repo. | |
| 14 | + | #[derive(Debug, Deserialize)] | |
| 15 | + | pub struct Access { | |
| 16 | + | pub remote: String, | |
| 17 | + | pub token: String, | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] | |
| 21 | + | pub enum Service { | |
| 22 | + | #[serde(rename = "git-upload-pack")] | |
| 23 | + | UploadPack, | |
| 24 | + | #[serde(rename = "git-receive-pack")] | |
| 25 | + | ReceivePack, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | impl Service { | |
| 29 | + | pub fn as_str(self) -> &'static str { | |
| 30 | + | match self { | |
| 31 | + | Service::UploadPack => "git-upload-pack", | |
| 32 | + | Service::ReceivePack => "git-receive-pack", | |
| 33 | + | } | |
| 34 | + | } | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | #[derive(Deserialize)] | |
| 38 | + | struct ErrorBody { | |
| 39 | + | error: String, | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | pub struct Api { | |
| 43 | + | base: String, | |
| 44 | + | secret: String, | |
| 45 | + | pub http: reqwest::Client, | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | impl Api { | |
| 49 | + | pub fn new(base: String, secret: String) -> Self { | |
| 50 | + | Self { | |
| 51 | + | base, | |
| 52 | + | secret, | |
| 53 | + | http: reqwest::Client::new(), | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | /// The user who registered the key with this SHA-256 fingerprint. | |
| 58 | + | pub async fn user_for_key(&self, fingerprint: &str) -> Result<Option<User>> { | |
| 59 | + | let response = self | |
| 60 | + | .http | |
| 61 | + | .post(format!("{}/_internal/ssh/user", self.base)) | |
| 62 | + | .bearer_auth(&self.secret) | |
| 63 | + | .json(&serde_json::json!({ "fingerprint": fingerprint })) | |
| 64 | + | .send() | |
| 65 | + | .await | |
| 66 | + | .context("key lookup failed")?; | |
| 67 | + | if response.status() == reqwest::StatusCode::NOT_FOUND { | |
| 68 | + | return Ok(None); | |
| 69 | + | } | |
| 70 | + | Ok(Some(response.error_for_status()?.json().await?)) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | /// `Ok(Err(message))` is a refusal to show the user. | |
| 74 | + | pub async fn access( | |
| 75 | + | &self, | |
| 76 | + | user: &User, | |
| 77 | + | owner: &str, | |
| 78 | + | repo: &str, | |
| 79 | + | service: Service, | |
| 80 | + | ) -> Result<Result<Access, String>> { | |
| 81 | + | let response = self | |
| 82 | + | .http | |
| 83 | + | .post(format!("{}/_internal/ssh/access", self.base)) | |
| 84 | + | .bearer_auth(&self.secret) | |
| 85 | + | .json(&serde_json::json!({ | |
| 86 | + | "user_id": user.id, | |
| 87 | + | "owner": owner, | |
| 88 | + | "repo": repo, | |
| 89 | + | "service": service, | |
| 90 | + | })) | |
| 91 | + | .send() | |
| 92 | + | .await | |
| 93 | + | .context("access check failed")?; | |
| 94 | + | if response.status().is_client_error() { | |
| 95 | + | let body: ErrorBody = response.json().await?; | |
| 96 | + | return Ok(Err(body.error)); | |
| 97 | + | } | |
| 98 | + | Ok(Ok(response.error_for_status()?.json().await?)) | |
| 99 | + | } | |
| 100 | + | } |
| 1 | + | //! Bridges git's stateful SSH transport to the stateless smart HTTP protocol | |
| 2 | + | //! spoken by Artifacts. | |
| 3 | + | ||
| 4 | + | use anyhow::{Result, bail, ensure}; | |
| 5 | + | use flate2::{Decompress, FlushDecompress, Status}; | |
| 6 | + | use futures_util::StreamExt; | |
| 7 | + | use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt}; | |
| 8 | + | ||
| 9 | + | use crate::api::{Access, Service}; | |
| 10 | + | ||
| 11 | + | const FLUSH: &[u8] = b"0000"; | |
| 12 | + | const ZERO_ID: &str = "0000000000000000000000000000000000000000"; | |
| 13 | + | ||
| 14 | + | /// A pkt-line carrying `ERR`, which git prints as `fatal: remote error: …`. | |
| 15 | + | pub fn error_pkt(message: &str) -> Vec<u8> { | |
| 16 | + | let payload = format!("ERR {message}\n"); | |
| 17 | + | format!("{:04x}{payload}", payload.len() + 4).into_bytes() | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | pub async fn serve<R, W>( | |
| 21 | + | http: &reqwest::Client, | |
| 22 | + | access: &Access, | |
| 23 | + | service: Service, | |
| 24 | + | protocol_v2: bool, | |
| 25 | + | reader: &mut R, | |
| 26 | + | writer: &mut W, | |
| 27 | + | ) -> Result<()> | |
| 28 | + | where | |
| 29 | + | R: AsyncBufRead + Unpin, | |
| 30 | + | W: AsyncWrite + Unpin, | |
| 31 | + | { | |
| 32 | + | let bridge = Bridge { | |
| 33 | + | http, | |
| 34 | + | access, | |
| 35 | + | service, | |
| 36 | + | // Artifacts only supports version 2 for fetches. | |
| 37 | + | protocol_v2: protocol_v2 && service == Service::UploadPack, | |
| 38 | + | }; | |
| 39 | + | match service { | |
| 40 | + | Service::UploadPack => bridge.upload_pack(reader, writer).await, | |
| 41 | + | Service::ReceivePack => bridge.receive_pack(reader, writer).await, | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | struct Bridge<'a> { | |
| 46 | + | http: &'a reqwest::Client, | |
| 47 | + | access: &'a Access, | |
| 48 | + | service: Service, | |
| 49 | + | protocol_v2: bool, | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | impl Bridge<'_> { | |
| 53 | + | fn request(&self, method: reqwest::Method, path: &str) -> reqwest::RequestBuilder { | |
| 54 | + | let mut request = self | |
| 55 | + | .http | |
| 56 | + | .request(method, format!("{}/{path}", self.access.remote)) | |
| 57 | + | .bearer_auth(&self.access.token); | |
| 58 | + | if self.protocol_v2 { | |
| 59 | + | request = request.header("git-protocol", "version=2"); | |
| 60 | + | } | |
| 61 | + | request | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | /// Sends the ref advertisement, which over SSH comes without the | |
| 65 | + | /// `# service=…` section that smart HTTP prepends. | |
| 66 | + | async fn advertise<W: AsyncWrite + Unpin>(&self, writer: &mut W) -> Result<()> { | |
| 67 | + | let service = self.service.as_str(); | |
| 68 | + | let response = self | |
| 69 | + | .request(reqwest::Method::GET, &format!("info/refs?service={service}")) | |
| 70 | + | .send() | |
| 71 | + | .await? | |
| 72 | + | .error_for_status()?; | |
| 73 | + | let body = response.bytes().await?; | |
| 74 | + | let mut advertisement = &body[..]; | |
| 75 | + | if advertisement.get(4..).is_some_and(|rest| rest.starts_with(b"# service=")) { | |
| 76 | + | let length = usize::from_str_radix(std::str::from_utf8(&body[..4])?, 16)?; | |
| 77 | + | ensure!(body.len() >= length + 4, "truncated ref advertisement"); | |
| 78 | + | advertisement = &body[length + 4..]; | |
| 79 | + | } | |
| 80 | + | writer.write_all(advertisement).await?; | |
| 81 | + | writer.flush().await?; | |
| 82 | + | Ok(()) | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | /// Posts one request and streams the response back to the client. | |
| 86 | + | async fn rpc<W: AsyncWrite + Unpin>(&self, body: Vec<u8>, writer: &mut W) -> Result<()> { | |
| 87 | + | let service = self.service.as_str(); | |
| 88 | + | let response = self | |
| 89 | + | .request(reqwest::Method::POST, service) | |
| 90 | + | .header("content-type", format!("application/x-{service}-request")) | |
| 91 | + | .header("accept", format!("application/x-{service}-result")) | |
| 92 | + | .body(body) | |
| 93 | + | .send() | |
| 94 | + | .await? | |
| 95 | + | .error_for_status()?; | |
| 96 | + | let mut stream = response.bytes_stream(); | |
| 97 | + | while let Some(chunk) = stream.next().await { | |
| 98 | + | writer.write_all(&chunk?).await?; | |
| 99 | + | } | |
| 100 | + | writer.flush().await?; | |
| 101 | + | Ok(()) | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | /// Protocol v2 is a series of self-contained commands, so each one maps | |
| 105 | + | /// onto a single HTTP request. | |
| 106 | + | async fn upload_pack<R, W>(&self, reader: &mut R, writer: &mut W) -> Result<()> | |
| 107 | + | where | |
| 108 | + | R: AsyncBufRead + Unpin, | |
| 109 | + | W: AsyncWrite + Unpin, | |
| 110 | + | { | |
| 111 | + | if !self.protocol_v2 { | |
| 112 | + | writer | |
| 113 | + | .write_all(&error_pkt( | |
| 114 | + | "g1t needs git protocol version 2 over SSH (git 2.26 or newer)", | |
| 115 | + | )) | |
| 116 | + | .await?; | |
| 117 | + | return Ok(()); | |
| 118 | + | } | |
| 119 | + | self.advertise(writer).await?; | |
| 120 | + | loop { | |
| 121 | + | let mut command = Vec::new(); | |
| 122 | + | if reader.fill_buf().await?.is_empty() { | |
| 123 | + | return Ok(()); | |
| 124 | + | } | |
| 125 | + | read_section(reader, &mut command).await?; | |
| 126 | + | if command == FLUSH { | |
| 127 | + | return Ok(()); | |
| 128 | + | } | |
| 129 | + | self.rpc(command, writer).await?; | |
| 130 | + | } | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | async fn receive_pack<R, W>(&self, reader: &mut R, writer: &mut W) -> Result<()> | |
| 134 | + | where | |
| 135 | + | R: AsyncBufRead + Unpin, | |
| 136 | + | W: AsyncWrite + Unpin, | |
| 137 | + | { | |
| 138 | + | self.advertise(writer).await?; | |
| 139 | + | if reader.fill_buf().await?.is_empty() { | |
| 140 | + | return Ok(()); | |
| 141 | + | } | |
| 142 | + | let mut body = Vec::new(); | |
| 143 | + | let lines = read_section(reader, &mut body).await?; | |
| 144 | + | let commands: Vec<&str> = lines | |
| 145 | + | .iter() | |
| 146 | + | .filter(|line| !line.starts_with("shallow ")) | |
| 147 | + | .map(String::as_str) | |
| 148 | + | .collect(); | |
| 149 | + | // A bare flush means the client had nothing to push. | |
| 150 | + | let Some(first) = commands.first() else { | |
| 151 | + | return Ok(()); | |
| 152 | + | }; | |
| 153 | + | let capabilities = first.split_once('\0').map_or("", |(_, caps)| caps); | |
| 154 | + | let has_push_options = capabilities.split(' ').any(|cap| cap == "push-options"); | |
| 155 | + | // Commands are "<old id> <new id> <ref>"; only deletions send no pack. | |
| 156 | + | let sends_pack = commands | |
| 157 | + | .iter() | |
| 158 | + | .any(|command| command.split(' ').nth(1) != Some(ZERO_ID)); | |
| 159 | + | ||
| 160 | + | if has_push_options { | |
| 161 | + | read_section(reader, &mut body).await?; | |
| 162 | + | } | |
| 163 | + | if sends_pack { | |
| 164 | + | read_pack(reader, &mut body).await?; | |
| 165 | + | } | |
| 166 | + | self.rpc(body, writer).await | |
| 167 | + | } | |
| 168 | + | } | |
| 169 | + | ||
| 170 | + | /// Reads pkt-lines up to and including the next flush, appending the raw | |
| 171 | + | /// bytes to `raw`. Returns the payload of each line. | |
| 172 | + | async fn read_section<R: AsyncBufRead + Unpin>( | |
| 173 | + | reader: &mut R, | |
| 174 | + | raw: &mut Vec<u8>, | |
| 175 | + | ) -> Result<Vec<String>> { | |
| 176 | + | let mut lines = Vec::new(); | |
| 177 | + | loop { | |
| 178 | + | let mut header = [0u8; 4]; | |
| 179 | + | reader.read_exact(&mut header).await?; | |
| 180 | + | raw.extend_from_slice(&header); | |
| 181 | + | let length = usize::from_str_radix(std::str::from_utf8(&header)?, 16)?; | |
| 182 | + | match length { | |
| 183 | + | 0 => return Ok(lines), | |
| 184 | + | // Delimiter and response-end packets carry no payload. | |
| 185 | + | 1 | 2 => continue, | |
| 186 | + | 3 => bail!("invalid pkt-line length"), | |
| 187 | + | _ => { | |
| 188 | + | let mut payload = vec![0u8; length - 4]; | |
| 189 | + | reader.read_exact(&mut payload).await?; | |
| 190 | + | raw.extend_from_slice(&payload); | |
| 191 | + | lines.push(String::from_utf8_lossy(&payload).trim_end().to_owned()); | |
| 192 | + | } | |
| 193 | + | } | |
| 194 | + | } | |
| 195 | + | } | |
| 196 | + | ||
| 197 | + | async fn read_into<R: AsyncBufRead + Unpin>( | |
| 198 | + | reader: &mut R, | |
| 199 | + | raw: &mut Vec<u8>, | |
| 200 | + | count: usize, | |
| 201 | + | ) -> Result<()> { | |
| 202 | + | let start = raw.len(); | |
| 203 | + | raw.resize(start + count, 0); | |
| 204 | + | reader.read_exact(&mut raw[start..]).await?; | |
| 205 | + | Ok(()) | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | async fn read_byte<R: AsyncBufRead + Unpin>(reader: &mut R, raw: &mut Vec<u8>) -> Result<u8> { | |
| 209 | + | let byte = reader.read_u8().await?; | |
| 210 | + | raw.push(byte); | |
| 211 | + | Ok(byte) | |
| 212 | + | } | |
| 213 | + | ||
| 214 | + | /// Reads exactly one packfile. Over SSH nothing marks where the pack ends | |
| 215 | + | /// (the client keeps the channel open for the reply), so the only way to | |
| 216 | + | /// find the end is to walk every object. | |
| 217 | + | async fn read_pack<R: AsyncBufRead + Unpin>(reader: &mut R, raw: &mut Vec<u8>) -> Result<()> { | |
| 218 | + | const OFS_DELTA: u8 = 6; | |
| 219 | + | const REF_DELTA: u8 = 7; | |
| 220 | + | ||
| 221 | + | let start = raw.len(); | |
| 222 | + | read_into(reader, raw, 12).await?; | |
| 223 | + | ensure!(&raw[start..start + 4] == b"PACK", "expected a packfile"); | |
| 224 | + | let objects = u32::from_be_bytes(raw[start + 8..start + 12].try_into()?); | |
| 225 | + | ||
| 226 | + | let mut scratch = vec![0u8; 64 * 1024]; | |
| 227 | + | for _ in 0..objects { | |
| 228 | + | // Type and size header: a varint whose first byte holds the type. | |
| 229 | + | let mut byte = read_byte(reader, raw).await?; | |
| 230 | + | let kind = (byte >> 4) & 7; | |
| 231 | + | while byte & 0x80 != 0 { | |
| 232 | + | byte = read_byte(reader, raw).await?; | |
| 233 | + | } | |
| 234 | + | match kind { | |
| 235 | + | OFS_DELTA => { | |
| 236 | + | while read_byte(reader, raw).await? & 0x80 != 0 {} | |
| 237 | + | } | |
| 238 | + | REF_DELTA => read_into(reader, raw, 20).await?, | |
| 239 | + | _ => {} | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | let mut inflate = Decompress::new(true); | |
| 243 | + | loop { | |
| 244 | + | let input = reader.fill_buf().await?; | |
| 245 | + | ensure!(!input.is_empty(), "packfile ended mid-object"); | |
| 246 | + | let before = inflate.total_in(); | |
| 247 | + | let status = inflate.decompress(input, &mut scratch, FlushDecompress::None)?; | |
| 248 | + | let consumed = (inflate.total_in() - before) as usize; | |
| 249 | + | raw.extend_from_slice(&input[..consumed]); | |
| 250 | + | reader.consume(consumed); | |
| 251 | + | if status == Status::StreamEnd { | |
| 252 | + | break; | |
| 253 | + | } | |
| 254 | + | } | |
| 255 | + | } | |
| 256 | + | // Trailing SHA-1 checksum. | |
| 257 | + | read_into(reader, raw, 20).await | |
| 258 | + | } |
| 1 | + | //! SSH front end for g1t. Accepts `git@g1t.sh:owner/repo.git`, authenticates | |
| 2 | + | //! the client's public key against the g1t Worker, and bridges git to | |
| 3 | + | //! Artifacts over HTTPS. | |
| 4 | + | //! | |
| 5 | + | //! Connections arrive either as raw TCP or wrapped in a WebSocket, which is | |
| 6 | + | //! how they reach a Cloudflare Container when tunnelled through a Worker. | |
| 7 | + | ||
| 8 | + | mod api; | |
| 9 | + | mod git; | |
| 10 | + | ||
| 11 | + | use std::collections::HashMap; | |
| 12 | + | use std::sync::Arc; | |
| 13 | + | use std::time::Duration; | |
| 14 | + | ||
| 15 | + | use anyhow::{Context, Result}; | |
| 16 | + | use futures_util::{SinkExt, StreamExt}; | |
| 17 | + | use russh::keys::ssh_key::{HashAlg, PublicKey}; | |
| 18 | + | use russh::keys::PrivateKey; | |
| 19 | + | use russh::server::{Auth, Config, Handler, Msg, Session}; | |
| 20 | + | use russh::{Channel, ChannelId, MethodKind, MethodSet}; | |
| 21 | + | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader}; | |
| 22 | + | use tokio::net::TcpListener; | |
| 23 | + | use tokio_tungstenite::tungstenite::Message; | |
| 24 | + | ||
| 25 | + | use api::{Api, Service, User}; | |
| 26 | + | ||
| 27 | + | const TCP_ADDR: &str = "0.0.0.0:2222"; | |
| 28 | + | const WEBSOCKET_ADDR: &str = "0.0.0.0:8080"; | |
| 29 | + | ||
| 30 | + | #[tokio::main] | |
| 31 | + | async fn main() -> Result<()> { | |
| 32 | + | let host_key = std::env::var("SSH_HOST_KEY").context("SSH_HOST_KEY is not set")?; | |
| 33 | + | let api = Arc::new(Api::new( | |
| 34 | + | std::env::var("G1T_API").unwrap_or_else(|_| "https://g1t.sh".into()), | |
| 35 | + | std::env::var("INTERNAL_SECRET").context("INTERNAL_SECRET is not set")?, | |
| 36 | + | )); | |
| 37 | + | let config = Arc::new(Config { | |
| 38 | + | keys: vec![PrivateKey::from_openssh(host_key).context("invalid SSH_HOST_KEY")?], | |
| 39 | + | methods: MethodSet::from(&[MethodKind::PublicKey][..]), | |
| 40 | + | auth_rejection_time: Duration::from_secs(1), | |
| 41 | + | auth_rejection_time_initial: Some(Duration::ZERO), | |
| 42 | + | inactivity_timeout: Some(Duration::from_secs(300)), | |
| 43 | + | ..Default::default() | |
| 44 | + | }); | |
| 45 | + | ||
| 46 | + | let tcp = TcpListener::bind(TCP_ADDR).await?; | |
| 47 | + | let websocket = TcpListener::bind(WEBSOCKET_ADDR).await?; | |
| 48 | + | eprintln!("g1t-sshd listening on {TCP_ADDR} (tcp) and {WEBSOCKET_ADDR} (websocket)"); | |
| 49 | + | loop { | |
| 50 | + | tokio::select! { | |
| 51 | + | accepted = tcp.accept() => { | |
| 52 | + | let (stream, _) = accepted?; | |
| 53 | + | tokio::spawn(run_session(config.clone(), api.clone(), stream)); | |
| 54 | + | } | |
| 55 | + | accepted = websocket.accept() => { | |
| 56 | + | let (stream, _) = accepted?; | |
| 57 | + | let (config, api) = (config.clone(), api.clone()); | |
| 58 | + | tokio::spawn(async move { | |
| 59 | + | match tokio_tungstenite::accept_async(stream).await { | |
| 60 | + | Ok(socket) => run_session(config, api, websocket_stream(socket)).await, | |
| 61 | + | Err(error) => eprintln!("websocket handshake failed: {error}"), | |
| 62 | + | } | |
| 63 | + | }); | |
| 64 | + | } | |
| 65 | + | } | |
| 66 | + | } | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | async fn run_session<S>(config: Arc<Config>, api: Arc<Api>, stream: S) | |
| 70 | + | where | |
| 71 | + | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, | |
| 72 | + | { | |
| 73 | + | let handler = Connection { | |
| 74 | + | api, | |
| 75 | + | user: None, | |
| 76 | + | git_protocol: None, | |
| 77 | + | channels: HashMap::new(), | |
| 78 | + | }; | |
| 79 | + | let result = match russh::server::run_stream(config, stream, handler).await { | |
| 80 | + | Ok(session) => session.await, | |
| 81 | + | Err(error) => Err(error), | |
| 82 | + | }; | |
| 83 | + | if let Err(error) = result { | |
| 84 | + | eprintln!("session ended: {error:#}"); | |
| 85 | + | } | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | /// Presents a WebSocket's binary messages as a plain byte stream. | |
| 89 | + | fn websocket_stream<S>(socket: tokio_tungstenite::WebSocketStream<S>) -> tokio::io::DuplexStream | |
| 90 | + | where | |
| 91 | + | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, | |
| 92 | + | { | |
| 93 | + | let (ours, theirs) = tokio::io::duplex(64 * 1024); | |
| 94 | + | let (mut sink, mut source) = socket.split(); | |
| 95 | + | let (mut from_ssh, mut to_ssh) = tokio::io::split(theirs); | |
| 96 | + | tokio::spawn(async move { | |
| 97 | + | while let Some(Ok(message)) = source.next().await { | |
| 98 | + | if let Message::Binary(bytes) = message { | |
| 99 | + | if to_ssh.write_all(&bytes).await.is_err() { | |
| 100 | + | break; | |
| 101 | + | } | |
| 102 | + | } | |
| 103 | + | } | |
| 104 | + | let _ = to_ssh.shutdown().await; | |
| 105 | + | }); | |
| 106 | + | tokio::spawn(async move { | |
| 107 | + | let mut buffer = vec![0u8; 32 * 1024]; | |
| 108 | + | loop { | |
| 109 | + | match from_ssh.read(&mut buffer).await { | |
| 110 | + | Ok(0) | Err(_) => break, | |
| 111 | + | Ok(read) => { | |
| 112 | + | let message = Message::Binary(buffer[..read].to_vec().into()); | |
| 113 | + | if sink.send(message).await.is_err() { | |
| 114 | + | break; | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | } | |
| 118 | + | } | |
| 119 | + | let _ = sink.close().await; | |
| 120 | + | }); | |
| 121 | + | ours | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | struct Connection { | |
| 125 | + | api: Arc<Api>, | |
| 126 | + | user: Option<User>, | |
| 127 | + | /// Value of the `GIT_PROTOCOL` environment variable, if the client sent it. | |
| 128 | + | git_protocol: Option<String>, | |
| 129 | + | channels: HashMap<ChannelId, Channel<Msg>>, | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | impl Connection { | |
| 133 | + | async fn lookup(&self, key: &PublicKey) -> Result<Option<User>> { | |
| 134 | + | let fingerprint = key.fingerprint(HashAlg::Sha256).to_string(); | |
| 135 | + | self.api.user_for_key(&fingerprint).await | |
| 136 | + | } | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | impl Handler for Connection { | |
| 140 | + | type Error = anyhow::Error; | |
| 141 | + | ||
| 142 | + | async fn auth_publickey_offered(&mut self, _: &str, key: &PublicKey) -> Result<Auth> { | |
| 143 | + | Ok(match self.lookup(key).await? { | |
| 144 | + | Some(_) => Auth::Accept, | |
| 145 | + | None => Auth::reject(), | |
| 146 | + | }) | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | /// Called once the client has proven it holds the private key. | |
| 150 | + | async fn auth_publickey(&mut self, _: &str, key: &PublicKey) -> Result<Auth> { | |
| 151 | + | self.user = self.lookup(key).await?; | |
| 152 | + | Ok(match self.user { | |
| 153 | + | Some(_) => Auth::Accept, | |
| 154 | + | None => Auth::reject(), | |
| 155 | + | }) | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | async fn channel_open_session( | |
| 159 | + | &mut self, | |
| 160 | + | channel: Channel<Msg>, | |
| 161 | + | reply: russh::server::ChannelOpenHandle, | |
| 162 | + | _: &mut Session, | |
| 163 | + | ) -> Result<()> { | |
| 164 | + | self.channels.insert(channel.id(), channel); | |
| 165 | + | reply.accept().await; | |
| 166 | + | Ok(()) | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | async fn env_request( | |
| 170 | + | &mut self, | |
| 171 | + | channel: ChannelId, | |
| 172 | + | name: &str, | |
| 173 | + | value: &str, | |
| 174 | + | session: &mut Session, | |
| 175 | + | ) -> Result<()> { | |
| 176 | + | if name == "GIT_PROTOCOL" { | |
| 177 | + | self.git_protocol = Some(value.to_owned()); | |
| 178 | + | } | |
| 179 | + | session.channel_success(channel)?; | |
| 180 | + | Ok(()) | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | async fn shell_request(&mut self, id: ChannelId, session: &mut Session) -> Result<()> { | |
| 184 | + | let (Some(user), Some(channel)) = (&self.user, self.channels.remove(&id)) else { | |
| 185 | + | return Ok(session.channel_failure(id)?); | |
| 186 | + | }; | |
| 187 | + | session.channel_success(id)?; | |
| 188 | + | let greeting = format!( | |
| 189 | + | "Hi {}! You've successfully authenticated, but g1t does not provide shell access.\r\n", | |
| 190 | + | user.username | |
| 191 | + | ); | |
| 192 | + | tokio::spawn(async move { | |
| 193 | + | let _ = channel.data(greeting.as_bytes()).await; | |
| 194 | + | finish(channel, 1).await; | |
| 195 | + | }); | |
| 196 | + | Ok(()) | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | async fn exec_request( | |
| 200 | + | &mut self, | |
| 201 | + | id: ChannelId, | |
| 202 | + | command: &[u8], | |
| 203 | + | session: &mut Session, | |
| 204 | + | ) -> Result<()> { | |
| 205 | + | let (Some(user), Some(channel)) = (self.user.clone(), self.channels.remove(&id)) else { | |
| 206 | + | return Ok(session.channel_failure(id)?); | |
| 207 | + | }; | |
| 208 | + | session.channel_success(id)?; | |
| 209 | + | ||
| 210 | + | let command = String::from_utf8_lossy(command).into_owned(); | |
| 211 | + | let protocol_v2 = self | |
| 212 | + | .git_protocol | |
| 213 | + | .as_deref() | |
| 214 | + | .is_some_and(|value| value.split(':').any(|part| part == "version=2")); | |
| 215 | + | let api = self.api.clone(); | |
| 216 | + | tokio::spawn(async move { | |
| 217 | + | let (mut read_half, write_half) = channel.split(); | |
| 218 | + | let mut reader = BufReader::new(read_half.make_reader()); | |
| 219 | + | let mut writer = write_half.make_writer(); | |
| 220 | + | let result = run_git(&api, &user, &command, protocol_v2, &mut reader, &mut writer).await; | |
| 221 | + | let status = match result { | |
| 222 | + | Ok(()) => 0, | |
| 223 | + | Err(error) => { | |
| 224 | + | eprintln!("{}: `{command}` failed: {error:#}", user.username); | |
| 225 | + | let _ = writer.write_all(&git::error_pkt("internal error")).await; | |
| 226 | + | 1 | |
| 227 | + | } | |
| 228 | + | }; | |
| 229 | + | let _ = writer.shutdown().await; | |
| 230 | + | let _ = write_half.exit_status(status).await; | |
| 231 | + | let _ = write_half.eof().await; | |
| 232 | + | let _ = write_half.close().await; | |
| 233 | + | }); | |
| 234 | + | Ok(()) | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | async fn finish(channel: Channel<Msg>, status: u32) { | |
| 239 | + | let _ = channel.exit_status(status).await; | |
| 240 | + | let _ = channel.eof().await; | |
| 241 | + | let _ = channel.close().await; | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | /// Parses `git-upload-pack 'owner/repo.git'` and friends. | |
| 245 | + | fn parse_command(command: &str) -> Option<(Service, &str, &str)> { | |
| 246 | + | let (program, path) = command.trim().rsplit_once(' ')?; | |
| 247 | + | let service = match program { | |
| 248 | + | "git-upload-pack" | "git upload-pack" => Service::UploadPack, | |
| 249 | + | "git-receive-pack" | "git receive-pack" => Service::ReceivePack, | |
| 250 | + | _ => return None, | |
| 251 | + | }; | |
| 252 | + | let path = path.trim_matches(['\'', '"']).trim_start_matches('/'); | |
| 253 | + | let path = path.strip_suffix(".git").unwrap_or(path); | |
| 254 | + | let (owner, repo) = path.split_once('/')?; | |
| 255 | + | (!owner.is_empty() && !repo.is_empty() && !repo.contains('/')).then_some((service, owner, repo)) | |
| 256 | + | } | |
| 257 | + | ||
| 258 | + | async fn run_git<R, W>( | |
| 259 | + | api: &Api, | |
| 260 | + | user: &User, | |
| 261 | + | command: &str, | |
| 262 | + | protocol_v2: bool, | |
| 263 | + | reader: &mut R, | |
| 264 | + | writer: &mut W, | |
| 265 | + | ) -> Result<()> | |
| 266 | + | where | |
| 267 | + | R: tokio::io::AsyncBufRead + Unpin, | |
| 268 | + | W: AsyncWrite + Unpin, | |
| 269 | + | { | |
| 270 | + | let Some((service, owner, repo)) = parse_command(command) else { | |
| 271 | + | writer | |
| 272 | + | .write_all(&git::error_pkt("g1t only supports git over SSH")) | |
| 273 | + | .await?; | |
| 274 | + | return Ok(()); | |
| 275 | + | }; | |
| 276 | + | match api.access(user, owner, repo, service).await? { | |
| 277 | + | Ok(access) => git::serve(&api.http, &access, service, protocol_v2, reader, writer).await, | |
| 278 | + | Err(message) => Ok(writer.write_all(&git::error_pkt(&message)).await?), | |
| 279 | + | } | |
| 280 | + | } |
| 1 | + | # g1t plan | |
| 2 | + | ||
| 3 | + | g1t is a git forge for agents, built on Cloudflare Workers and Artifacts for | |
| 4 | + | the "Build the Next-Gen Git Platform on Cloudflare" competition. | |
| 5 | + | ||
| 6 | + | - Submission closes **October 14, 2026, 11:59 PM PDT**: a 5–10 minute demo | |
| 7 | + | video, this repository (MIT) and run instructions. | |
| 8 | + | - Judging: 50% originality and quality of the prototype for agent-oriented | |
| 9 | + | collaboration; 25% multi-agent concurrency, coordination, context | |
| 10 | + | preservation, review and conflict handling; 25% ease of use. | |
| 11 | + | ||
| 12 | + | ## Product model | |
| 13 | + | ||
| 14 | + | A pull request assumes one author and one change. g1t assumes many agents | |
| 15 | + | working at once, in two shapes: several agents racing on the same goal, and | |
| 16 | + | many different goals in flight that all have to land on `main`. | |
| 17 | + | ||
| 18 | + | | Concept | What it is | | |
| 19 | + | | --- | --- | | |
| 20 | + | | **Intent** | A goal stated against a repo, with acceptance checks (commands that must pass). Replaces the issue and the pull request. | | |
| 21 | + | | **Attempt** | One agent's run at an intent, in its own Artifacts fork. Any number run in parallel. | | |
| 22 | + | | **Session** | The agent's full context for an attempt: prompt, messages, tool calls, cost. Stored with the attempt and linked from every commit it produced. | | |
| 23 | + | | **Arena** | The compare view for an intent: every attempt side by side with diff, check results, conflicts against main and against each other, and a reviewer agent's summary. | | |
| 24 | + | | **Ship** | A person or a policy picks an attempt. A per-repo merge queue lands it; the other attempts are rebased by their agents or closed. | | |
| 25 | + | ||
| 26 | + | Features that fall out of the model: | |
| 27 | + | ||
| 28 | + | - **Why-blame.** Click a line and see the prompt and reasoning that produced | |
| 29 | + | it, not only the commit. | |
| 30 | + | - **Overlap radar.** Attempts that touch the same files are flagged while the | |
| 31 | + | agents are still working, and the agents are told. | |
| 32 | + | - **Live lanes.** Watch every attempt progress in real time. | |
| 33 | + | ||
| 34 | + | ## Converging on main | |
| 35 | + | ||
| 36 | + | Twelve intents started together will finish at different times and touch | |
| 37 | + | overlapping code. Getting them all into `main` without a person refereeing | |
| 38 | + | is the hard part, and it is handled in four places. | |
| 39 | + | ||
| 40 | + | 1. **Before work starts: plan the overlap away.** A project is a graph of | |
| 41 | + | intents. A planner agent can split a large goal into intents, predict | |
| 42 | + | which files each will touch, and add a dependency where two would collide, | |
| 43 | + | so one starts from the other's result instead of from `main`. | |
| 44 | + | 2. **While agents work: overlap radar.** Each attempt's changed files and | |
| 45 | + | symbols are tracked as it pushes. When two attempts from different intents | |
| 46 | + | enter the same area, both agents are told what the other is doing there. | |
| 47 | + | 3. **When `main` moves: the author resolves.** Every open attempt is | |
| 48 | + | trial-merged against the new `main`. A clean merge updates the attempt | |
| 49 | + | silently. A conflict resumes that attempt's agent with its original | |
| 50 | + | session and the incoming change, so the conflict is resolved by the agent | |
| 51 | + | that wrote the code and still knows why. | |
| 52 | + | 4. **At landing: a speculative queue.** Shipped attempts enter the repo's | |
| 53 | + | queue. g1t builds the combined states (`main`+A, `main`+A+B, …) and runs | |
| 54 | + | their checks in parallel. Attempts land in order as their combined state | |
| 55 | + | passes; one that fails is ejected back to its agent and the states behind | |
| 56 | + | it are rebuilt. `main` only ever receives a state that passed. | |
| 57 | + | ||
| 58 | + | Landing can be fully automatic: a repo policy such as "checks pass and the | |
| 59 | + | reviewer agent approves" ships without a person. | |
| 60 | + | ||
| 61 | + | ## Agents aware of each other | |
| 62 | + | ||
| 63 | + | Each repo keeps a live **work registry**: for every running attempt, its | |
| 64 | + | intent, a running summary of what it has done, and the files and symbols it | |
| 65 | + | has touched or plans to touch. Agents use it through MCP tools; g1t also | |
| 66 | + | acts on it without being asked. | |
| 67 | + | ||
| 68 | + | - **Before starting.** When an intent is opened, or an agent is about to | |
| 69 | + | begin a task, g1t searches open intents and running attempts for the same | |
| 70 | + | goal (by meaning, not wording) and for the same area of code. If a match | |
| 71 | + | exists the agent is told who is on it and how far along, and chooses: join | |
| 72 | + | as a deliberate racer, wait for the result, or drop the task. Duplicate | |
| 73 | + | intents are offered for merging. | |
| 74 | + | - **Finding out-of-scope work.** An agent that discovers something outside | |
| 75 | + | its intent asks the registry who works there. If another attempt owns that | |
| 76 | + | area, it **hands off**: a note, the relevant excerpt of its session, and | |
| 77 | + | optionally commits the receiver can take. If nobody does, it opens a child | |
| 78 | + | intent instead of widening its own change. | |
| 79 | + | - **Asking.** An agent can put a question or a request to another attempt. | |
| 80 | + | The receiver gets it at its next turn. | |
| 81 | + | - **Waiting.** An agent that needs another attempt's result parks itself. | |
| 82 | + | Its sandbox sleeps, spend stops, and it resumes from the new state when | |
| 83 | + | that attempt ships. | |
| 84 | + | - **Agents that do not cooperate.** For pushes from tools that never call | |
| 85 | + | these tools, g1t compares the pushed change against running attempts and | |
| 86 | + | flags near-duplicates itself. | |
| 87 | + | ||
| 88 | + | Every handoff, question and wait has a state (offered, accepted, declined, | |
| 89 | + | done), appears in the timeline, and is visible to people. A handoff declined | |
| 90 | + | twice, or two agents passing work back and forth, goes to the "needs you" | |
| 91 | + | inbox. | |
| 92 | + | ||
| 93 | + | ## Review at scale | |
| 94 | + | ||
| 95 | + | Cloudflare's brief asks "how do you review everything they produce?". With | |
| 96 | + | hundreds of agents, a person cannot read every diff, so review is by | |
| 97 | + | exception. | |
| 98 | + | ||
| 99 | + | - **Evidence, not diffs.** Every attempt carries a proof bundle: checks run | |
| 100 | + | and their output, a preview URL, a plain-language summary, and the | |
| 101 | + | behaviour that changed. | |
| 102 | + | - **Two agent reviewers.** One reviews the change against the intent. A | |
| 103 | + | second is adversarial: it tries to break the change and reports what it | |
| 104 | + | found. | |
| 105 | + | - **Risk tiers.** Each change is scored from what it touches, how large it | |
| 106 | + | is, and how the reviewers ruled. Low risk ships on policy; high risk goes | |
| 107 | + | to a person with the evidence already assembled. | |
| 108 | + | - **Trust is earned.** An agent's record on a path (shipped, reverted, caught | |
| 109 | + | by review) raises or lowers the tier its changes land in. | |
| 110 | + | - **Sampling.** A share of auto-shipped changes is sent to a person anyway, | |
| 111 | + | to keep the policy honest. | |
| 112 | + | ||
| 113 | + | ## Rethinking the git primitives | |
| 114 | + | ||
| 115 | + | - **No branches for agents.** An attempt is a fork; `main` is the only | |
| 116 | + | long-lived line. There is nothing to name, clean up or go stale. | |
| 117 | + | - **Projected main.** New attempts start from `main` plus everything already | |
| 118 | + | in the landing queue, so they are built on the state they will land on. | |
| 119 | + | - **Structural merge.** The merge engine merges by syntax tree, not by line, | |
| 120 | + | for supported languages. Two agents adding different functions to the same | |
| 121 | + | file do not conflict. | |
| 122 | + | - **Forkable sessions.** A session can be forked at any turn: the code as it | |
| 123 | + | was at that moment plus the conversation up to it, continued with a | |
| 124 | + | different instruction. Branching applies to the reasoning as well as the | |
| 125 | + | code. | |
| 126 | + | - **Provenance in history.** Every commit records its intent, session, | |
| 127 | + | agent, model and cost, and is signed with a key issued to that attempt. The | |
| 128 | + | history can be audited by machine. | |
| 129 | + | ||
| 130 | + | ## People in the loop | |
| 131 | + | ||
| 132 | + | ### Code that arrives from outside | |
| 133 | + | ||
| 134 | + | People will keep pushing with plain git, their editor, or another tool. Every | |
| 135 | + | push goes through g1t's git front end, so none of it bypasses the model. | |
| 136 | + | ||
| 137 | + | - **A push to a branch becomes an attempt.** g1t adopts it with the pusher as | |
| 138 | + | author. A reviewer agent writes the intent it appears to serve and offers | |
| 139 | + | to attach it to an open intent it matches. From there it gets the same | |
| 140 | + | checks, arena and queue as agent work. | |
| 141 | + | - **A push to `main` follows repo policy.** Protected: refused with a message | |
| 142 | + | saying which ref to push to instead, so it enters the queue. Open: accepted | |
| 143 | + | and treated as "`main` moved", which re-verifies the queue and triggers | |
| 144 | + | resolve-on-move for every open attempt. | |
| 145 | + | - **Context is an open format.** A commit trailer names the session that | |
| 146 | + | produced it, so any tool can attach its transcript. Commits without one are | |
| 147 | + | shown in why-blame as "pushed by a person, no session". | |
| 148 | + | - **Approval rules.** Per repo and per path: ship automatically, require a | |
| 149 | + | named person, or require a person when the change is large or the reviewer | |
| 150 | + | agent is unsure. | |
| 151 | + | ||
| 152 | + | ### Joining work that is already running | |
| 153 | + | ||
| 154 | + | - **Every session has a live page** that works on a phone: the transcript as | |
| 155 | + | it streams, the current diff, check results. | |
| 156 | + | - **Steer.** Send a message, pause, or redirect. Hosted agents receive it | |
| 157 | + | immediately; a person's own Claude Code receives it at its next turn | |
| 158 | + | through the CLI hooks. | |
| 159 | + | - **Answer.** When an agent is blocked on a question, it appears in a "needs | |
| 160 | + | you" inbox and as a notification. The answer resumes the agent. | |
| 161 | + | - **Take over and hand back.** Check out the attempt's fork, commit by hand, | |
| 162 | + | push, and let the agent continue from there. | |
| 163 | + | ||
| 164 | + | ### Planning by writing | |
| 165 | + | ||
| 166 | + | - **Brief.** Write the outcome in prose on the site, or commit it as a | |
| 167 | + | markdown file. A planner agent turns it into a project: intents, acceptance | |
| 168 | + | checks, dependencies. The person edits the graph before anything starts. | |
| 169 | + | - **Plan from their own agent.** The same operations are MCP tools, so a | |
| 170 | + | person can plan in their own Claude Code session and create the project | |
| 171 | + | from there. | |
| 172 | + | - **The brief stays the source of truth.** Editing it later re-plans: new | |
| 173 | + | intents are added, obsolete ones are closed. | |
| 174 | + | ||
| 175 | + | ### Seeing what moved | |
| 176 | + | ||
| 177 | + | - **Project page.** The outcome, the intent graph coloured by state, and how | |
| 178 | + | many acceptance checks pass now compared with when the project started. | |
| 179 | + | - **Digest.** An agent-written summary per project and per person: what | |
| 180 | + | shipped, what is blocked on whom, which conflicts were resolved, what it | |
| 181 | + | cost. | |
| 182 | + | - **Timeline.** Every event (push, steer, check, conflict, ship) in order, | |
| 183 | + | each linked to the session and the person or agent behind it. | |
| 184 | + | ||
| 185 | + | ## One session, any surface | |
| 186 | + | ||
| 187 | + | A session belongs to g1t, not to the device it started on. The browser, a | |
| 188 | + | phone and Claude Code are views of the same session. | |
| 189 | + | ||
| 190 | + | - **Browser and phone.** The site is a responsive, installable web app with | |
| 191 | + | push notifications. Everything a person does (brief, steer, answer, | |
| 192 | + | approve, ship) works there. | |
| 193 | + | - **Claude Code.** Through `mcp.g1t.sh` and the CLI hooks, a local session is | |
| 194 | + | a g1t session: its transcript syncs as it runs and it appears in mission | |
| 195 | + | control like any other. | |
| 196 | + | - **Moving a session.** A local session can be sent to the cloud: a hosted | |
| 197 | + | agent takes over the fork and the transcript and continues, so the laptop | |
| 198 | + | can close. A hosted session can be pulled down: the CLI checks out the fork | |
| 199 | + | and resumes it in local Claude Code with its history. | |
| 200 | + | - **Limit.** A session running only on a laptop stops when the laptop does. | |
| 201 | + | It can be steered between turns but not continued until it is moved or the | |
| 202 | + | laptop is back. | |
| 203 | + | ||
| 204 | + | ## For people who do not write code | |
| 205 | + | ||
| 206 | + | - **Documents are first-class.** Specs, guides, policies and decisions live | |
| 207 | + | in repos as markdown, shown in a Docs view: rendered pages, edited in the | |
| 208 | + | browser like a document, with inline comments. "Suggest a change" is an | |
| 209 | + | attempt and "publish" is ship, without git vocabulary. | |
| 210 | + | - **Document intents.** "Write the onboarding guide for the billing API" is | |
| 211 | + | an intent. Its acceptance checks are a checklist judged by a reviewer agent | |
| 212 | + | instead of commands. Agents draft and revise; people comment and approve. | |
| 213 | + | - **Templates.** Product brief, RFC, decision record. A filled-in template is | |
| 214 | + | a brief the planner can turn into a project. | |
| 215 | + | - **Explain.** Ask about any repo, project or change in plain language and | |
| 216 | + | get an answer with links to the code and sessions behind it. | |
| 217 | + | - **Living documentation.** g1t generates "how this works" pages from the | |
| 218 | + | code and keeps them current. When a shipped change contradicts a document, | |
| 219 | + | an intent opens to update it. | |
| 220 | + | - **See it, don't read it.** Every attempt on a deployable repo gets a | |
| 221 | + | preview URL (Workers Builds from the attempt's fork), so an approver clicks | |
| 222 | + | through the result instead of reading a diff. Changes are also summarised | |
| 223 | + | in plain language. | |
| 224 | + | - **Roles.** Viewer, commenter, planner, approver: a person can plan and | |
| 225 | + | approve work without ever cloning a repo. | |
| 226 | + | ||
| 227 | + | ## The macro view | |
| 228 | + | ||
| 229 | + | The hierarchy above a single repo: | |
| 230 | + | ||
| 231 | + | | Level | What it is | | |
| 232 | + | | --- | --- | | |
| 233 | + | | **Workspace** | A company or team: its people, repos, agents, budget and policies. | | |
| 234 | + | | **Initiative** | A business outcome with an owner and measurable results, e.g. "move billing to usage-based pricing". Spans any number of repos. | | |
| 235 | + | | **Project** | One deliverable inside an initiative: a brief and its graph of intents. | | |
| 236 | + | | **Intent / Attempt** | As above. An intent may touch several repos; its attempt then holds one fork per repo and they land together. | | |
| 237 | + | ||
| 238 | + | ### Portfolio | |
| 239 | + | ||
| 240 | + | One page answers "where is the business" across every initiative: | |
| 241 | + | ||
| 242 | + | - **Health** per initiative: on track, at risk, or blocked, derived from | |
| 243 | + | facts (checks passing, intents stalled, questions waiting on a person), | |
| 244 | + | not self-reported. | |
| 245 | + | - **Progress** as measurable results: acceptance checks passing, intents | |
| 246 | + | shipped out of planned, and the trend since the start. | |
| 247 | + | - **Forecast** from actual throughput: at the current rate, when the | |
| 248 | + | remaining intents land. | |
| 249 | + | - **Spend** in tokens and dollars against a budget, per initiative. | |
| 250 | + | - **Waiting on people**: every decision or approval a person owes, by name. | |
| 251 | + | - **Roadmap**: initiatives laid out as now, next, later, with optional | |
| 252 | + | time-boxed cycles for teams that work in sprints. | |
| 253 | + | ||
| 254 | + | ### Status without asking | |
| 255 | + | ||
| 256 | + | - **Standup.** An agent writes a daily report per initiative and one for the | |
| 257 | + | whole workspace: what shipped, what changed direction, what is at risk and | |
| 258 | + | why, what needs a person. Delivered by email or webhook. | |
| 259 | + | - **Ask.** A question box over the full event log and all sessions: "what | |
| 260 | + | happened on the billing migration since Monday?" answers with links to the | |
| 261 | + | sessions and commits behind each claim. | |
| 262 | + | ||
| 263 | + | ### Long-running agents | |
| 264 | + | ||
| 265 | + | Work that runs for days needs supervision that does not depend on someone | |
| 266 | + | watching. | |
| 267 | + | ||
| 268 | + | - **Checkpoints.** A long attempt reports milestones against its intent, so | |
| 269 | + | progress is visible before anything ships. | |
| 270 | + | - **Stall and drift detection.** An attempt with no meaningful progress, or | |
| 271 | + | whose changes have wandered away from its intent, is flagged and can be | |
| 272 | + | stopped or re-briefed automatically. | |
| 273 | + | - **Budgets.** Hard limits on spend and time per attempt, project and | |
| 274 | + | initiative. | |
| 275 | + | ||
| 276 | + | ### Context hub | |
| 277 | + | ||
| 278 | + | Agents working across repos and days need context that outlives any one | |
| 279 | + | session and reaches beyond the code. The context hub is one place an agent | |
| 280 | + | asks, whatever the source. | |
| 281 | + | ||
| 282 | + | | Source | What it holds | How it gets there | | |
| 283 | + | | --- | --- | --- | | |
| 284 | + | | **Memory** | Decisions, conventions, gotchas, facts about systems | Written by agents and people in g1t | | |
| 285 | + | | **Code and sessions** | The repos, and the reasoning behind every change | Already in g1t | | |
| 286 | + | | **Connected sources** | Jira and Linear tickets, Notion and Confluence pages, Google Drive documents, Slack threads, Sentry issues | Connectors, authorised per workspace | | |
| 287 | + | ||
| 288 | + | How it behaves: | |
| 289 | + | ||
| 290 | + | - **One search.** An agent asks a question and gets ranked results across | |
| 291 | + | all sources, each labelled with where it came from, who wrote it, and how | |
| 292 | + | fresh it is. | |
| 293 | + | - **Connected sources stay where they are.** g1t indexes them for search and | |
| 294 | + | fetches the current version when an agent opens one. The external system | |
| 295 | + | remains the source of truth, and a link placed on an intent ("see | |
| 296 | + | JIRA-482", a Notion URL) is pulled into the agent's starting context. | |
| 297 | + | - **Permissions carry over.** A connector only exposes what the connecting | |
| 298 | + | account can see, and a workspace admin chooses which spaces, projects or | |
| 299 | + | channels are included. | |
| 300 | + | - **External content is untrusted.** A ticket or page can contain text meant | |
| 301 | + | to manipulate an agent. It is marked as reference material, never treated | |
| 302 | + | as instructions. | |
| 303 | + | - **Documentation is separate.** Context is what agents know; documentation | |
| 304 | + | is what people read, and it is generated from context and code. | |
| 305 | + | ||
| 306 | + | Memory is the part of the hub that g1t owns and agents write to: | |
| 307 | + | ||
| 308 | + | - **Memory is written freely.** Any agent or person adds an entry with one | |
| 309 | + | call: a decision, a convention, a gotcha, a fact about a system. No review | |
| 310 | + | gate. Each entry records who wrote it, from which session, and when. | |
| 311 | + | - **It is still a repository.** Each workspace has a memory repo in | |
| 312 | + | Artifacts, so every write is a commit: versioned, attributable, and | |
| 313 | + | revertible. | |
| 314 | + | - **It is kept healthy by an agent.** A consolidation agent merges | |
| 315 | + | duplicates, retires entries that newer ones contradict, and flags | |
| 316 | + | conflicts it cannot settle. People can pin an entry (agents may not change | |
| 317 | + | it), correct it, or retract it. | |
| 318 | + | - **Agents read it.** Every session starts with the context relevant to its | |
| 319 | + | intent, found by search, and can query more through MCP. | |
| 320 | + | - **Updates are events.** A memory write or a change in a connected source | |
| 321 | + | is an event, so "when context changes, update the affected docs" is an | |
| 322 | + | automation, on by default. | |
| 323 | + | - **It is scoped inside the workspace.** Some context applies to the whole | |
| 324 | + | workspace, some to one initiative, project or repo, so an agent gets what | |
| 325 | + | applies to its work. | |
| 326 | + | - **It never crosses workspaces.** A workspace is the isolation boundary: its | |
| 327 | + | context, sessions and private repos are invisible to every other | |
| 328 | + | workspace, and an agent's token is bound to one workspace. | |
| 329 | + | ||
| 330 | + | ## Working in g1t | |
| 331 | + | ||
| 332 | + | - **Mission control.** The signed-in home page: every running session, every | |
| 333 | + | intent waiting on a decision, and what shipped, across all repos. | |
| 334 | + | - **Projects.** Group intents across repos toward one outcome and track how | |
| 335 | + | many are open, racing, or shipped. | |
| 336 | + | - **Steering.** Send a message to a running attempt, or to all attempts on an | |
| 337 | + | intent at once, without stopping them. | |
| 338 | + | - **Automations.** Rules that start work without a person (next section). | |
| 339 | + | ||
| 340 | + | ## Automations and integrations | |
| 341 | + | ||
| 342 | + | An automation is **when** an event happens, **if** conditions hold, **do** | |
| 343 | + | something. They are defined as files in the repo (`.g1t/automations/`), the | |
| 344 | + | way GitHub Actions workflows are, and can also be built in the UI. | |
| 345 | + | ||
| 346 | + | ### Events that can trigger one | |
| 347 | + | ||
| 348 | + | | Source | Examples | | |
| 349 | + | | --- | --- | | |
| 350 | + | | Git | push, ship, check failed, `main` moved | | |
| 351 | + | | g1t | intent opened, attempt stalled, context updated, handoff declined, budget reached | | |
| 352 | + | | Time | cron schedule | | |
| 353 | + | | Integrations | Sentry issue, PagerDuty incident, Linear or Jira ticket, Slack message or mention, GitHub issue, Stripe event | | |
| 354 | + | | Anything else | a signed generic webhook, or an email to a per-repo address | | |
| 355 | + | ||
| 356 | + | **Actions**: open an intent (optionally racing N attempts with a named | |
| 357 | + | agent), message a running attempt, update documentation, notify, call a | |
| 358 | + | webhook, write back to the source system. | |
| 359 | + | ||
| 360 | + | **Example: Sentry.** A new production error arrives. The automation opens an | |
| 361 | + | intent with the stack trace, release and frequency as its brief. Why-blame | |
| 362 | + | finds the session that wrote the failing line, so the fixing agent starts | |
| 363 | + | with the original reasoning. When the fix ships, g1t comments on the Sentry | |
| 364 | + | issue and resolves it. | |
| 365 | + | ||
| 366 | + | ### Rules every automation obeys | |
| 367 | + | ||
| 368 | + | - **Deduplication.** The same Sentry issue firing 500 times maps to one | |
| 369 | + | intent. | |
| 370 | + | - **Limits.** Concurrency and budget caps per automation. | |
| 371 | + | - **Loop protection.** Work started by an automation cannot retrigger the | |
| 372 | + | same automation without a person in between. | |
| 373 | + | - **External input is untrusted.** A webhook payload can contain text written | |
| 374 | + | by an attacker. Agents started by external events run with reduced | |
| 375 | + | permissions and cannot ship without the repo's approval rule passing. | |
| 376 | + | ||
| 377 | + | **Checks** are the other half of what GitHub Actions does: build and test | |
| 378 | + | commands declared in `.g1t/checks.yaml`, run in sandboxes on every attempt | |
| 379 | + | and on every combined state in the landing queue. | |
| 380 | + | ||
| 381 | + | Agents can also reach integrations directly: an agent definition lists MCP | |
| 382 | + | servers (Sentry, Linear and so on) it may use while working. | |
| 383 | + | ||
| 384 | + | ## Agents and models | |
| 385 | + | ||
| 386 | + | ### Defining an agent | |
| 387 | + | ||
| 388 | + | An agent is a file (`.g1t/agents/<name>.md`, or in the workspace library): | |
| 389 | + | instructions, the harness and model to run, the tools and MCP servers it may | |
| 390 | + | use, its sandbox image, permissions and budget. Agents take roles: planner, | |
| 391 | + | implementer, reviewer, conflict resolver, documenter, memory consolidator. | |
| 392 | + | Each role has a default that a repo can replace. | |
| 393 | + | ||
| 394 | + | ### Where it runs, and on whose model | |
| 395 | + | ||
| 396 | + | | Option | How it works | Fits | | |
| 397 | + | | --- | --- | --- | | |
| 398 | + | | Hosted, g1t's model | g1t runs the sandbox and bills usage | Getting started; no keys to manage | | |
| 399 | + | | Hosted, your API key | Same sandbox, your Anthropic, OpenAI or Google key | Teams with existing contracts | | |
| 400 | + | | Hosted, your endpoint | Any OpenAI-compatible URL: Bedrock, Vertex, Azure, a self-hosted model | Private or fine-tuned models | | |
| 401 | + | | Your runner | A g1t runner daemon on your own machines picks up attempts | Code or models that may not leave your network | | |
| 402 | + | | Your own session | Local Claude Code, Cursor or any MCP client joins through `mcp.g1t.sh` | Individuals; subscription plans | | |
| 403 | + | ||
| 404 | + | Decisions behind this: | |
| 405 | + | ||
| 406 | + | - **g1t does not build its own agent loop.** It runs existing harnesses | |
| 407 | + | (Claude Code first, through its headless mode) behind a small runner | |
| 408 | + | contract: a container image, an entry command, and session events reported | |
| 409 | + | through the CLI. Other harnesses plug in by meeting the contract. | |
| 410 | + | - **All hosted model traffic goes through Cloudflare AI Gateway.** That gives | |
| 411 | + | one place for spend tracking, budgets, rate limits, fallback and logs, | |
| 412 | + | whichever provider or endpoint is behind it. | |
| 413 | + | - **Subscriptions stay local.** A Claude subscription cannot be used by a | |
| 414 | + | hosted sandbox; it needs an API key. People on subscriptions use their own | |
| 415 | + | Claude Code session, which is a full participant. | |
| 416 | + | - **Keys are secrets.** Stored in Cloudflare Secrets Store, injected into the | |
| 417 | + | sandbox for one attempt, never shown again. | |
| 418 | + | ||
| 419 | + | ### Choosing the right agent automatically | |
| 420 | + | ||
| 421 | + | Because several agents can race on the same intent, every arena is an | |
| 422 | + | evaluation on real work. g1t records, per repo and per kind of intent, each | |
| 423 | + | agent's win rate, cost and time. That produces a leaderboard, and a routing | |
| 424 | + | policy: send each new intent to the agent that wins that kind most often, | |
| 425 | + | start with the cheapest that is good enough, and escalate to a stronger one | |
| 426 | + | when checks fail. | |
| 427 | + | ||
| 428 | + | ## What GitHub ships today, and where g1t differs | |
| 429 | + | ||
| 430 | + | GitHub's Agent HQ and Copilot app give each agent session its own git | |
| 431 | + | worktree and branch, list sessions in a mission-control view grouped by | |
| 432 | + | project, and let a task be assigned to several agents so their output can be | |
| 433 | + | compared. Underneath, the unit of work is still a branch and a pull request. | |
| 434 | + | ||
| 435 | + | | | GitHub | g1t | | |
| 436 | + | | --- | --- | --- | | |
| 437 | + | | Where a session works | A worktree on one developer's machine, or a cloud sandbox | A server-side fork that any agent on any machine can join and anyone can open | | |
| 438 | + | | Agent context | Lives in the app's session view | Stored with the repository and linked from each commit (why-blame) | | |
| 439 | + | | Several agents on one task | Separate pull requests to compare by hand | One intent, one arena, ranked attempts | | |
| 440 | + | | Collisions between agents | Found as merge conflicts at the end | Flagged during the work (overlap radar) | | |
| 441 | + | | Landing changes | One pull request at a time | A merge queue that ships the winner and rebases or closes the rest | | |
| 442 | + | | Which agents | Those offered through a Copilot subscription | Any MCP client, plus hosted agents | | |
| 443 | + | ||
| 444 | + | ## How agents connect | |
| 445 | + | ||
| 446 | + | 1. **Bring your own agent.** A remote MCP server at `mcp.g1t.sh` lets Claude | |
| 447 | + | Code (or any MCP client) list intents, claim one, get a clone URL and | |
| 448 | + | token, report progress and submit. Adding it is one command; sign-in is a | |
| 449 | + | browser OAuth flow with no token to paste. The `g1t` CLI installs Claude | |
| 450 | + | Code hooks that upload the session transcript as the agent works. | |
| 451 | + | 2. **Hosted agents.** Press "Run 10 attempts" on an intent. g1t starts | |
| 452 | + | sandboxes (Cloudflare Sandbox SDK), each running a coding agent headless | |
| 453 | + | against its own fork. | |
| 454 | + | 3. **API and CLI.** Everything above is available at `api.g1t.sh` and | |
| 455 | + | through `g1t`. | |
| 456 | + | ||
| 457 | + | ## Public surfaces | |
| 458 | + | ||
| 459 | + | | Host | What it serves | | |
| 460 | + | | --- | --- | | |
| 461 | + | | `g1t.sh` | The site, git over HTTPS, git over SSH | | |
| 462 | + | | `api.g1t.sh` | Versioned REST API with a published OpenAPI document, cursor pagination, rate-limit headers, idempotency keys on writes, server-sent events for live attempt state, and signed webhooks | | |
| 463 | + | | `mcp.g1t.sh` | Remote MCP server over streamable HTTP | | |
| 464 | + | ||
| 465 | + | g1t is its own OAuth 2.1 authorization server: authorization code with PKCE, | |
| 466 | + | dynamic client registration, discovery metadata, refresh tokens, and scopes | |
| 467 | + | per resource (`repo:read`, `repo:write`, `intent:write`, `attempt:write`). | |
| 468 | + | MCP clients, the CLI (device flow) and third-party apps all use it. Access | |
| 469 | + | tokens and SSH keys remain for git itself. | |
| 470 | + | ||
| 471 | + | ## Architecture | |
| 472 | + | ||
| 473 | + | | Component | Language | Runs on | Responsibility | | |
| 474 | + | | --- | --- | --- | --- | | |
| 475 | + | | `packages/contracts` | TypeScript | — | The interface of every service, the event catalogue, shared types. Services and clients depend on this, never on each other's code. | | |
| 476 | + | | `services/identity` | TypeScript | Worker + D1 | Accounts, sessions, SSH keys, access tokens; later the OAuth server | | |
| 477 | + | | `services/repos` | TypeScript | Worker + D1 + Artifacts | Repository registry, contents, forks, git over HTTPS. Storage sits behind a `GitStore` port with an Artifacts adapter. | | |
| 478 | + | | `services/events` | TypeScript | Worker + Queues + D1 | The event bus: durable log, and one queue per subscribing service | | |
| 479 | + | | `services/work` | TypeScript | Worker + D1 | Intents, attempts, sessions; later a Durable Object per repo for the landing queue and live state | | |
| 480 | + | | `apps/web` | TypeScript | Worker | Server-rendered site. Holds no data; calls services over RPC. | | |
| 481 | + | | `apps/api` (next) | TypeScript | Worker | REST API (`api.g1t.sh`) and MCP server (`mcp.g1t.sh`) over the same services | | |
| 482 | + | | `crates/sshd` | Rust | Container | Git over SSH, bridged to Artifacts | | |
| 483 | + | | `crates/merged` | Rust | Container | Trial merges, conflict matrix, landing merges (needs real git; the Artifacts binding is read-only) | | |
| 484 | + | | `crates/core` | Rust | native and WASM | pkt-line, packfile and diff code shared by the above and by the Worker | | |
| 485 | + | | `crates/g1t` | Rust | user's machine | CLI: auth, SSH proxy, Claude Code hooks, intents and attempts | | |
| 486 | + | | runner image | — | Sandbox | Hosted agent environment | | |
| 487 | + | ||
| 488 | + | Storage: Artifacts for repositories (one fork per attempt), D1 for accounts | |
| 489 | + | and metadata, R2 for session transcripts and logs, Durable Object SQLite for | |
| 490 | + | per-repo coordination state. | |
| 491 | + | ||
| 492 | + | How the services fit together: | |
| 493 | + | ||
| 494 | + | - **Each service is its own Worker with its own database.** It deploys, | |
| 495 | + | scales and fails on its own. Callers reach it through a typed RPC binding | |
| 496 | + | to the interface in `packages/contracts`. | |
| 497 | + | - **Expected failures are values.** Every call returns a `Result`, so "not | |
| 498 | + | found" or "forbidden" crosses a service boundary as data. | |
| 499 | + | - **Side effects travel as events.** A service publishes what happened | |
| 500 | + | (`git.push`, `intent.opened`, `attempt.started`, …) to the bus and does not | |
| 501 | + | call other services to react. Each subscriber consumes from its own queue. | |
| 502 | + | Timelines, webhooks and automations read the same stream, which is what | |
| 503 | + | lets something like GitHub Actions be built on top. | |
| 504 | + | - **Every read takes the viewer.** Authorization is decided inside the | |
| 505 | + | service that owns the data, not by its callers. | |
| 506 | + | ||
| 507 | + | The Workers runtime scales request handling on its own, so the edge layer | |
| 508 | + | stays in TypeScript. Rust is used where there is real computation or a real | |
| 509 | + | protocol to implement. | |
| 510 | + | ||
| 511 | + | ## Accounts and forge basics | |
| 512 | + | ||
| 513 | + | - Registration with email verification, sign-in, forgot password (Cloudflare | |
| 514 | + | Email Sending), Turnstile on public forms. | |
| 515 | + | - GitHub sign-in, SSH keys, access tokens, active sessions. | |
| 516 | + | - Profiles, public and private repositories, repository search (D1 full-text). | |
| 517 | + | - Rendered README, syntax highlighting, commit history, diffs. | |
| 518 | + | ||
| 519 | + | ## Built on Cloudflare | |
| 520 | + | ||
| 521 | + | | Need | Product | | |
| 522 | + | | --- | --- | | |
| 523 | + | | Repositories; a fork per attempt; data residency per workspace | Artifacts (forks, jurisdictions) | | |
| 524 | + | | Reacting to pushes | Artifacts event subscriptions on Queues | | |
| 525 | + | | Preview URL per attempt; deploy on ship | Workers Builds and previews | | |
| 526 | + | | Site, API, MCP, git front end | Workers | | |
| 527 | + | | Per-repo coordination, live updates | Durable Objects | | |
| 528 | + | | Attempt lifecycles, automations | Workflows, Cron Triggers | | |
| 529 | + | | Agent sandboxes, SSH server, merge engine | Sandbox SDK and Containers | | |
| 530 | + | | Fast starts on large repos | ArtifactFS | | |
| 531 | + | | Model traffic, spend, budgets | AI Gateway | | |
| 532 | + | | Summaries, embeddings | Workers AI | | |
| 533 | + | | Context hub search | Vectorize | | |
| 534 | + | | Accounts and metadata | D1 | | |
| 535 | + | | Transcripts and logs | R2 | | |
| 536 | + | | Email, bot protection, keys | Email Sending, Turnstile, Secrets Store | | |
| 537 | + | ||
| 538 | + | ## The submission | |
| 539 | + | ||
| 540 | + | - **g1t is built on g1t.** This repository is hosted on g1t.sh, its features | |
| 541 | + | are opened as intents and built by racing agents, and it deploys from | |
| 542 | + | Artifacts through Workers Builds. The history is the proof. | |
| 543 | + | - **The demo follows one story.** A brief becomes a project; twelve intents | |
| 544 | + | fan out to dozens of agents; agents notice each other, hand off, and | |
| 545 | + | resolve a conflict; reviewers triage; the queue lands everything on | |
| 546 | + | `main`; why-blame explains a line; the portfolio shows where it all | |
| 547 | + | stands. Then the same thing at a thousand agents. | |
| 548 | + | - **Judges can try it in a minute.** Open registration on g1t.sh, one-click | |
| 549 | + | import of a GitHub repo, one command to connect Claude Code, a seeded demo | |
| 550 | + | workspace, and a single deploy command for running their own copy. | |
| 551 | + | - **The formats are open.** The commit trailers, session format and runner | |
| 552 | + | contract are published so other tools can interoperate. | |
| 553 | + | ||
| 554 | + | ## Build order | |
| 555 | + | ||
| 556 | + | Done: site on g1t.sh; git over HTTPS; accounts and private repos; the four | |
| 557 | + | services and the event bus; intents, attempts (a fork each) and session | |
| 558 | + | storage, with pages for each. SSH server written, not deployed. | |
| 559 | + | ||
| 560 | + | 1. `api.g1t.sh` and `mcp.g1t.sh`; CLI with Claude Code hooks, so an outside | |
| 561 | + | agent can claim an intent, push, and record its session. | |
| 562 | + | 2. OAuth server. | |
| 563 | + | 3. SSH deployed; registration, email verification, forgot password; search; | |
| 564 | + | GitHub import; this repo hosted on g1t. | |
| 565 | + | 4. Hosted agents in sandboxes behind the runner contract; agent | |
| 566 | + | definitions; AI Gateway; push events; checks. | |
| 567 | + | 5. Merge engine with structural merge; landing queue with speculative | |
| 568 | + | checks; projected main; resolve-on-move. | |
| 569 | + | 6. Arena, diffs, proof bundles, reviewer and adversarial reviewer, risk | |
| 570 | + | tiers; work registry with overlap radar, | |
| 571 | + | duplicate detection, handoff, ask and wait. | |
| 572 | + | 7. Adopting outside pushes; protected `main`; approval rules. | |
| 573 | + | 8. Projects with briefs, planner and dependency graph; mission control with | |
| 574 | + | the "needs you" inbox; live session pages and steering. | |
| 575 | + | 9. Why-blame, signed provenance, forkable sessions, digest and timeline, | |
| 576 | + | landing page. | |
| 577 | + | 10. Workspaces and initiatives; context hub (memory, unified search, | |
| 578 | + | Jira and Notion connectors); multi-repo intents. | |
| 579 | + | 11. Portfolio, standup and ask; checkpoints, stall detection, budgets. | |
| 580 | + | 12. Moving sessions between local and hosted; installable web app with | |
| 581 | + | notifications; preview URLs per attempt. | |
| 582 | + | 13. Docs view, document intents, templates, explain, living documentation, | |
| 583 | + | roles. | |
| 584 | + | 14. Automations: event bus, triggers, Sentry and generic webhook | |
| 585 | + | integrations, write-back. | |
| 586 | + | 15. Own keys, own endpoints, self-hosted runners; agent leaderboard and | |
| 587 | + | routing. | |
| 588 | + | 16. Large run (100+ agents across many intents), hardening, README, demo. | |
| 589 | + | ||
| 590 | + | Later: code search, mirroring to GitHub, passkeys, SSH | |
| 591 | + | on port 22 without the CLI proxy (needs the Workers inbound TCP private | |
| 592 | + | beta). |
| 1 | + | { | |
| 2 | + | "name": "g1t-monorepo", | |
| 3 | + | "lockfileVersion": 3, | |
| 4 | + | "requires": true, | |
| 5 | + | "packages": { | |
| 6 | + | "": { | |
| 7 | + | "name": "g1t-monorepo", | |
| 8 | + | "workspaces": [ | |
| 9 | + | "apps/*", | |
| 10 | + | "services/*", | |
| 11 | + | "packages/*" | |
| 12 | + | ], | |
| 13 | + | "devDependencies": { | |
| 14 | + | "typescript": "^5.9.3", | |
| 15 | + | "wrangler": "^4.146.0" | |
| 16 | + | } | |
| 17 | + | }, | |
| 18 | + | "apps/web": { | |
| 19 | + | "name": "@g1t/web", | |
| 20 | + | "hasInstallScript": true, | |
| 21 | + | "license": "MIT", | |
| 22 | + | "dependencies": { | |
| 23 | + | "@g1t/contracts": "*", | |
| 24 | + | "isbot": "^5.1.36", | |
| 25 | + | "react": "^19.2.8", | |
| 26 | + | "react-dom": "^19.2.8", | |
| 27 | + | "react-router": "^8.4.0" | |
| 28 | + | }, | |
| 29 | + | "devDependencies": { | |
| 30 | + | "@cloudflare/vite-plugin": "^1.62.4", | |
| 31 | + | "@react-router/dev": "^8.4.0", | |
| 32 | + | "@tailwindcss/vite": "^4.2.2", | |
| 33 | + | "@types/node": "^22.20.5", | |
| 34 | + | "@types/react": "^19.2.18", | |
| 35 | + | "@types/react-dom": "^19.2.7", | |
| 36 | + | "tailwindcss": "^4.2.2", | |
| 37 | + | "typescript": "^5.9.3", | |
| 38 | + | "vite": "^8.0.3", | |
| 39 | + | "wrangler": "^4.146.0" | |
| 40 | + | } | |
| 41 | + | }, | |
| 42 | + | "node_modules/@babel/code-frame": { | |
| 43 | + | "version": "7.29.7", | |
| 44 | + | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", | |
| 45 | + | "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", | |
| 46 | + | "dev": true, | |
| 47 | + | "license": "MIT", | |
| 48 | + | "dependencies": { | |
| 49 | + | "@babel/helper-validator-identifier": "^7.29.7", | |
| 50 | + | "js-tokens": "^4.0.0", | |
| 51 | + | "picocolors": "^1.1.1" | |
| 52 | + | }, | |
| 53 | + | "engines": { | |
| 54 | + | "node": ">=6.9.0" | |
| 55 | + | } | |
| 56 | + | }, | |
| 57 | + | "node_modules/@babel/compat-data": { | |
| 58 | + | "version": "7.29.7", | |
| 59 | + | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", | |
| 60 | + | "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", | |
| 61 | + | "dev": true, | |
| 62 | + | "license": "MIT", | |
| 63 | + | "engines": { | |
| 64 | + | "node": ">=6.9.0" | |
| 65 | + | } | |
| 66 | + | }, | |
| 67 | + | "node_modules/@babel/core": { | |
| 68 | + | "version": "7.29.7", | |
| 69 | + | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", | |
| 70 | + | "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", | |
| 71 | + | "dev": true, | |
| 72 | + | "license": "MIT", | |
| 73 | + | "dependencies": { | |
| 74 | + | "@babel/code-frame": "^7.29.7", | |
| 75 | + | "@babel/generator": "^7.29.7", | |
| 76 | + | "@babel/helper-compilation-targets": "^7.29.7", | |
| 77 | + | "@babel/helper-module-transforms": "^7.29.7", | |
| 78 | + | "@babel/helpers": "^7.29.7", | |
| 79 | + | "@babel/parser": "^7.29.7", | |
| 80 | + | "@babel/template": "^7.29.7", | |
| 81 | + | "@babel/traverse": "^7.29.7", | |
| 82 | + | "@babel/types": "^7.29.7", | |
| 83 | + | "@jridgewell/remapping": "^2.3.5", | |
| 84 | + | "convert-source-map": "^2.0.0", | |
| 85 | + | "debug": "^4.1.0", | |
| 86 | + | "gensync": "^1.0.0-beta.2", | |
| 87 | + | "json5": "^2.2.3", | |
| 88 | + | "semver": "^6.3.1" | |
| 89 | + | }, | |
| 90 | + | "engines": { | |
| 91 | + | "node": ">=6.9.0" | |
| 92 | + | }, | |
| 93 | + | "funding": { | |
| 94 | + | "type": "opencollective", | |
| 95 | + | "url": "https://opencollective.com/babel" | |
| 96 | + | } | |
| 97 | + | }, | |
| 98 | + | "node_modules/@babel/core/node_modules/semver": { | |
| 99 | + | "version": "6.3.1", | |
| 100 | + | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", | |
| 101 | + | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", | |
| 102 | + | "dev": true, | |
| 103 | + | "license": "ISC", | |
| 104 | + | "bin": { | |
| 105 | + | "semver": "bin/semver.js" | |
| 106 | + | } | |
| 107 | + | }, | |
| 108 | + | "node_modules/@babel/generator": { | |
| 109 | + | "version": "7.29.8", | |
| 110 | + | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", | |
| 111 | + | "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", | |
| 112 | + | "dev": true, | |
| 113 | + | "license": "MIT", | |
| 114 | + | "dependencies": { | |
| 115 | + | "@babel/parser": "^7.29.8", | |
| 116 | + | "@babel/types": "^7.29.8", | |
| 117 | + | "@jridgewell/gen-mapping": "^0.3.12", | |
| 118 | + | "@jridgewell/trace-mapping": "^0.3.28", | |
| 119 | + | "jsesc": "^3.0.2" | |
| 120 | + | }, | |
| 121 | + | "engines": { | |
| 122 | + | "node": ">=6.9.0" | |
| 123 | + | } | |
| 124 | + | }, | |
| 125 | + | "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { | |
| 126 | + | "version": "0.3.31", | |
| 127 | + | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 128 | + | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 129 | + | "dev": true, | |
| 130 | + | "license": "MIT", | |
| 131 | + | "dependencies": { | |
| 132 | + | "@jridgewell/resolve-uri": "^3.1.0", | |
| 133 | + | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 134 | + | } | |
| 135 | + | }, | |
| 136 | + | "node_modules/@babel/helper-compilation-targets": { | |
| 137 | + | "version": "7.29.7", | |
| 138 | + | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", | |
| 139 | + | "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", | |
| 140 | + | "dev": true, | |
| 141 | + | "license": "MIT", | |
| 142 | + | "dependencies": { | |
| 143 | + | "@babel/compat-data": "^7.29.7", | |
| 144 | + | "@babel/helper-validator-option": "^7.29.7", | |
| 145 | + | "browserslist": "^4.24.0", | |
| 146 | + | "lru-cache": "^5.1.1", | |
| 147 | + | "semver": "^6.3.1" | |
| 148 | + | }, | |
| 149 | + | "engines": { | |
| 150 | + | "node": ">=6.9.0" | |
| 151 | + | } | |
| 152 | + | }, | |
| 153 | + | "node_modules/@babel/helper-compilation-targets/node_modules/semver": { | |
| 154 | + | "version": "6.3.1", | |
| 155 | + | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", | |
| 156 | + | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", | |
| 157 | + | "dev": true, | |
| 158 | + | "license": "ISC", | |
| 159 | + | "bin": { | |
| 160 | + | "semver": "bin/semver.js" | |
| 161 | + | } | |
| 162 | + | }, | |
| 163 | + | "node_modules/@babel/helper-globals": { | |
| 164 | + | "version": "7.29.7", | |
| 165 | + | "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", | |
| 166 | + | "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", | |
| 167 | + | "dev": true, | |
| 168 | + | "license": "MIT", | |
| 169 | + | "engines": { | |
| 170 | + | "node": ">=6.9.0" | |
| 171 | + | } | |
| 172 | + | }, | |
| 173 | + | "node_modules/@babel/helper-module-imports": { | |
| 174 | + | "version": "7.29.7", | |
| 175 | + | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", | |
| 176 | + | "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", | |
| 177 | + | "dev": true, | |
| 178 | + | "license": "MIT", | |
| 179 | + | "dependencies": { | |
| 180 | + | "@babel/traverse": "^7.29.7", | |
| 181 | + | "@babel/types": "^7.29.7" | |
| 182 | + | }, | |
| 183 | + | "engines": { | |
| 184 | + | "node": ">=6.9.0" | |
| 185 | + | } | |
| 186 | + | }, | |
| 187 | + | "node_modules/@babel/helper-module-transforms": { | |
| 188 | + | "version": "7.29.7", | |
| 189 | + | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", | |
| 190 | + | "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", | |
| 191 | + | "dev": true, | |
| 192 | + | "license": "MIT", | |
| 193 | + | "dependencies": { | |
| 194 | + | "@babel/helper-module-imports": "^7.29.7", | |
| 195 | + | "@babel/helper-validator-identifier": "^7.29.7", | |
| 196 | + | "@babel/traverse": "^7.29.7" | |
| 197 | + | }, | |
| 198 | + | "engines": { | |
| 199 | + | "node": ">=6.9.0" | |
| 200 | + | }, | |
| 201 | + | "peerDependencies": { | |
| 202 | + | "@babel/core": "^7.0.0" | |
| 203 | + | } | |
| 204 | + | }, | |
| 205 | + | "node_modules/@babel/helper-string-parser": { | |
| 206 | + | "version": "7.29.7", | |
| 207 | + | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", | |
| 208 | + | "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", | |
| 209 | + | "dev": true, | |
| 210 | + | "license": "MIT", | |
| 211 | + | "engines": { | |
| 212 | + | "node": ">=6.9.0" | |
| 213 | + | } | |
| 214 | + | }, | |
| 215 | + | "node_modules/@babel/helper-validator-identifier": { | |
| 216 | + | "version": "7.29.7", | |
| 217 | + | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", | |
| 218 | + | "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", | |
| 219 | + | "dev": true, | |
| 220 | + | "license": "MIT", | |
| 221 | + | "engines": { | |
| 222 | + | "node": ">=6.9.0" | |
| 223 | + | } | |
| 224 | + | }, | |
| 225 | + | "node_modules/@babel/helper-validator-option": { | |
| 226 | + | "version": "7.29.7", | |
| 227 | + | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", | |
| 228 | + | "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", | |
| 229 | + | "dev": true, | |
| 230 | + | "license": "MIT", | |
| 231 | + | "engines": { | |
| 232 | + | "node": ">=6.9.0" | |
| 233 | + | } | |
| 234 | + | }, | |
| 235 | + | "node_modules/@babel/helpers": { | |
| 236 | + | "version": "7.29.7", | |
| 237 | + | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", | |
| 238 | + | "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", | |
| 239 | + | "dev": true, | |
| 240 | + | "license": "MIT", | |
| 241 | + | "dependencies": { | |
| 242 | + | "@babel/template": "^7.29.7", | |
| 243 | + | "@babel/types": "^7.29.7" | |
| 244 | + | }, | |
| 245 | + | "engines": { | |
| 246 | + | "node": ">=6.9.0" | |
| 247 | + | } | |
| 248 | + | }, | |
| 249 | + | "node_modules/@babel/parser": { | |
| 250 | + | "version": "7.29.9", | |
| 251 | + | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", | |
| 252 | + | "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", | |
| 253 | + | "dev": true, | |
| 254 | + | "license": "MIT", | |
| 255 | + | "dependencies": { | |
| 256 | + | "@babel/types": "^7.29.8" | |
| 257 | + | }, | |
| 258 | + | "bin": { | |
| 259 | + | "parser": "bin/babel-parser.js" | |
| 260 | + | }, | |
| 261 | + | "engines": { | |
| 262 | + | "node": ">=6.0.0" | |
| 263 | + | } | |
| 264 | + | }, | |
| 265 | + | "node_modules/@babel/template": { | |
| 266 | + | "version": "7.29.7", | |
| 267 | + | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", | |
| 268 | + | "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", | |
| 269 | + | "dev": true, | |
| 270 | + | "license": "MIT", | |
| 271 | + | "dependencies": { | |
| 272 | + | "@babel/code-frame": "^7.29.7", | |
| 273 | + | "@babel/parser": "^7.29.7", | |
| 274 | + | "@babel/types": "^7.29.7" | |
| 275 | + | }, | |
| 276 | + | "engines": { | |
| 277 | + | "node": ">=6.9.0" | |
| 278 | + | } | |
| 279 | + | }, | |
| 280 | + | "node_modules/@babel/traverse": { | |
| 281 | + | "version": "7.29.8", | |
| 282 | + | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", | |
| 283 | + | "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", | |
| 284 | + | "dev": true, | |
| 285 | + | "license": "MIT", | |
| 286 | + | "dependencies": { | |
| 287 | + | "@babel/code-frame": "^7.29.7", | |
| 288 | + | "@babel/generator": "^7.29.8", | |
| 289 | + | "@babel/helper-globals": "^7.29.7", | |
| 290 | + | "@babel/parser": "^7.29.8", | |
| 291 | + | "@babel/template": "^7.29.7", | |
| 292 | + | "@babel/types": "^7.29.8", | |
| 293 | + | "debug": "^4.3.1" | |
| 294 | + | }, | |
| 295 | + | "engines": { | |
| 296 | + | "node": ">=6.9.0" | |
| 297 | + | } | |
| 298 | + | }, | |
| 299 | + | "node_modules/@babel/types": { | |
| 300 | + | "version": "7.29.8", | |
| 301 | + | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", | |
| 302 | + | "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", | |
| 303 | + | "dev": true, | |
| 304 | + | "license": "MIT", | |
| 305 | + | "dependencies": { | |
| 306 | + | "@babel/helper-string-parser": "^7.29.7", | |
| 307 | + | "@babel/helper-validator-identifier": "^7.29.7" | |
| 308 | + | }, | |
| 309 | + | "engines": { | |
| 310 | + | "node": ">=6.9.0" | |
| 311 | + | } | |
| 312 | + | }, | |
| 313 | + | "node_modules/@cloudflare/kv-asset-handler": { | |
| 314 | + | "version": "0.5.0", | |
| 315 | + | "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", | |
| 316 | + | "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", | |
| 317 | + | "dev": true, | |
| 318 | + | "license": "MIT OR Apache-2.0", | |
| 319 | + | "engines": { | |
| 320 | + | "node": ">=22.0.0" | |
| 321 | + | } | |
| 322 | + | }, | |
| 323 | + | "node_modules/@cloudflare/unenv-preset": { | |
| 324 | + | "version": "2.16.2", | |
| 325 | + | "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.2.tgz", | |
| 326 | + | "integrity": "sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==", | |
| 327 | + | "dev": true, | |
| 328 | + | "license": "MIT OR Apache-2.0", | |
| 329 | + | "peerDependencies": { | |
| 330 | + | "unenv": "2.0.0-rc.24", | |
| 331 | + | "workerd": ">1.20260305.0 <2.0.0-0" | |
| 332 | + | }, | |
| 333 | + | "peerDependenciesMeta": { | |
| 334 | + | "workerd": { | |
| 335 | + | "optional": true | |
| 336 | + | } | |
| 337 | + | } | |
| 338 | + | }, | |
| 339 | + | "node_modules/@cloudflare/vite-plugin": { | |
| 340 | + | "version": "1.62.4", | |
| 341 | + | "resolved": "https://registry.npmjs.org/@cloudflare/vite-plugin/-/vite-plugin-1.62.4.tgz", | |
| 342 | + | "integrity": "sha512-6mJj8UwAiNNra8j3rC/uYwe8aYa2/tudFe2hbvY+WQ8RjONzNryVAoj7PkMgLPZKxxhShXwYdT4tCSxTB2CWow==", | |
| 343 | + | "dev": true, | |
| 344 | + | "license": "MIT", | |
| 345 | + | "dependencies": { | |
| 346 | + | "@cloudflare/unenv-preset": "2.16.2", | |
| 347 | + | "miniflare": "5.20261001.0-alpha", | |
| 348 | + | "unenv": "2.0.0-rc.24", | |
| 349 | + | "workerd": "1.20261001.1", | |
| 350 | + | "wrangler": "4.146.0", | |
| 351 | + | "ws": "8.21.0" | |
| 352 | + | }, | |
| 353 | + | "bin": { | |
| 354 | + | "cf-vite": "bin/cf-vite" | |
| 355 | + | }, | |
| 356 | + | "peerDependencies": { | |
| 357 | + | "vite": "^6.1.0 || ^7.0.0 || ^8.0.0", | |
| 358 | + | "wrangler": "^4.146.0" | |
| 359 | + | } | |
| 360 | + | }, | |
| 361 | + | "node_modules/@cloudflare/workerd-darwin-64": { | |
| 362 | + | "version": "1.20261001.1", | |
| 363 | + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261001.1.tgz", | |
| 364 | + | "integrity": "sha512-4cgSgDf28JSw/P5Dj5GCS59hzVqS5XnmGAWNkvYHLI6ODU9idGaMMNEuhJXDkEG/lsABmlVlnCgsdh2VbKWepw==", | |
| 365 | + | "cpu": [ | |
| 366 | + | "x64" | |
| 367 | + | ], | |
| 368 | + | "dev": true, | |
| 369 | + | "license": "Apache-2.0", | |
| 370 | + | "optional": true, | |
| 371 | + | "os": [ | |
| 372 | + | "darwin" | |
| 373 | + | ], | |
| 374 | + | "engines": { | |
| 375 | + | "node": ">=16" | |
| 376 | + | } | |
| 377 | + | }, | |
| 378 | + | "node_modules/@cloudflare/workerd-darwin-arm64": { | |
| 379 | + | "version": "1.20261001.1", | |
| 380 | + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261001.1.tgz", | |
| 381 | + | "integrity": "sha512-8ulAWruEVouNmEIsQsy9WSSCS9zkLu93W2MTwp5esiFyoPp05BNSVFIFsYSPi1pkFmBBd7fsnwMpbLkaJLaVPQ==", | |
| 382 | + | "cpu": [ | |
| 383 | + | "arm64" | |
| 384 | + | ], | |
| 385 | + | "dev": true, | |
| 386 | + | "license": "Apache-2.0", | |
| 387 | + | "optional": true, | |
| 388 | + | "os": [ | |
| 389 | + | "darwin" | |
| 390 | + | ], | |
| 391 | + | "engines": { | |
| 392 | + | "node": ">=16" | |
| 393 | + | } | |
| 394 | + | }, | |
| 395 | + | "node_modules/@cloudflare/workerd-linux-64": { | |
| 396 | + | "version": "1.20261001.1", | |
| 397 | + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261001.1.tgz", | |
| 398 | + | "integrity": "sha512-kZbTZJGrhsMOdqZ2BIybjaBRLZjYsRLWj7mcNw/6Y3hodOhp5MrNzcz4iWGwNVWwyIV+7Pl+/LX5VcgnRqdHOg==", | |
| 399 | + | "cpu": [ | |
| 400 | + | "x64" | |
| 401 | + | ], | |
| 402 | + | "dev": true, | |
| 403 | + | "license": "Apache-2.0", | |
| 404 | + | "optional": true, | |
| 405 | + | "os": [ | |
| 406 | + | "linux" | |
| 407 | + | ], | |
| 408 | + | "engines": { | |
| 409 | + | "node": ">=16" | |
| 410 | + | } | |
| 411 | + | }, | |
| 412 | + | "node_modules/@cloudflare/workerd-linux-arm64": { | |
| 413 | + | "version": "1.20261001.1", | |
| 414 | + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261001.1.tgz", | |
| 415 | + | "integrity": "sha512-oOk3Zj6k/8oP0FJgZBWDn7+BqbsqIMEMaV95pulHPVbwVu4wYoLfQq2hp0vkbCNsCFLqZb7cqixXdrwD54ZIow==", | |
| 416 | + | "cpu": [ | |
| 417 | + | "arm64" | |
| 418 | + | ], | |
| 419 | + | "dev": true, | |
| 420 | + | "license": "Apache-2.0", | |
| 421 | + | "optional": true, | |
| 422 | + | "os": [ | |
| 423 | + | "linux" | |
| 424 | + | ], | |
| 425 | + | "engines": { | |
| 426 | + | "node": ">=16" | |
| 427 | + | } | |
| 428 | + | }, | |
| 429 | + | "node_modules/@cloudflare/workerd-windows-64": { | |
| 430 | + | "version": "1.20261001.1", | |
| 431 | + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261001.1.tgz", | |
| 432 | + | "integrity": "sha512-uRxm5W4VyBkoSaoP1BfOuH0873tE+vxsknF4sab6/5YIRvIAufChq3QDp+zlAn67PuGPGcn7W8QraA0t4ucmOQ==", | |
| 433 | + | "cpu": [ | |
| 434 | + | "x64" | |
| 435 | + | ], | |
| 436 | + | "dev": true, | |
| 437 | + | "license": "Apache-2.0", | |
| 438 | + | "optional": true, | |
| 439 | + | "os": [ | |
| 440 | + | "win32" | |
| 441 | + | ], | |
| 442 | + | "engines": { | |
| 443 | + | "node": ">=16" | |
| 444 | + | } | |
| 445 | + | }, | |
| 446 | + | "node_modules/@cspotcode/source-map-support": { | |
| 447 | + | "version": "0.8.1", | |
| 448 | + | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", | |
| 449 | + | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", | |
| 450 | + | "dev": true, | |
| 451 | + | "license": "MIT", | |
| 452 | + | "dependencies": { | |
| 453 | + | "@jridgewell/trace-mapping": "0.3.9" | |
| 454 | + | }, | |
| 455 | + | "engines": { | |
| 456 | + | "node": ">=12" | |
| 457 | + | } | |
| 458 | + | }, | |
| 459 | + | "node_modules/@emnapi/runtime": { | |
| 460 | + | "version": "1.11.3", | |
| 461 | + | "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", | |
| 462 | + | "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", | |
| 463 | + | "dev": true, | |
| 464 | + | "license": "MIT", | |
| 465 | + | "optional": true, | |
| 466 | + | "dependencies": { | |
| 467 | + | "tslib": "^2.4.0" | |
| 468 | + | } | |
| 469 | + | }, | |
| 470 | + | "node_modules/@esbuild/aix-ppc64": { | |
| 471 | + | "version": "0.28.1", | |
| 472 | + | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", | |
| 473 | + | "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", | |
| 474 | + | "cpu": [ | |
| 475 | + | "ppc64" | |
| 476 | + | ], | |
| 477 | + | "dev": true, | |
| 478 | + | "license": "MIT", | |
| 479 | + | "optional": true, | |
| 480 | + | "os": [ | |
| 481 | + | "aix" | |
| 482 | + | ], | |
| 483 | + | "engines": { | |
| 484 | + | "node": ">=18" | |
| 485 | + | } | |
| 486 | + | }, | |
| 487 | + | "node_modules/@esbuild/android-arm": { | |
| 488 | + | "version": "0.28.1", | |
| 489 | + | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", | |
| 490 | + | "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", | |
| 491 | + | "cpu": [ | |
| 492 | + | "arm" | |
| 493 | + | ], | |
| 494 | + | "dev": true, | |
| 495 | + | "license": "MIT", | |
| 496 | + | "optional": true, | |
| 497 | + | "os": [ | |
| 498 | + | "android" | |
| 499 | + | ], | |
| 500 | + | "engines": { | |
| 501 | + | "node": ">=18" | |
| 502 | + | } | |
| 503 | + | }, | |
| 504 | + | "node_modules/@esbuild/android-arm64": { | |
| 505 | + | "version": "0.28.1", | |
| 506 | + | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", | |
| 507 | + | "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", | |
| 508 | + | "cpu": [ | |
| 509 | + | "arm64" | |
| 510 | + | ], | |
| 511 | + | "dev": true, | |
| 512 | + | "license": "MIT", | |
| 513 | + | "optional": true, | |
| 514 | + | "os": [ | |
| 515 | + | "android" | |
| 516 | + | ], | |
| 517 | + | "engines": { | |
| 518 | + | "node": ">=18" | |
| 519 | + | } | |
| 520 | + | }, | |
| 521 | + | "node_modules/@esbuild/android-x64": { | |
| 522 | + | "version": "0.28.1", | |
| 523 | + | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", | |
| 524 | + | "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", | |
| 525 | + | "cpu": [ | |
| 526 | + | "x64" | |
| 527 | + | ], | |
| 528 | + | "dev": true, | |
| 529 | + | "license": "MIT", | |
| 530 | + | "optional": true, | |
| 531 | + | "os": [ | |
| 532 | + | "android" | |
| 533 | + | ], | |
| 534 | + | "engines": { | |
| 535 | + | "node": ">=18" | |
| 536 | + | } | |
| 537 | + | }, | |
| 538 | + | "node_modules/@esbuild/darwin-arm64": { | |
| 539 | + | "version": "0.28.1", | |
| 540 | + | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", | |
| 541 | + | "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", | |
| 542 | + | "cpu": [ | |
| 543 | + | "arm64" | |
| 544 | + | ], | |
| 545 | + | "dev": true, | |
| 546 | + | "license": "MIT", | |
| 547 | + | "optional": true, | |
| 548 | + | "os": [ | |
| 549 | + | "darwin" | |
| 550 | + | ], | |
| 551 | + | "engines": { | |
| 552 | + | "node": ">=18" | |
| 553 | + | } | |
| 554 | + | }, | |
| 555 | + | "node_modules/@esbuild/darwin-x64": { | |
| 556 | + | "version": "0.28.1", | |
| 557 | + | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", | |
| 558 | + | "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", | |
| 559 | + | "cpu": [ | |
| 560 | + | "x64" | |
| 561 | + | ], | |
| 562 | + | "dev": true, | |
| 563 | + | "license": "MIT", | |
| 564 | + | "optional": true, | |
| 565 | + | "os": [ | |
| 566 | + | "darwin" | |
| 567 | + | ], | |
| 568 | + | "engines": { | |
| 569 | + | "node": ">=18" | |
| 570 | + | } | |
| 571 | + | }, | |
| 572 | + | "node_modules/@esbuild/freebsd-arm64": { | |
| 573 | + | "version": "0.28.1", | |
| 574 | + | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", | |
| 575 | + | "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", | |
| 576 | + | "cpu": [ | |
| 577 | + | "arm64" | |
| 578 | + | ], | |
| 579 | + | "dev": true, | |
| 580 | + | "license": "MIT", | |
| 581 | + | "optional": true, | |
| 582 | + | "os": [ | |
| 583 | + | "freebsd" | |
| 584 | + | ], | |
| 585 | + | "engines": { | |
| 586 | + | "node": ">=18" | |
| 587 | + | } | |
| 588 | + | }, | |
| 589 | + | "node_modules/@esbuild/freebsd-x64": { | |
| 590 | + | "version": "0.28.1", | |
| 591 | + | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", | |
| 592 | + | "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", | |
| 593 | + | "cpu": [ | |
| 594 | + | "x64" | |
| 595 | + | ], | |
| 596 | + | "dev": true, | |
| 597 | + | "license": "MIT", | |
| 598 | + | "optional": true, | |
| 599 | + | "os": [ | |
| 600 | + | "freebsd" | |
| 601 | + | ], | |
| 602 | + | "engines": { | |
| 603 | + | "node": ">=18" | |
| 604 | + | } | |
| 605 | + | }, | |
| 606 | + | "node_modules/@esbuild/linux-arm": { | |
| 607 | + | "version": "0.28.1", | |
| 608 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", | |
| 609 | + | "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", | |
| 610 | + | "cpu": [ | |
| 611 | + | "arm" | |
| 612 | + | ], | |
| 613 | + | "dev": true, | |
| 614 | + | "license": "MIT", | |
| 615 | + | "optional": true, | |
| 616 | + | "os": [ | |
| 617 | + | "linux" | |
| 618 | + | ], | |
| 619 | + | "engines": { | |
| 620 | + | "node": ">=18" | |
| 621 | + | } | |
| 622 | + | }, | |
| 623 | + | "node_modules/@esbuild/linux-arm64": { | |
| 624 | + | "version": "0.28.1", | |
| 625 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", | |
| 626 | + | "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", | |
| 627 | + | "cpu": [ | |
| 628 | + | "arm64" | |
| 629 | + | ], | |
| 630 | + | "dev": true, | |
| 631 | + | "license": "MIT", | |
| 632 | + | "optional": true, | |
| 633 | + | "os": [ | |
| 634 | + | "linux" | |
| 635 | + | ], | |
| 636 | + | "engines": { | |
| 637 | + | "node": ">=18" | |
| 638 | + | } | |
| 639 | + | }, | |
| 640 | + | "node_modules/@esbuild/linux-ia32": { | |
| 641 | + | "version": "0.28.1", | |
| 642 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", | |
| 643 | + | "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", | |
| 644 | + | "cpu": [ | |
| 645 | + | "ia32" | |
| 646 | + | ], | |
| 647 | + | "dev": true, | |
| 648 | + | "license": "MIT", | |
| 649 | + | "optional": true, | |
| 650 | + | "os": [ | |
| 651 | + | "linux" | |
| 652 | + | ], | |
| 653 | + | "engines": { | |
| 654 | + | "node": ">=18" | |
| 655 | + | } | |
| 656 | + | }, | |
| 657 | + | "node_modules/@esbuild/linux-loong64": { | |
| 658 | + | "version": "0.28.1", | |
| 659 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", | |
| 660 | + | "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", | |
| 661 | + | "cpu": [ | |
| 662 | + | "loong64" | |
| 663 | + | ], | |
| 664 | + | "dev": true, | |
| 665 | + | "license": "MIT", | |
| 666 | + | "optional": true, | |
| 667 | + | "os": [ | |
| 668 | + | "linux" | |
| 669 | + | ], | |
| 670 | + | "engines": { | |
| 671 | + | "node": ">=18" | |
| 672 | + | } | |
| 673 | + | }, | |
| 674 | + | "node_modules/@esbuild/linux-mips64el": { | |
| 675 | + | "version": "0.28.1", | |
| 676 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", | |
| 677 | + | "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", | |
| 678 | + | "cpu": [ | |
| 679 | + | "mips64el" | |
| 680 | + | ], | |
| 681 | + | "dev": true, | |
| 682 | + | "license": "MIT", | |
| 683 | + | "optional": true, | |
| 684 | + | "os": [ | |
| 685 | + | "linux" | |
| 686 | + | ], | |
| 687 | + | "engines": { | |
| 688 | + | "node": ">=18" | |
| 689 | + | } | |
| 690 | + | }, | |
| 691 | + | "node_modules/@esbuild/linux-ppc64": { | |
| 692 | + | "version": "0.28.1", | |
| 693 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", | |
| 694 | + | "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", | |
| 695 | + | "cpu": [ | |
| 696 | + | "ppc64" | |
| 697 | + | ], | |
| 698 | + | "dev": true, | |
| 699 | + | "license": "MIT", | |
| 700 | + | "optional": true, | |
| 701 | + | "os": [ | |
| 702 | + | "linux" | |
| 703 | + | ], | |
| 704 | + | "engines": { | |
| 705 | + | "node": ">=18" | |
| 706 | + | } | |
| 707 | + | }, | |
| 708 | + | "node_modules/@esbuild/linux-riscv64": { | |
| 709 | + | "version": "0.28.1", | |
| 710 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", | |
| 711 | + | "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", | |
| 712 | + | "cpu": [ | |
| 713 | + | "riscv64" | |
| 714 | + | ], | |
| 715 | + | "dev": true, | |
| 716 | + | "license": "MIT", | |
| 717 | + | "optional": true, | |
| 718 | + | "os": [ | |
| 719 | + | "linux" | |
| 720 | + | ], | |
| 721 | + | "engines": { | |
| 722 | + | "node": ">=18" | |
| 723 | + | } | |
| 724 | + | }, | |
| 725 | + | "node_modules/@esbuild/linux-s390x": { | |
| 726 | + | "version": "0.28.1", | |
| 727 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", | |
| 728 | + | "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", | |
| 729 | + | "cpu": [ | |
| 730 | + | "s390x" | |
| 731 | + | ], | |
| 732 | + | "dev": true, | |
| 733 | + | "license": "MIT", | |
| 734 | + | "optional": true, | |
| 735 | + | "os": [ | |
| 736 | + | "linux" | |
| 737 | + | ], | |
| 738 | + | "engines": { | |
| 739 | + | "node": ">=18" | |
| 740 | + | } | |
| 741 | + | }, | |
| 742 | + | "node_modules/@esbuild/linux-x64": { | |
| 743 | + | "version": "0.28.1", | |
| 744 | + | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", | |
| 745 | + | "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", | |
| 746 | + | "cpu": [ | |
| 747 | + | "x64" | |
| 748 | + | ], | |
| 749 | + | "dev": true, | |
| 750 | + | "license": "MIT", | |
| 751 | + | "optional": true, | |
| 752 | + | "os": [ | |
| 753 | + | "linux" | |
| 754 | + | ], | |
| 755 | + | "engines": { | |
| 756 | + | "node": ">=18" | |
| 757 | + | } | |
| 758 | + | }, | |
| 759 | + | "node_modules/@esbuild/netbsd-arm64": { | |
| 760 | + | "version": "0.28.1", | |
| 761 | + | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", | |
| 762 | + | "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", | |
| 763 | + | "cpu": [ | |
| 764 | + | "arm64" | |
| 765 | + | ], | |
| 766 | + | "dev": true, | |
| 767 | + | "license": "MIT", | |
| 768 | + | "optional": true, | |
| 769 | + | "os": [ | |
| 770 | + | "netbsd" | |
| 771 | + | ], | |
| 772 | + | "engines": { | |
| 773 | + | "node": ">=18" | |
| 774 | + | } | |
| 775 | + | }, | |
| 776 | + | "node_modules/@esbuild/netbsd-x64": { | |
| 777 | + | "version": "0.28.1", | |
| 778 | + | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", | |
| 779 | + | "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", | |
| 780 | + | "cpu": [ | |
| 781 | + | "x64" | |
| 782 | + | ], | |
| 783 | + | "dev": true, | |
| 784 | + | "license": "MIT", | |
| 785 | + | "optional": true, | |
| 786 | + | "os": [ | |
| 787 | + | "netbsd" | |
| 788 | + | ], | |
| 789 | + | "engines": { | |
| 790 | + | "node": ">=18" | |
| 791 | + | } | |
| 792 | + | }, | |
| 793 | + | "node_modules/@esbuild/openbsd-arm64": { | |
| 794 | + | "version": "0.28.1", | |
| 795 | + | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", | |
| 796 | + | "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", | |
| 797 | + | "cpu": [ | |
| 798 | + | "arm64" | |
| 799 | + | ], | |
| 800 | + | "dev": true, | |
| 801 | + | "license": "MIT", | |
| 802 | + | "optional": true, | |
| 803 | + | "os": [ | |
| 804 | + | "openbsd" | |
| 805 | + | ], | |
| 806 | + | "engines": { | |
| 807 | + | "node": ">=18" | |
| 808 | + | } | |
| 809 | + | }, | |
| 810 | + | "node_modules/@esbuild/openbsd-x64": { | |
| 811 | + | "version": "0.28.1", | |
| 812 | + | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", | |
| 813 | + | "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", | |
| 814 | + | "cpu": [ | |
| 815 | + | "x64" | |
| 816 | + | ], | |
| 817 | + | "dev": true, | |
| 818 | + | "license": "MIT", | |
| 819 | + | "optional": true, | |
| 820 | + | "os": [ | |
| 821 | + | "openbsd" | |
| 822 | + | ], | |
| 823 | + | "engines": { | |
| 824 | + | "node": ">=18" | |
| 825 | + | } | |
| 826 | + | }, | |
| 827 | + | "node_modules/@esbuild/openharmony-arm64": { | |
| 828 | + | "version": "0.28.1", | |
| 829 | + | "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", | |
| 830 | + | "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", | |
| 831 | + | "cpu": [ | |
| 832 | + | "arm64" | |
| 833 | + | ], | |
| 834 | + | "dev": true, | |
| 835 | + | "license": "MIT", | |
| 836 | + | "optional": true, | |
| 837 | + | "os": [ | |
| 838 | + | "openharmony" | |
| 839 | + | ], | |
| 840 | + | "engines": { | |
| 841 | + | "node": ">=18" | |
| 842 | + | } | |
| 843 | + | }, | |
| 844 | + | "node_modules/@esbuild/sunos-x64": { | |
| 845 | + | "version": "0.28.1", | |
| 846 | + | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", | |
| 847 | + | "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", | |
| 848 | + | "cpu": [ | |
| 849 | + | "x64" | |
| 850 | + | ], | |
| 851 | + | "dev": true, | |
| 852 | + | "license": "MIT", | |
| 853 | + | "optional": true, | |
| 854 | + | "os": [ | |
| 855 | + | "sunos" | |
| 856 | + | ], | |
| 857 | + | "engines": { | |
| 858 | + | "node": ">=18" | |
| 859 | + | } | |
| 860 | + | }, | |
| 861 | + | "node_modules/@esbuild/win32-arm64": { | |
| 862 | + | "version": "0.28.1", | |
| 863 | + | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", | |
| 864 | + | "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", | |
| 865 | + | "cpu": [ | |
| 866 | + | "arm64" | |
| 867 | + | ], | |
| 868 | + | "dev": true, | |
| 869 | + | "license": "MIT", | |
| 870 | + | "optional": true, | |
| 871 | + | "os": [ | |
| 872 | + | "win32" | |
| 873 | + | ], | |
| 874 | + | "engines": { | |
| 875 | + | "node": ">=18" | |
| 876 | + | } | |
| 877 | + | }, | |
| 878 | + | "node_modules/@esbuild/win32-ia32": { | |
| 879 | + | "version": "0.28.1", | |
| 880 | + | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", | |
| 881 | + | "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", | |
| 882 | + | "cpu": [ | |
| 883 | + | "ia32" | |
| 884 | + | ], | |
| 885 | + | "dev": true, | |
| 886 | + | "license": "MIT", | |
| 887 | + | "optional": true, | |
| 888 | + | "os": [ | |
| 889 | + | "win32" | |
| 890 | + | ], | |
| 891 | + | "engines": { | |
| 892 | + | "node": ">=18" | |
| 893 | + | } | |
| 894 | + | }, | |
| 895 | + | "node_modules/@esbuild/win32-x64": { | |
| 896 | + | "version": "0.28.1", | |
| 897 | + | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", | |
| 898 | + | "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", | |
| 899 | + | "cpu": [ | |
| 900 | + | "x64" | |
| 901 | + | ], | |
| 902 | + | "dev": true, | |
| 903 | + | "license": "MIT", | |
| 904 | + | "optional": true, | |
| 905 | + | "os": [ | |
| 906 | + | "win32" | |
| 907 | + | ], | |
| 908 | + | "engines": { | |
| 909 | + | "node": ">=18" | |
| 910 | + | } | |
| 911 | + | }, | |
| 912 | + | "node_modules/@g1t/contracts": { | |
| 913 | + | "resolved": "packages/contracts", | |
| 914 | + | "link": true | |
| 915 | + | }, | |
| 916 | + | "node_modules/@g1t/events": { | |
| 917 | + | "resolved": "services/events", | |
| 918 | + | "link": true | |
| 919 | + | }, | |
| 920 | + | "node_modules/@g1t/identity": { | |
| 921 | + | "resolved": "services/identity", | |
| 922 | + | "link": true | |
| 923 | + | }, | |
| 924 | + | "node_modules/@g1t/repos": { | |
| 925 | + | "resolved": "services/repos", | |
| 926 | + | "link": true | |
| 927 | + | }, | |
| 928 | + | "node_modules/@g1t/web": { | |
| 929 | + | "resolved": "apps/web", | |
| 930 | + | "link": true | |
| 931 | + | }, | |
| 932 | + | "node_modules/@g1t/work": { | |
| 933 | + | "resolved": "services/work", | |
| 934 | + | "link": true | |
| 935 | + | }, | |
| 936 | + | "node_modules/@img/colour": { | |
| 937 | + | "version": "1.1.0", | |
| 938 | + | "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", | |
| 939 | + | "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", | |
| 940 | + | "dev": true, | |
| 941 | + | "license": "MIT", | |
| 942 | + | "engines": { | |
| 943 | + | "node": ">=18" | |
| 944 | + | } | |
| 945 | + | }, | |
| 946 | + | "node_modules/@img/sharp-darwin-arm64": { | |
| 947 | + | "version": "0.35.4", | |
| 948 | + | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", | |
| 949 | + | "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", | |
| 950 | + | "cpu": [ | |
| 951 | + | "arm64" | |
| 952 | + | ], | |
| 953 | + | "dev": true, | |
| 954 | + | "license": "Apache-2.0", | |
| 955 | + | "optional": true, | |
| 956 | + | "os": [ | |
| 957 | + | "darwin" | |
| 958 | + | ], | |
| 959 | + | "engines": { | |
| 960 | + | "node": ">=20.9.0" | |
| 961 | + | }, | |
| 962 | + | "funding": { | |
| 963 | + | "url": "https://opencollective.com/libvips" | |
| 964 | + | }, | |
| 965 | + | "optionalDependencies": { | |
| 966 | + | "@img/sharp-libvips-darwin-arm64": "1.3.3" | |
| 967 | + | } | |
| 968 | + | }, | |
| 969 | + | "node_modules/@img/sharp-darwin-x64": { | |
| 970 | + | "version": "0.35.4", | |
| 971 | + | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", | |
| 972 | + | "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", | |
| 973 | + | "cpu": [ | |
| 974 | + | "x64" | |
| 975 | + | ], | |
| 976 | + | "dev": true, | |
| 977 | + | "license": "Apache-2.0", | |
| 978 | + | "optional": true, | |
| 979 | + | "os": [ | |
| 980 | + | "darwin" | |
| 981 | + | ], | |
| 982 | + | "engines": { | |
| 983 | + | "node": ">=20.9.0" | |
| 984 | + | }, | |
| 985 | + | "funding": { | |
| 986 | + | "url": "https://opencollective.com/libvips" | |
| 987 | + | }, | |
| 988 | + | "optionalDependencies": { | |
| 989 | + | "@img/sharp-libvips-darwin-x64": "1.3.3" | |
| 990 | + | } | |
| 991 | + | }, | |
| 992 | + | "node_modules/@img/sharp-freebsd-wasm32": { | |
| 993 | + | "version": "0.35.4", | |
| 994 | + | "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", | |
| 995 | + | "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", | |
| 996 | + | "dev": true, | |
| 997 | + | "license": "Apache-2.0", | |
| 998 | + | "optional": true, | |
| 999 | + | "os": [ | |
| 1000 | + | "freebsd" | |
| 1001 | + | ], | |
| 1002 | + | "dependencies": { | |
| 1003 | + | "@img/sharp-wasm32": "0.35.4" | |
| 1004 | + | }, | |
| 1005 | + | "engines": { | |
| 1006 | + | "node": ">=20.9.0" | |
| 1007 | + | }, | |
| 1008 | + | "funding": { | |
| 1009 | + | "url": "https://opencollective.com/libvips" | |
| 1010 | + | } | |
| 1011 | + | }, | |
| 1012 | + | "node_modules/@img/sharp-libvips-darwin-arm64": { | |
| 1013 | + | "version": "1.3.3", | |
| 1014 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", | |
| 1015 | + | "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", | |
| 1016 | + | "cpu": [ | |
| 1017 | + | "arm64" | |
| 1018 | + | ], | |
| 1019 | + | "dev": true, | |
| 1020 | + | "license": "LGPL-3.0-or-later", | |
| 1021 | + | "optional": true, | |
| 1022 | + | "os": [ | |
| 1023 | + | "darwin" | |
| 1024 | + | ], | |
| 1025 | + | "funding": { | |
| 1026 | + | "url": "https://opencollective.com/libvips" | |
| 1027 | + | } | |
| 1028 | + | }, | |
| 1029 | + | "node_modules/@img/sharp-libvips-darwin-x64": { | |
| 1030 | + | "version": "1.3.3", | |
| 1031 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", | |
| 1032 | + | "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", | |
| 1033 | + | "cpu": [ | |
| 1034 | + | "x64" | |
| 1035 | + | ], | |
| 1036 | + | "dev": true, | |
| 1037 | + | "license": "LGPL-3.0-or-later", | |
| 1038 | + | "optional": true, | |
| 1039 | + | "os": [ | |
| 1040 | + | "darwin" | |
| 1041 | + | ], | |
| 1042 | + | "funding": { | |
| 1043 | + | "url": "https://opencollective.com/libvips" | |
| 1044 | + | } | |
| 1045 | + | }, | |
| 1046 | + | "node_modules/@img/sharp-libvips-linux-arm": { | |
| 1047 | + | "version": "1.3.3", | |
| 1048 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", | |
| 1049 | + | "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", | |
| 1050 | + | "cpu": [ | |
| 1051 | + | "arm" | |
| 1052 | + | ], | |
| 1053 | + | "dev": true, | |
| 1054 | + | "libc": [ | |
| 1055 | + | "glibc" | |
| 1056 | + | ], | |
| 1057 | + | "license": "LGPL-3.0-or-later", | |
| 1058 | + | "optional": true, | |
| 1059 | + | "os": [ | |
| 1060 | + | "linux" | |
| 1061 | + | ], | |
| 1062 | + | "funding": { | |
| 1063 | + | "url": "https://opencollective.com/libvips" | |
| 1064 | + | } | |
| 1065 | + | }, | |
| 1066 | + | "node_modules/@img/sharp-libvips-linux-arm64": { | |
| 1067 | + | "version": "1.3.3", | |
| 1068 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", | |
| 1069 | + | "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", | |
| 1070 | + | "cpu": [ | |
| 1071 | + | "arm64" | |
| 1072 | + | ], | |
| 1073 | + | "dev": true, | |
| 1074 | + | "libc": [ | |
| 1075 | + | "glibc" | |
| 1076 | + | ], | |
| 1077 | + | "license": "LGPL-3.0-or-later", | |
| 1078 | + | "optional": true, | |
| 1079 | + | "os": [ | |
| 1080 | + | "linux" | |
| 1081 | + | ], | |
| 1082 | + | "funding": { | |
| 1083 | + | "url": "https://opencollective.com/libvips" | |
| 1084 | + | } | |
| 1085 | + | }, | |
| 1086 | + | "node_modules/@img/sharp-libvips-linux-ppc64": { | |
| 1087 | + | "version": "1.3.3", | |
| 1088 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", | |
| 1089 | + | "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", | |
| 1090 | + | "cpu": [ | |
| 1091 | + | "ppc64" | |
| 1092 | + | ], | |
| 1093 | + | "dev": true, | |
| 1094 | + | "libc": [ | |
| 1095 | + | "glibc" | |
| 1096 | + | ], | |
| 1097 | + | "license": "LGPL-3.0-or-later", | |
| 1098 | + | "optional": true, | |
| 1099 | + | "os": [ | |
| 1100 | + | "linux" | |
| 1101 | + | ], | |
| 1102 | + | "funding": { | |
| 1103 | + | "url": "https://opencollective.com/libvips" | |
| 1104 | + | } | |
| 1105 | + | }, | |
| 1106 | + | "node_modules/@img/sharp-libvips-linux-riscv64": { | |
| 1107 | + | "version": "1.3.3", | |
| 1108 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", | |
| 1109 | + | "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", | |
| 1110 | + | "cpu": [ | |
| 1111 | + | "riscv64" | |
| 1112 | + | ], | |
| 1113 | + | "dev": true, | |
| 1114 | + | "libc": [ | |
| 1115 | + | "glibc" | |
| 1116 | + | ], | |
| 1117 | + | "license": "LGPL-3.0-or-later", | |
| 1118 | + | "optional": true, | |
| 1119 | + | "os": [ | |
| 1120 | + | "linux" | |
| 1121 | + | ], | |
| 1122 | + | "funding": { | |
| 1123 | + | "url": "https://opencollective.com/libvips" | |
| 1124 | + | } | |
| 1125 | + | }, | |
| 1126 | + | "node_modules/@img/sharp-libvips-linux-s390x": { | |
| 1127 | + | "version": "1.3.3", | |
| 1128 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", | |
| 1129 | + | "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", | |
| 1130 | + | "cpu": [ | |
| 1131 | + | "s390x" | |
| 1132 | + | ], | |
| 1133 | + | "dev": true, | |
| 1134 | + | "libc": [ | |
| 1135 | + | "glibc" | |
| 1136 | + | ], | |
| 1137 | + | "license": "LGPL-3.0-or-later", | |
| 1138 | + | "optional": true, | |
| 1139 | + | "os": [ | |
| 1140 | + | "linux" | |
| 1141 | + | ], | |
| 1142 | + | "funding": { | |
| 1143 | + | "url": "https://opencollective.com/libvips" | |
| 1144 | + | } | |
| 1145 | + | }, | |
| 1146 | + | "node_modules/@img/sharp-libvips-linux-x64": { | |
| 1147 | + | "version": "1.3.3", | |
| 1148 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", | |
| 1149 | + | "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", | |
| 1150 | + | "cpu": [ | |
| 1151 | + | "x64" | |
| 1152 | + | ], | |
| 1153 | + | "dev": true, | |
| 1154 | + | "libc": [ | |
| 1155 | + | "glibc" | |
| 1156 | + | ], | |
| 1157 | + | "license": "LGPL-3.0-or-later", | |
| 1158 | + | "optional": true, | |
| 1159 | + | "os": [ | |
| 1160 | + | "linux" | |
| 1161 | + | ], | |
| 1162 | + | "funding": { | |
| 1163 | + | "url": "https://opencollective.com/libvips" | |
| 1164 | + | } | |
| 1165 | + | }, | |
| 1166 | + | "node_modules/@img/sharp-libvips-linuxmusl-arm64": { | |
| 1167 | + | "version": "1.3.3", | |
| 1168 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", | |
| 1169 | + | "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", | |
| 1170 | + | "cpu": [ | |
| 1171 | + | "arm64" | |
| 1172 | + | ], | |
| 1173 | + | "dev": true, | |
| 1174 | + | "libc": [ | |
| 1175 | + | "musl" | |
| 1176 | + | ], | |
| 1177 | + | "license": "LGPL-3.0-or-later", | |
| 1178 | + | "optional": true, | |
| 1179 | + | "os": [ | |
| 1180 | + | "linux" | |
| 1181 | + | ], | |
| 1182 | + | "funding": { | |
| 1183 | + | "url": "https://opencollective.com/libvips" | |
| 1184 | + | } | |
| 1185 | + | }, | |
| 1186 | + | "node_modules/@img/sharp-libvips-linuxmusl-x64": { | |
| 1187 | + | "version": "1.3.3", | |
| 1188 | + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", | |
| 1189 | + | "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", | |
| 1190 | + | "cpu": [ | |
| 1191 | + | "x64" | |
| 1192 | + | ], | |
| 1193 | + | "dev": true, | |
| 1194 | + | "libc": [ | |
| 1195 | + | "musl" | |
| 1196 | + | ], | |
| 1197 | + | "license": "LGPL-3.0-or-later", | |
| 1198 | + | "optional": true, | |
| 1199 | + | "os": [ | |
| 1200 | + | "linux" | |
| 1201 | + | ], | |
| 1202 | + | "funding": { | |
| 1203 | + | "url": "https://opencollective.com/libvips" | |
| 1204 | + | } | |
| 1205 | + | }, | |
| 1206 | + | "node_modules/@img/sharp-linux-arm": { | |
| 1207 | + | "version": "0.35.4", | |
| 1208 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", | |
| 1209 | + | "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", | |
| 1210 | + | "cpu": [ | |
| 1211 | + | "arm" | |
| 1212 | + | ], | |
| 1213 | + | "dev": true, | |
| 1214 | + | "libc": [ | |
| 1215 | + | "glibc" | |
| 1216 | + | ], | |
| 1217 | + | "license": "Apache-2.0", | |
| 1218 | + | "optional": true, | |
| 1219 | + | "os": [ | |
| 1220 | + | "linux" | |
| 1221 | + | ], | |
| 1222 | + | "engines": { | |
| 1223 | + | "node": ">=20.9.0" | |
| 1224 | + | }, | |
| 1225 | + | "funding": { | |
| 1226 | + | "url": "https://opencollective.com/libvips" | |
| 1227 | + | }, | |
| 1228 | + | "optionalDependencies": { | |
| 1229 | + | "@img/sharp-libvips-linux-arm": "1.3.3" | |
| 1230 | + | } | |
| 1231 | + | }, | |
| 1232 | + | "node_modules/@img/sharp-linux-arm64": { | |
| 1233 | + | "version": "0.35.4", | |
| 1234 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", | |
| 1235 | + | "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", | |
| 1236 | + | "cpu": [ | |
| 1237 | + | "arm64" | |
| 1238 | + | ], | |
| 1239 | + | "dev": true, | |
| 1240 | + | "libc": [ | |
| 1241 | + | "glibc" | |
| 1242 | + | ], | |
| 1243 | + | "license": "Apache-2.0", | |
| 1244 | + | "optional": true, | |
| 1245 | + | "os": [ | |
| 1246 | + | "linux" | |
| 1247 | + | ], | |
| 1248 | + | "engines": { | |
| 1249 | + | "node": ">=20.9.0" | |
| 1250 | + | }, | |
| 1251 | + | "funding": { | |
| 1252 | + | "url": "https://opencollective.com/libvips" | |
| 1253 | + | }, | |
| 1254 | + | "optionalDependencies": { | |
| 1255 | + | "@img/sharp-libvips-linux-arm64": "1.3.3" | |
| 1256 | + | } | |
| 1257 | + | }, | |
| 1258 | + | "node_modules/@img/sharp-linux-ppc64": { | |
| 1259 | + | "version": "0.35.4", | |
| 1260 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", | |
| 1261 | + | "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", | |
| 1262 | + | "cpu": [ | |
| 1263 | + | "ppc64" | |
| 1264 | + | ], | |
| 1265 | + | "dev": true, | |
| 1266 | + | "libc": [ | |
| 1267 | + | "glibc" | |
| 1268 | + | ], | |
| 1269 | + | "license": "Apache-2.0", | |
| 1270 | + | "optional": true, | |
| 1271 | + | "os": [ | |
| 1272 | + | "linux" | |
| 1273 | + | ], | |
| 1274 | + | "engines": { | |
| 1275 | + | "node": ">=20.9.0" | |
| 1276 | + | }, | |
| 1277 | + | "funding": { | |
| 1278 | + | "url": "https://opencollective.com/libvips" | |
| 1279 | + | }, | |
| 1280 | + | "optionalDependencies": { | |
| 1281 | + | "@img/sharp-libvips-linux-ppc64": "1.3.3" | |
| 1282 | + | } | |
| 1283 | + | }, | |
| 1284 | + | "node_modules/@img/sharp-linux-riscv64": { | |
| 1285 | + | "version": "0.35.4", | |
| 1286 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", | |
| 1287 | + | "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", | |
| 1288 | + | "cpu": [ | |
| 1289 | + | "riscv64" | |
| 1290 | + | ], | |
| 1291 | + | "dev": true, | |
| 1292 | + | "libc": [ | |
| 1293 | + | "glibc" | |
| 1294 | + | ], | |
| 1295 | + | "license": "Apache-2.0", | |
| 1296 | + | "optional": true, | |
| 1297 | + | "os": [ | |
| 1298 | + | "linux" | |
| 1299 | + | ], | |
| 1300 | + | "engines": { | |
| 1301 | + | "node": ">=20.9.0" | |
| 1302 | + | }, | |
| 1303 | + | "funding": { | |
| 1304 | + | "url": "https://opencollective.com/libvips" | |
| 1305 | + | }, | |
| 1306 | + | "optionalDependencies": { | |
| 1307 | + | "@img/sharp-libvips-linux-riscv64": "1.3.3" | |
| 1308 | + | } | |
| 1309 | + | }, | |
| 1310 | + | "node_modules/@img/sharp-linux-s390x": { | |
| 1311 | + | "version": "0.35.4", | |
| 1312 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", | |
| 1313 | + | "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", | |
| 1314 | + | "cpu": [ | |
| 1315 | + | "s390x" | |
| 1316 | + | ], | |
| 1317 | + | "dev": true, | |
| 1318 | + | "libc": [ | |
| 1319 | + | "glibc" | |
| 1320 | + | ], | |
| 1321 | + | "license": "Apache-2.0", | |
| 1322 | + | "optional": true, | |
| 1323 | + | "os": [ | |
| 1324 | + | "linux" | |
| 1325 | + | ], | |
| 1326 | + | "engines": { | |
| 1327 | + | "node": ">=20.9.0" | |
| 1328 | + | }, | |
| 1329 | + | "funding": { | |
| 1330 | + | "url": "https://opencollective.com/libvips" | |
| 1331 | + | }, | |
| 1332 | + | "optionalDependencies": { | |
| 1333 | + | "@img/sharp-libvips-linux-s390x": "1.3.3" | |
| 1334 | + | } | |
| 1335 | + | }, | |
| 1336 | + | "node_modules/@img/sharp-linux-x64": { | |
| 1337 | + | "version": "0.35.4", | |
| 1338 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", | |
| 1339 | + | "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", | |
| 1340 | + | "cpu": [ | |
| 1341 | + | "x64" | |
| 1342 | + | ], | |
| 1343 | + | "dev": true, | |
| 1344 | + | "libc": [ | |
| 1345 | + | "glibc" | |
| 1346 | + | ], | |
| 1347 | + | "license": "Apache-2.0", | |
| 1348 | + | "optional": true, | |
| 1349 | + | "os": [ | |
| 1350 | + | "linux" | |
| 1351 | + | ], | |
| 1352 | + | "engines": { | |
| 1353 | + | "node": ">=20.9.0" | |
| 1354 | + | }, | |
| 1355 | + | "funding": { | |
| 1356 | + | "url": "https://opencollective.com/libvips" | |
| 1357 | + | }, | |
| 1358 | + | "optionalDependencies": { | |
| 1359 | + | "@img/sharp-libvips-linux-x64": "1.3.3" | |
| 1360 | + | } | |
| 1361 | + | }, | |
| 1362 | + | "node_modules/@img/sharp-linuxmusl-arm64": { | |
| 1363 | + | "version": "0.35.4", | |
| 1364 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", | |
| 1365 | + | "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", | |
| 1366 | + | "cpu": [ | |
| 1367 | + | "arm64" | |
| 1368 | + | ], | |
| 1369 | + | "dev": true, | |
| 1370 | + | "libc": [ | |
| 1371 | + | "musl" | |
| 1372 | + | ], | |
| 1373 | + | "license": "Apache-2.0", | |
| 1374 | + | "optional": true, | |
| 1375 | + | "os": [ | |
| 1376 | + | "linux" | |
| 1377 | + | ], | |
| 1378 | + | "engines": { | |
| 1379 | + | "node": ">=20.9.0" | |
| 1380 | + | }, | |
| 1381 | + | "funding": { | |
| 1382 | + | "url": "https://opencollective.com/libvips" | |
| 1383 | + | }, | |
| 1384 | + | "optionalDependencies": { | |
| 1385 | + | "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" | |
| 1386 | + | } | |
| 1387 | + | }, | |
| 1388 | + | "node_modules/@img/sharp-linuxmusl-x64": { | |
| 1389 | + | "version": "0.35.4", | |
| 1390 | + | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", | |
| 1391 | + | "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", | |
| 1392 | + | "cpu": [ | |
| 1393 | + | "x64" | |
| 1394 | + | ], | |
| 1395 | + | "dev": true, | |
| 1396 | + | "libc": [ | |
| 1397 | + | "musl" | |
| 1398 | + | ], | |
| 1399 | + | "license": "Apache-2.0", | |
| 1400 | + | "optional": true, | |
| 1401 | + | "os": [ | |
| 1402 | + | "linux" | |
| 1403 | + | ], | |
| 1404 | + | "engines": { | |
| 1405 | + | "node": ">=20.9.0" | |
| 1406 | + | }, | |
| 1407 | + | "funding": { | |
| 1408 | + | "url": "https://opencollective.com/libvips" | |
| 1409 | + | }, | |
| 1410 | + | "optionalDependencies": { | |
| 1411 | + | "@img/sharp-libvips-linuxmusl-x64": "1.3.3" | |
| 1412 | + | } | |
| 1413 | + | }, | |
| 1414 | + | "node_modules/@img/sharp-wasm32": { | |
| 1415 | + | "version": "0.35.4", | |
| 1416 | + | "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", | |
| 1417 | + | "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", | |
| 1418 | + | "dev": true, | |
| 1419 | + | "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", | |
| 1420 | + | "optional": true, | |
| 1421 | + | "dependencies": { | |
| 1422 | + | "@emnapi/runtime": "^1.11.3" | |
| 1423 | + | }, | |
| 1424 | + | "engines": { | |
| 1425 | + | "node": ">=20.9.0" | |
| 1426 | + | }, | |
| 1427 | + | "funding": { | |
| 1428 | + | "url": "https://opencollective.com/libvips" | |
| 1429 | + | } | |
| 1430 | + | }, | |
| 1431 | + | "node_modules/@img/sharp-webcontainers-wasm32": { | |
| 1432 | + | "version": "0.35.4", | |
| 1433 | + | "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", | |
| 1434 | + | "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", | |
| 1435 | + | "cpu": [ | |
| 1436 | + | "wasm32" | |
| 1437 | + | ], | |
| 1438 | + | "dev": true, | |
| 1439 | + | "license": "Apache-2.0", | |
| 1440 | + | "optional": true, | |
| 1441 | + | "dependencies": { | |
| 1442 | + | "@img/sharp-wasm32": "0.35.4" | |
| 1443 | + | }, | |
| 1444 | + | "engines": { | |
| 1445 | + | "node": ">=20.9.0" | |
| 1446 | + | }, | |
| 1447 | + | "funding": { | |
| 1448 | + | "url": "https://opencollective.com/libvips" | |
| 1449 | + | } | |
| 1450 | + | }, | |
| 1451 | + | "node_modules/@img/sharp-win32-arm64": { | |
| 1452 | + | "version": "0.35.4", | |
| 1453 | + | "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", | |
| 1454 | + | "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", | |
| 1455 | + | "cpu": [ | |
| 1456 | + | "arm64" | |
| 1457 | + | ], | |
| 1458 | + | "dev": true, | |
| 1459 | + | "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 1460 | + | "optional": true, | |
| 1461 | + | "os": [ | |
| 1462 | + | "win32" | |
| 1463 | + | ], | |
| 1464 | + | "engines": { | |
| 1465 | + | "node": ">=20.9.0" | |
| 1466 | + | }, | |
| 1467 | + | "funding": { | |
| 1468 | + | "url": "https://opencollective.com/libvips" | |
| 1469 | + | } | |
| 1470 | + | }, | |
| 1471 | + | "node_modules/@img/sharp-win32-ia32": { | |
| 1472 | + | "version": "0.35.4", | |
| 1473 | + | "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", | |
| 1474 | + | "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", | |
| 1475 | + | "cpu": [ | |
| 1476 | + | "ia32" | |
| 1477 | + | ], | |
| 1478 | + | "dev": true, | |
| 1479 | + | "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 1480 | + | "optional": true, | |
| 1481 | + | "os": [ | |
| 1482 | + | "win32" | |
| 1483 | + | ], | |
| 1484 | + | "engines": { | |
| 1485 | + | "node": "^20.9.0" | |
| 1486 | + | }, | |
| 1487 | + | "funding": { | |
| 1488 | + | "url": "https://opencollective.com/libvips" | |
| 1489 | + | } | |
| 1490 | + | }, | |
| 1491 | + | "node_modules/@img/sharp-win32-x64": { | |
| 1492 | + | "version": "0.35.4", | |
| 1493 | + | "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", | |
| 1494 | + | "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", | |
| 1495 | + | "cpu": [ | |
| 1496 | + | "x64" | |
| 1497 | + | ], | |
| 1498 | + | "dev": true, | |
| 1499 | + | "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 1500 | + | "optional": true, | |
| 1501 | + | "os": [ | |
| 1502 | + | "win32" | |
| 1503 | + | ], | |
| 1504 | + | "engines": { | |
| 1505 | + | "node": ">=20.9.0" | |
| 1506 | + | }, | |
| 1507 | + | "funding": { | |
| 1508 | + | "url": "https://opencollective.com/libvips" | |
| 1509 | + | } | |
| 1510 | + | }, | |
| 1511 | + | "node_modules/@jridgewell/gen-mapping": { | |
| 1512 | + | "version": "0.3.13", | |
| 1513 | + | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", | |
| 1514 | + | "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", | |
| 1515 | + | "dev": true, | |
| 1516 | + | "license": "MIT", | |
| 1517 | + | "dependencies": { | |
| 1518 | + | "@jridgewell/sourcemap-codec": "^1.5.0", | |
| 1519 | + | "@jridgewell/trace-mapping": "^0.3.24" | |
| 1520 | + | } | |
| 1521 | + | }, | |
| 1522 | + | "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { | |
| 1523 | + | "version": "0.3.31", | |
| 1524 | + | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 1525 | + | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 1526 | + | "dev": true, | |
| 1527 | + | "license": "MIT", | |
| 1528 | + | "dependencies": { | |
| 1529 | + | "@jridgewell/resolve-uri": "^3.1.0", | |
| 1530 | + | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 1531 | + | } | |
| 1532 | + | }, | |
| 1533 | + | "node_modules/@jridgewell/remapping": { | |
| 1534 | + | "version": "2.3.5", | |
| 1535 | + | "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", | |
| 1536 | + | "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", | |
| 1537 | + | "dev": true, | |
| 1538 | + | "license": "MIT", | |
| 1539 | + | "dependencies": { | |
| 1540 | + | "@jridgewell/gen-mapping": "^0.3.5", | |
| 1541 | + | "@jridgewell/trace-mapping": "^0.3.24" | |
| 1542 | + | } | |
| 1543 | + | }, | |
| 1544 | + | "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { | |
| 1545 | + | "version": "0.3.31", | |
| 1546 | + | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 1547 | + | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 1548 | + | "dev": true, | |
| 1549 | + | "license": "MIT", | |
| 1550 | + | "dependencies": { | |
| 1551 | + | "@jridgewell/resolve-uri": "^3.1.0", | |
| 1552 | + | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 1553 | + | } | |
| 1554 | + | }, | |
| 1555 | + | "node_modules/@jridgewell/resolve-uri": { | |
| 1556 | + | "version": "3.1.2", | |
| 1557 | + | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 1558 | + | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 1559 | + | "dev": true, | |
| 1560 | + | "license": "MIT", | |
| 1561 | + | "engines": { | |
| 1562 | + | "node": ">=6.0.0" | |
| 1563 | + | } | |
| 1564 | + | }, | |
| 1565 | + | "node_modules/@jridgewell/sourcemap-codec": { | |
| 1566 | + | "version": "1.6.0", | |
| 1567 | + | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", | |
| 1568 | + | "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", | |
| 1569 | + | "dev": true, | |
| 1570 | + | "license": "MIT" | |
| 1571 | + | }, | |
| 1572 | + | "node_modules/@jridgewell/trace-mapping": { | |
| 1573 | + | "version": "0.3.9", | |
| 1574 | + | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", | |
| 1575 | + | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", | |
| 1576 | + | "dev": true, | |
| 1577 | + | "license": "MIT", | |
| 1578 | + | "dependencies": { | |
| 1579 | + | "@jridgewell/resolve-uri": "^3.0.3", | |
| 1580 | + | "@jridgewell/sourcemap-codec": "^1.4.10" | |
| 1581 | + | } | |
| 1582 | + | }, | |
| 1583 | + | "node_modules/@oxc-project/types": { | |
| 1584 | + | "version": "0.152.0", | |
| 1585 | + | "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", | |
| 1586 | + | "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", | |
| 1587 | + | "dev": true, | |
| 1588 | + | "license": "MIT", | |
| 1589 | + | "funding": { | |
| 1590 | + | "url": "https://github.com/sponsors/oxc-project" | |
| 1591 | + | } | |
| 1592 | + | }, | |
| 1593 | + | "node_modules/@poppinss/colors": { | |
| 1594 | + | "version": "4.1.6", | |
| 1595 | + | "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", | |
| 1596 | + | "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", | |
| 1597 | + | "dev": true, | |
| 1598 | + | "license": "MIT", | |
| 1599 | + | "dependencies": { | |
| 1600 | + | "kleur": "^4.1.5" | |
| 1601 | + | } | |
| 1602 | + | }, | |
| 1603 | + | "node_modules/@poppinss/dumper": { | |
| 1604 | + | "version": "0.6.5", | |
| 1605 | + | "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", | |
| 1606 | + | "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", | |
| 1607 | + | "dev": true, | |
| 1608 | + | "license": "MIT", | |
| 1609 | + | "dependencies": { | |
| 1610 | + | "@poppinss/colors": "^4.1.5", | |
| 1611 | + | "@sindresorhus/is": "^7.0.2", | |
| 1612 | + | "supports-color": "^10.0.0" | |
| 1613 | + | } | |
| 1614 | + | }, | |
| 1615 | + | "node_modules/@poppinss/exception": { | |
| 1616 | + | "version": "1.2.3", | |
| 1617 | + | "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", | |
| 1618 | + | "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", | |
| 1619 | + | "dev": true, | |
| 1620 | + | "license": "MIT" | |
| 1621 | + | }, | |
| 1622 | + | "node_modules/@react-router/dev": { | |
| 1623 | + | "version": "8.4.0", | |
| 1624 | + | "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-8.4.0.tgz", | |
| 1625 | + | "integrity": "sha512-GKznpn6h8c52xGXwtsypD5ZwNiWFSeqoKPdyGq7SfG+T9rFjGO8F0i/R/yECfZqHXaNz1NVmpODuBamBy9FfMg==", | |
| 1626 | + | "dev": true, | |
| 1627 | + | "license": "MIT", | |
| 1628 | + | "dependencies": { | |
| 1629 | + | "@babel/core": "^7.29.7", | |
| 1630 | + | "@babel/generator": "^7.29.7", | |
| 1631 | + | "@babel/parser": "^7.29.7", | |
| 1632 | + | "@babel/traverse": "^7.29.7", | |
| 1633 | + | "@babel/types": "^7.29.7", | |
| 1634 | + | "@react-router/node": "8.4.0", | |
| 1635 | + | "@remix-run/node-fetch-server": "^0.14.1", | |
| 1636 | + | "babel-dead-code-elimination": "^1.0.12", | |
| 1637 | + | "chokidar": "^5.0.0", | |
| 1638 | + | "dedent": "^1.7.2", | |
| 1639 | + | "es-module-lexer": "^2.1.0", | |
| 1640 | + | "exit-hook": "5.1.0", | |
| 1641 | + | "isbot": "^5.1.40", | |
| 1642 | + | "jsesc": "3.1.0", | |
| 1643 | + | "lodash": "^4.18.1", | |
| 1644 | + | "p-map": "^7.0.4", | |
| 1645 | + | "pathe": "^2.0.3", | |
| 1646 | + | "picocolors": "^1.1.1", | |
| 1647 | + | "pkg-types": "^2.3.1", | |
| 1648 | + | "react-refresh": "^0.18.0", | |
| 1649 | + | "semver": "^7.8.1", | |
| 1650 | + | "tinyglobby": "^0.2.16", | |
| 1651 | + | "valibot": "^1.4.1" | |
| 1652 | + | }, | |
| 1653 | + | "bin": { | |
| 1654 | + | "react-router": "bin.cjs" | |
| 1655 | + | }, | |
| 1656 | + | "engines": { | |
| 1657 | + | "node": ">=22.22.0" | |
| 1658 | + | }, | |
| 1659 | + | "peerDependencies": { | |
| 1660 | + | "@react-router/serve": "^8.4.0", | |
| 1661 | + | "@vitejs/plugin-rsc": "~0.5.26", | |
| 1662 | + | "react-router": "^8.4.0", | |
| 1663 | + | "react-server-dom-webpack": "^19.2.7", | |
| 1664 | + | "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0", | |
| 1665 | + | "vite": "^7.0.0 || ^8.0.0", | |
| 1666 | + | "wrangler": "^4.0.0" | |
| 1667 | + | }, | |
| 1668 | + | "peerDependenciesMeta": { | |
| 1669 | + | "@react-router/serve": { | |
| 1670 | + | "optional": true | |
| 1671 | + | }, | |
| 1672 | + | "@vitejs/plugin-rsc": { | |
| 1673 | + | "optional": true | |
| 1674 | + | }, | |
| 1675 | + | "react-server-dom-webpack": { | |
| 1676 | + | "optional": true | |
| 1677 | + | }, | |
| 1678 | + | "typescript": { | |
| 1679 | + | "optional": true | |
| 1680 | + | }, | |
| 1681 | + | "wrangler": { | |
| 1682 | + | "optional": true | |
| 1683 | + | } | |
| 1684 | + | } | |
| 1685 | + | }, | |
| 1686 | + | "node_modules/@react-router/node": { | |
| 1687 | + | "version": "8.4.0", | |
| 1688 | + | "resolved": "https://registry.npmjs.org/@react-router/node/-/node-8.4.0.tgz", | |
| 1689 | + | "integrity": "sha512-1teZHtk7XDm1pteQ2Plgtvtjjup5C5sIwFlOMYVIyoOH7X3bId3Qb//kUdbydX6NRIxcGK28UnfZlz4SrD/mkg==", | |
| 1690 | + | "dev": true, | |
| 1691 | + | "license": "MIT", | |
| 1692 | + | "dependencies": { | |
| 1693 | + | "@remix-run/node-fetch-server": "^0.14.1" | |
| 1694 | + | }, | |
| 1695 | + | "engines": { | |
| 1696 | + | "node": ">=22.22.0" | |
| 1697 | + | }, | |
| 1698 | + | "peerDependencies": { | |
| 1699 | + | "react-router": "8.4.0", | |
| 1700 | + | "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" | |
| 1701 | + | }, | |
| 1702 | + | "peerDependenciesMeta": { | |
| 1703 | + | "typescript": { | |
| 1704 | + | "optional": true | |
| 1705 | + | } | |
| 1706 | + | } | |
| 1707 | + | }, | |
| 1708 | + | "node_modules/@remix-run/node-fetch-server": { | |
| 1709 | + | "version": "0.14.2", | |
| 1710 | + | "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.14.2.tgz", | |
| 1711 | + | "integrity": "sha512-1cpM/f5i1ka/9CUMBwZXZp7Bn92x8VQjzg+F4eEe88DlVdNHkAKrf/vvbZoYgdkfy2bWHtGC6LQGLmojEqOF4Q==", | |
| 1712 | + | "dev": true, | |
| 1713 | + | "license": "MIT" | |
| 1714 | + | }, | |
| 1715 | + | "node_modules/@remix-run/route-pattern": { | |
| 1716 | + | "version": "0.22.1", | |
| 1717 | + | "resolved": "https://registry.npmjs.org/@remix-run/route-pattern/-/route-pattern-0.22.1.tgz", | |
| 1718 | + | "integrity": "sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==", | |
| 1719 | + | "license": "MIT" | |
| 1720 | + | }, | |
| 1721 | + | "node_modules/@rolldown/binding-android-arm-eabi": { | |
| 1722 | + | "version": "1.2.12", | |
| 1723 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", | |
| 1724 | + | "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", | |
| 1725 | + | "cpu": [ | |
| 1726 | + | "arm" | |
| 1727 | + | ], | |
| 1728 | + | "dev": true, | |
| 1729 | + | "license": "MIT", | |
| 1730 | + | "optional": true, | |
| 1731 | + | "os": [ | |
| 1732 | + | "android" | |
| 1733 | + | ], | |
| 1734 | + | "engines": { | |
| 1735 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1736 | + | } | |
| 1737 | + | }, | |
| 1738 | + | "node_modules/@rolldown/binding-android-arm64": { | |
| 1739 | + | "version": "1.2.12", | |
| 1740 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", | |
| 1741 | + | "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", | |
| 1742 | + | "cpu": [ | |
| 1743 | + | "arm64" | |
| 1744 | + | ], | |
| 1745 | + | "dev": true, | |
| 1746 | + | "license": "MIT", | |
| 1747 | + | "optional": true, | |
| 1748 | + | "os": [ | |
| 1749 | + | "android" | |
| 1750 | + | ], | |
| 1751 | + | "engines": { | |
| 1752 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1753 | + | } | |
| 1754 | + | }, | |
| 1755 | + | "node_modules/@rolldown/binding-darwin-arm64": { | |
| 1756 | + | "version": "1.2.12", | |
| 1757 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", | |
| 1758 | + | "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", | |
| 1759 | + | "cpu": [ | |
| 1760 | + | "arm64" | |
| 1761 | + | ], | |
| 1762 | + | "dev": true, | |
| 1763 | + | "license": "MIT", | |
| 1764 | + | "optional": true, | |
| 1765 | + | "os": [ | |
| 1766 | + | "darwin" | |
| 1767 | + | ], | |
| 1768 | + | "engines": { | |
| 1769 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1770 | + | } | |
| 1771 | + | }, | |
| 1772 | + | "node_modules/@rolldown/binding-darwin-x64": { | |
| 1773 | + | "version": "1.2.12", | |
| 1774 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", | |
| 1775 | + | "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", | |
| 1776 | + | "cpu": [ | |
| 1777 | + | "x64" | |
| 1778 | + | ], | |
| 1779 | + | "dev": true, | |
| 1780 | + | "license": "MIT", | |
| 1781 | + | "optional": true, | |
| 1782 | + | "os": [ | |
| 1783 | + | "darwin" | |
| 1784 | + | ], | |
| 1785 | + | "engines": { | |
| 1786 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1787 | + | } | |
| 1788 | + | }, | |
| 1789 | + | "node_modules/@rolldown/binding-freebsd-x64": { | |
| 1790 | + | "version": "1.2.12", | |
| 1791 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", | |
| 1792 | + | "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", | |
| 1793 | + | "cpu": [ | |
| 1794 | + | "x64" | |
| 1795 | + | ], | |
| 1796 | + | "dev": true, | |
| 1797 | + | "license": "MIT", | |
| 1798 | + | "optional": true, | |
| 1799 | + | "os": [ | |
| 1800 | + | "freebsd" | |
| 1801 | + | ], | |
| 1802 | + | "engines": { | |
| 1803 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1804 | + | } | |
| 1805 | + | }, | |
| 1806 | + | "node_modules/@rolldown/binding-linux-arm-gnueabihf": { | |
| 1807 | + | "version": "1.2.12", | |
| 1808 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", | |
| 1809 | + | "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", | |
| 1810 | + | "cpu": [ | |
| 1811 | + | "arm" | |
| 1812 | + | ], | |
| 1813 | + | "dev": true, | |
| 1814 | + | "license": "MIT", | |
| 1815 | + | "optional": true, | |
| 1816 | + | "os": [ | |
| 1817 | + | "linux" | |
| 1818 | + | ], | |
| 1819 | + | "engines": { | |
| 1820 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1821 | + | } | |
| 1822 | + | }, | |
| 1823 | + | "node_modules/@rolldown/binding-linux-arm64-gnu": { | |
| 1824 | + | "version": "1.2.12", | |
| 1825 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", | |
| 1826 | + | "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", | |
| 1827 | + | "cpu": [ | |
| 1828 | + | "arm64" | |
| 1829 | + | ], | |
| 1830 | + | "dev": true, | |
| 1831 | + | "libc": [ | |
| 1832 | + | "glibc" | |
| 1833 | + | ], | |
| 1834 | + | "license": "MIT", | |
| 1835 | + | "optional": true, | |
| 1836 | + | "os": [ | |
| 1837 | + | "linux" | |
| 1838 | + | ], | |
| 1839 | + | "engines": { | |
| 1840 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1841 | + | } | |
| 1842 | + | }, | |
| 1843 | + | "node_modules/@rolldown/binding-linux-arm64-musl": { | |
| 1844 | + | "version": "1.2.12", | |
| 1845 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", | |
| 1846 | + | "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", | |
| 1847 | + | "cpu": [ | |
| 1848 | + | "arm64" | |
| 1849 | + | ], | |
| 1850 | + | "dev": true, | |
| 1851 | + | "libc": [ | |
| 1852 | + | "musl" | |
| 1853 | + | ], | |
| 1854 | + | "license": "MIT", | |
| 1855 | + | "optional": true, | |
| 1856 | + | "os": [ | |
| 1857 | + | "linux" | |
| 1858 | + | ], | |
| 1859 | + | "engines": { | |
| 1860 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1861 | + | } | |
| 1862 | + | }, | |
| 1863 | + | "node_modules/@rolldown/binding-linux-ppc64-gnu": { | |
| 1864 | + | "version": "1.2.12", | |
| 1865 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", | |
| 1866 | + | "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", | |
| 1867 | + | "cpu": [ | |
| 1868 | + | "ppc64" | |
| 1869 | + | ], | |
| 1870 | + | "dev": true, | |
| 1871 | + | "libc": [ | |
| 1872 | + | "glibc" | |
| 1873 | + | ], | |
| 1874 | + | "license": "MIT", | |
| 1875 | + | "optional": true, | |
| 1876 | + | "os": [ | |
| 1877 | + | "linux" | |
| 1878 | + | ], | |
| 1879 | + | "engines": { | |
| 1880 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1881 | + | } | |
| 1882 | + | }, | |
| 1883 | + | "node_modules/@rolldown/binding-linux-s390x-gnu": { | |
| 1884 | + | "version": "1.2.12", | |
| 1885 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", | |
| 1886 | + | "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", | |
| 1887 | + | "cpu": [ | |
| 1888 | + | "s390x" | |
| 1889 | + | ], | |
| 1890 | + | "dev": true, | |
| 1891 | + | "libc": [ | |
| 1892 | + | "glibc" | |
| 1893 | + | ], | |
| 1894 | + | "license": "MIT", | |
| 1895 | + | "optional": true, | |
| 1896 | + | "os": [ | |
| 1897 | + | "linux" | |
| 1898 | + | ], | |
| 1899 | + | "engines": { | |
| 1900 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1901 | + | } | |
| 1902 | + | }, | |
| 1903 | + | "node_modules/@rolldown/binding-linux-x64-gnu": { | |
| 1904 | + | "version": "1.2.12", | |
| 1905 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", | |
| 1906 | + | "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", | |
| 1907 | + | "cpu": [ | |
| 1908 | + | "x64" | |
| 1909 | + | ], | |
| 1910 | + | "dev": true, | |
| 1911 | + | "libc": [ | |
| 1912 | + | "glibc" | |
| 1913 | + | ], | |
| 1914 | + | "license": "MIT", | |
| 1915 | + | "optional": true, | |
| 1916 | + | "os": [ | |
| 1917 | + | "linux" | |
| 1918 | + | ], | |
| 1919 | + | "engines": { | |
| 1920 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1921 | + | } | |
| 1922 | + | }, | |
| 1923 | + | "node_modules/@rolldown/binding-linux-x64-musl": { | |
| 1924 | + | "version": "1.2.12", | |
| 1925 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", | |
| 1926 | + | "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", | |
| 1927 | + | "cpu": [ | |
| 1928 | + | "x64" | |
| 1929 | + | ], | |
| 1930 | + | "dev": true, | |
| 1931 | + | "libc": [ | |
| 1932 | + | "musl" | |
| 1933 | + | ], | |
| 1934 | + | "license": "MIT", | |
| 1935 | + | "optional": true, | |
| 1936 | + | "os": [ | |
| 1937 | + | "linux" | |
| 1938 | + | ], | |
| 1939 | + | "engines": { | |
| 1940 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1941 | + | } | |
| 1942 | + | }, | |
| 1943 | + | "node_modules/@rolldown/binding-openharmony-arm64": { | |
| 1944 | + | "version": "1.2.12", | |
| 1945 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", | |
| 1946 | + | "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", | |
| 1947 | + | "cpu": [ | |
| 1948 | + | "arm64" | |
| 1949 | + | ], | |
| 1950 | + | "dev": true, | |
| 1951 | + | "license": "MIT", | |
| 1952 | + | "optional": true, | |
| 1953 | + | "os": [ | |
| 1954 | + | "openharmony" | |
| 1955 | + | ], | |
| 1956 | + | "engines": { | |
| 1957 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1958 | + | } | |
| 1959 | + | }, | |
| 1960 | + | "node_modules/@rolldown/binding-win32-arm64-msvc": { | |
| 1961 | + | "version": "1.2.12", | |
| 1962 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", | |
| 1963 | + | "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", | |
| 1964 | + | "cpu": [ | |
| 1965 | + | "arm64" | |
| 1966 | + | ], | |
| 1967 | + | "dev": true, | |
| 1968 | + | "license": "MIT", | |
| 1969 | + | "optional": true, | |
| 1970 | + | "os": [ | |
| 1971 | + | "win32" | |
| 1972 | + | ], | |
| 1973 | + | "engines": { | |
| 1974 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1975 | + | } | |
| 1976 | + | }, | |
| 1977 | + | "node_modules/@rolldown/binding-win32-x64-msvc": { | |
| 1978 | + | "version": "1.2.12", | |
| 1979 | + | "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", | |
| 1980 | + | "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", | |
| 1981 | + | "cpu": [ | |
| 1982 | + | "x64" | |
| 1983 | + | ], | |
| 1984 | + | "dev": true, | |
| 1985 | + | "license": "MIT", | |
| 1986 | + | "optional": true, | |
| 1987 | + | "os": [ | |
| 1988 | + | "win32" | |
| 1989 | + | ], | |
| 1990 | + | "engines": { | |
| 1991 | + | "node": "^20.19.0 || >=22.12.0" | |
| 1992 | + | } | |
| 1993 | + | }, | |
| 1994 | + | "node_modules/@rolldown/pluginutils": { | |
| 1995 | + | "version": "1.0.1", | |
| 1996 | + | "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", | |
| 1997 | + | "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", | |
| 1998 | + | "dev": true, | |
| 1999 | + | "license": "MIT" | |
| 2000 | + | }, | |
| 2001 | + | "node_modules/@sindresorhus/is": { | |
| 2002 | + | "version": "7.2.0", | |
| 2003 | + | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", | |
| 2004 | + | "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", | |
| 2005 | + | "dev": true, | |
| 2006 | + | "license": "MIT", | |
| 2007 | + | "engines": { | |
| 2008 | + | "node": ">=18" | |
| 2009 | + | }, | |
| 2010 | + | "funding": { | |
| 2011 | + | "url": "https://github.com/sindresorhus/is?sponsor=1" | |
| 2012 | + | } | |
| 2013 | + | }, | |
| 2014 | + | "node_modules/@speed-highlight/core": { | |
| 2015 | + | "version": "1.2.24", | |
| 2016 | + | "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", | |
| 2017 | + | "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", | |
| 2018 | + | "dev": true, | |
| 2019 | + | "license": "CC0-1.0" | |
| 2020 | + | }, | |
| 2021 | + | "node_modules/@tailwindcss/node": { | |
| 2022 | + | "version": "4.3.3", | |
| 2023 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", | |
| 2024 | + | "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", | |
| 2025 | + | "dev": true, | |
| 2026 | + | "license": "MIT", | |
| 2027 | + | "dependencies": { | |
| 2028 | + | "@jridgewell/remapping": "^2.3.5", | |
| 2029 | + | "enhanced-resolve": "^5.24.1", | |
| 2030 | + | "jiti": "^2.7.0", | |
| 2031 | + | "lightningcss": "1.32.0", | |
| 2032 | + | "magic-string": "^0.30.21", | |
| 2033 | + | "source-map-js": "^1.2.1", | |
| 2034 | + | "tailwindcss": "4.3.3" | |
| 2035 | + | } | |
| 2036 | + | }, | |
| 2037 | + | "node_modules/@tailwindcss/oxide": { | |
| 2038 | + | "version": "4.3.3", | |
| 2039 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", | |
| 2040 | + | "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", | |
| 2041 | + | "dev": true, | |
| 2042 | + | "license": "MIT", | |
| 2043 | + | "engines": { | |
| 2044 | + | "node": ">= 20" | |
| 2045 | + | }, | |
| 2046 | + | "optionalDependencies": { | |
| 2047 | + | "@tailwindcss/oxide-android-arm64": "4.3.3", | |
| 2048 | + | "@tailwindcss/oxide-darwin-arm64": "4.3.3", | |
| 2049 | + | "@tailwindcss/oxide-darwin-x64": "4.3.3", | |
| 2050 | + | "@tailwindcss/oxide-freebsd-x64": "4.3.3", | |
| 2051 | + | "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", | |
| 2052 | + | "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", | |
| 2053 | + | "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", | |
| 2054 | + | "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", | |
| 2055 | + | "@tailwindcss/oxide-linux-x64-musl": "4.3.3", | |
| 2056 | + | "@tailwindcss/oxide-wasm32-wasi": "4.3.3", | |
| 2057 | + | "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", | |
| 2058 | + | "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" | |
| 2059 | + | } | |
| 2060 | + | }, | |
| 2061 | + | "node_modules/@tailwindcss/oxide-android-arm64": { | |
| 2062 | + | "version": "4.3.3", | |
| 2063 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", | |
| 2064 | + | "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", | |
| 2065 | + | "cpu": [ | |
| 2066 | + | "arm64" | |
| 2067 | + | ], | |
| 2068 | + | "dev": true, | |
| 2069 | + | "license": "MIT", | |
| 2070 | + | "optional": true, | |
| 2071 | + | "os": [ | |
| 2072 | + | "android" | |
| 2073 | + | ], | |
| 2074 | + | "engines": { | |
| 2075 | + | "node": ">= 20" | |
| 2076 | + | } | |
| 2077 | + | }, | |
| 2078 | + | "node_modules/@tailwindcss/oxide-darwin-arm64": { | |
| 2079 | + | "version": "4.3.3", | |
| 2080 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", | |
| 2081 | + | "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", | |
| 2082 | + | "cpu": [ | |
| 2083 | + | "arm64" | |
| 2084 | + | ], | |
| 2085 | + | "dev": true, | |
| 2086 | + | "license": "MIT", | |
| 2087 | + | "optional": true, | |
| 2088 | + | "os": [ | |
| 2089 | + | "darwin" | |
| 2090 | + | ], | |
| 2091 | + | "engines": { | |
| 2092 | + | "node": ">= 20" | |
| 2093 | + | } | |
| 2094 | + | }, | |
| 2095 | + | "node_modules/@tailwindcss/oxide-darwin-x64": { | |
| 2096 | + | "version": "4.3.3", | |
| 2097 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", | |
| 2098 | + | "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", | |
| 2099 | + | "cpu": [ | |
| 2100 | + | "x64" | |
| 2101 | + | ], | |
| 2102 | + | "dev": true, | |
| 2103 | + | "license": "MIT", | |
| 2104 | + | "optional": true, | |
| 2105 | + | "os": [ | |
| 2106 | + | "darwin" | |
| 2107 | + | ], | |
| 2108 | + | "engines": { | |
| 2109 | + | "node": ">= 20" | |
| 2110 | + | } | |
| 2111 | + | }, | |
| 2112 | + | "node_modules/@tailwindcss/oxide-freebsd-x64": { | |
| 2113 | + | "version": "4.3.3", | |
| 2114 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", | |
| 2115 | + | "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", | |
| 2116 | + | "cpu": [ | |
| 2117 | + | "x64" | |
| 2118 | + | ], | |
| 2119 | + | "dev": true, | |
| 2120 | + | "license": "MIT", | |
| 2121 | + | "optional": true, | |
| 2122 | + | "os": [ | |
| 2123 | + | "freebsd" | |
| 2124 | + | ], | |
| 2125 | + | "engines": { | |
| 2126 | + | "node": ">= 20" | |
| 2127 | + | } | |
| 2128 | + | }, | |
| 2129 | + | "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { | |
| 2130 | + | "version": "4.3.3", | |
| 2131 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", | |
| 2132 | + | "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", | |
| 2133 | + | "cpu": [ | |
| 2134 | + | "arm" | |
| 2135 | + | ], | |
| 2136 | + | "dev": true, | |
| 2137 | + | "license": "MIT", | |
| 2138 | + | "optional": true, | |
| 2139 | + | "os": [ | |
| 2140 | + | "linux" | |
| 2141 | + | ], | |
| 2142 | + | "engines": { | |
| 2143 | + | "node": ">= 20" | |
| 2144 | + | } | |
| 2145 | + | }, | |
| 2146 | + | "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { | |
| 2147 | + | "version": "4.3.3", | |
| 2148 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", | |
| 2149 | + | "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", | |
| 2150 | + | "cpu": [ | |
| 2151 | + | "arm64" | |
| 2152 | + | ], | |
| 2153 | + | "dev": true, | |
| 2154 | + | "libc": [ | |
| 2155 | + | "glibc" | |
| 2156 | + | ], | |
| 2157 | + | "license": "MIT", | |
| 2158 | + | "optional": true, | |
| 2159 | + | "os": [ | |
| 2160 | + | "linux" | |
| 2161 | + | ], | |
| 2162 | + | "engines": { | |
| 2163 | + | "node": ">= 20" | |
| 2164 | + | } | |
| 2165 | + | }, | |
| 2166 | + | "node_modules/@tailwindcss/oxide-linux-arm64-musl": { | |
| 2167 | + | "version": "4.3.3", | |
| 2168 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", | |
| 2169 | + | "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", | |
| 2170 | + | "cpu": [ | |
| 2171 | + | "arm64" | |
| 2172 | + | ], | |
| 2173 | + | "dev": true, | |
| 2174 | + | "libc": [ | |
| 2175 | + | "musl" | |
| 2176 | + | ], | |
| 2177 | + | "license": "MIT", | |
| 2178 | + | "optional": true, | |
| 2179 | + | "os": [ | |
| 2180 | + | "linux" | |
| 2181 | + | ], | |
| 2182 | + | "engines": { | |
| 2183 | + | "node": ">= 20" | |
| 2184 | + | } | |
| 2185 | + | }, | |
| 2186 | + | "node_modules/@tailwindcss/oxide-linux-x64-gnu": { | |
| 2187 | + | "version": "4.3.3", | |
| 2188 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", | |
| 2189 | + | "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", | |
| 2190 | + | "cpu": [ | |
| 2191 | + | "x64" | |
| 2192 | + | ], | |
| 2193 | + | "dev": true, | |
| 2194 | + | "libc": [ | |
| 2195 | + | "glibc" | |
| 2196 | + | ], | |
| 2197 | + | "license": "MIT", | |
| 2198 | + | "optional": true, | |
| 2199 | + | "os": [ | |
| 2200 | + | "linux" | |
| 2201 | + | ], | |
| 2202 | + | "engines": { | |
| 2203 | + | "node": ">= 20" | |
| 2204 | + | } | |
| 2205 | + | }, | |
| 2206 | + | "node_modules/@tailwindcss/oxide-linux-x64-musl": { | |
| 2207 | + | "version": "4.3.3", | |
| 2208 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", | |
| 2209 | + | "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", | |
| 2210 | + | "cpu": [ | |
| 2211 | + | "x64" | |
| 2212 | + | ], | |
| 2213 | + | "dev": true, | |
| 2214 | + | "libc": [ | |
| 2215 | + | "musl" | |
| 2216 | + | ], | |
| 2217 | + | "license": "MIT", | |
| 2218 | + | "optional": true, | |
| 2219 | + | "os": [ | |
| 2220 | + | "linux" | |
| 2221 | + | ], | |
| 2222 | + | "engines": { | |
| 2223 | + | "node": ">= 20" | |
| 2224 | + | } | |
| 2225 | + | }, | |
| 2226 | + | "node_modules/@tailwindcss/oxide-wasm32-wasi": { | |
| 2227 | + | "version": "4.3.3", | |
| 2228 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", | |
| 2229 | + | "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", | |
| 2230 | + | "bundleDependencies": [ | |
| 2231 | + | "@napi-rs/wasm-runtime", | |
| 2232 | + | "@emnapi/core", | |
| 2233 | + | "@emnapi/runtime", | |
| 2234 | + | "@tybys/wasm-util", | |
| 2235 | + | "@emnapi/wasi-threads", | |
| 2236 | + | "tslib" | |
| 2237 | + | ], | |
| 2238 | + | "cpu": [ | |
| 2239 | + | "wasm32" | |
| 2240 | + | ], | |
| 2241 | + | "dev": true, | |
| 2242 | + | "license": "MIT", | |
| 2243 | + | "optional": true, | |
| 2244 | + | "dependencies": { | |
| 2245 | + | "@emnapi/core": "^1.11.1", | |
| 2246 | + | "@emnapi/runtime": "^1.11.1", | |
| 2247 | + | "@emnapi/wasi-threads": "^1.2.2", | |
| 2248 | + | "@napi-rs/wasm-runtime": "^1.1.4", | |
| 2249 | + | "@tybys/wasm-util": "^0.10.2", | |
| 2250 | + | "tslib": "^2.8.1" | |
| 2251 | + | }, | |
| 2252 | + | "engines": { | |
| 2253 | + | "node": ">=14.0.0" | |
| 2254 | + | } | |
| 2255 | + | }, | |
| 2256 | + | "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { | |
| 2257 | + | "version": "4.3.3", | |
| 2258 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", | |
| 2259 | + | "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", | |
| 2260 | + | "cpu": [ | |
| 2261 | + | "arm64" | |
| 2262 | + | ], | |
| 2263 | + | "dev": true, | |
| 2264 | + | "license": "MIT", | |
| 2265 | + | "optional": true, | |
| 2266 | + | "os": [ | |
| 2267 | + | "win32" | |
| 2268 | + | ], | |
| 2269 | + | "engines": { | |
| 2270 | + | "node": ">= 20" | |
| 2271 | + | } | |
| 2272 | + | }, | |
| 2273 | + | "node_modules/@tailwindcss/oxide-win32-x64-msvc": { | |
| 2274 | + | "version": "4.3.3", | |
| 2275 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", | |
| 2276 | + | "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", | |
| 2277 | + | "cpu": [ | |
| 2278 | + | "x64" | |
| 2279 | + | ], | |
| 2280 | + | "dev": true, | |
| 2281 | + | "license": "MIT", | |
| 2282 | + | "optional": true, | |
| 2283 | + | "os": [ | |
| 2284 | + | "win32" | |
| 2285 | + | ], | |
| 2286 | + | "engines": { | |
| 2287 | + | "node": ">= 20" | |
| 2288 | + | } | |
| 2289 | + | }, | |
| 2290 | + | "node_modules/@tailwindcss/vite": { | |
| 2291 | + | "version": "4.3.3", | |
| 2292 | + | "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", | |
| 2293 | + | "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", | |
| 2294 | + | "dev": true, | |
| 2295 | + | "license": "MIT", | |
| 2296 | + | "dependencies": { | |
| 2297 | + | "@tailwindcss/node": "4.3.3", | |
| 2298 | + | "@tailwindcss/oxide": "4.3.3", | |
| 2299 | + | "tailwindcss": "4.3.3" | |
| 2300 | + | }, | |
| 2301 | + | "peerDependencies": { | |
| 2302 | + | "vite": "^5.2.0 || ^6 || ^7 || ^8" | |
| 2303 | + | } | |
| 2304 | + | }, | |
| 2305 | + | "node_modules/@types/node": { | |
| 2306 | + | "version": "22.20.5", | |
| 2307 | + | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", | |
| 2308 | + | "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", | |
| 2309 | + | "dev": true, | |
| 2310 | + | "license": "MIT", | |
| 2311 | + | "dependencies": { | |
| 2312 | + | "undici-types": "~6.21.0" | |
| 2313 | + | } | |
| 2314 | + | }, | |
| 2315 | + | "node_modules/@types/react": { | |
| 2316 | + | "version": "19.3.0", | |
| 2317 | + | "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", | |
| 2318 | + | "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", | |
| 2319 | + | "dev": true, | |
| 2320 | + | "license": "MIT", | |
| 2321 | + | "dependencies": { | |
| 2322 | + | "csstype": "^3.2.2" | |
| 2323 | + | } | |
| 2324 | + | }, | |
| 2325 | + | "node_modules/@types/react-dom": { | |
| 2326 | + | "version": "19.3.0", | |
| 2327 | + | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", | |
| 2328 | + | "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", | |
| 2329 | + | "dev": true, | |
| 2330 | + | "license": "MIT", | |
| 2331 | + | "peerDependencies": { | |
| 2332 | + | "@types/react": "^19.3.0" | |
| 2333 | + | } | |
| 2334 | + | }, | |
| 2335 | + | "node_modules/babel-dead-code-elimination": { | |
| 2336 | + | "version": "1.0.12", | |
| 2337 | + | "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", | |
| 2338 | + | "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", | |
| 2339 | + | "dev": true, | |
| 2340 | + | "license": "MIT", | |
| 2341 | + | "dependencies": { | |
| 2342 | + | "@babel/core": "^7.23.7", | |
| 2343 | + | "@babel/parser": "^7.23.6", | |
| 2344 | + | "@babel/traverse": "^7.23.7", | |
| 2345 | + | "@babel/types": "^7.23.6" | |
| 2346 | + | } | |
| 2347 | + | }, | |
| 2348 | + | "node_modules/baseline-browser-mapping": { | |
| 2349 | + | "version": "2.11.27", | |
| 2350 | + | "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", | |
| 2351 | + | "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", | |
| 2352 | + | "dev": true, | |
| 2353 | + | "license": "Apache-2.0", | |
| 2354 | + | "bin": { | |
| 2355 | + | "baseline-browser-mapping": "dist/cli.cjs" | |
| 2356 | + | }, | |
| 2357 | + | "engines": { | |
| 2358 | + | "node": ">=6.0.0" | |
| 2359 | + | } | |
| 2360 | + | }, | |
| 2361 | + | "node_modules/blake3-wasm": { | |
| 2362 | + | "version": "2.1.5", | |
| 2363 | + | "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", | |
| 2364 | + | "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", | |
| 2365 | + | "dev": true, | |
| 2366 | + | "license": "MIT" | |
| 2367 | + | }, | |
| 2368 | + | "node_modules/browserslist": { | |
| 2369 | + | "version": "4.29.3", | |
| 2370 | + | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", | |
| 2371 | + | "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", | |
| 2372 | + | "dev": true, | |
| 2373 | + | "funding": [ | |
| 2374 | + | { | |
| 2375 | + | "type": "opencollective", | |
| 2376 | + | "url": "https://opencollective.com/browserslist" | |
| 2377 | + | }, | |
| 2378 | + | { | |
| 2379 | + | "type": "tidelift", | |
| 2380 | + | "url": "https://tidelift.com/funding/github/npm/browserslist" | |
| 2381 | + | }, | |
| 2382 | + | { | |
| 2383 | + | "type": "github", | |
| 2384 | + | "url": "https://github.com/sponsors/ai" | |
| 2385 | + | } | |
| 2386 | + | ], | |
| 2387 | + | "license": "MIT", | |
| 2388 | + | "dependencies": { | |
| 2389 | + | "baseline-browser-mapping": "^2.11.26", | |
| 2390 | + | "caniuse-lite": "^1.0.30001813", | |
| 2391 | + | "electron-to-chromium": "^1.5.439", | |
| 2392 | + | "node-releases": "^2.0.57", | |
| 2393 | + | "update-browserslist-db": "^1.3.3" | |
| 2394 | + | }, | |
| 2395 | + | "bin": { | |
| 2396 | + | "browserslist": "cli.js" | |
| 2397 | + | }, | |
| 2398 | + | "engines": { | |
| 2399 | + | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" | |
| 2400 | + | } | |
| 2401 | + | }, | |
| 2402 | + | "node_modules/caniuse-lite": { | |
| 2403 | + | "version": "1.0.30001814", | |
| 2404 | + | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz", | |
| 2405 | + | "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==", | |
| 2406 | + | "dev": true, | |
| 2407 | + | "funding": [ | |
| 2408 | + | { | |
| 2409 | + | "type": "opencollective", | |
| 2410 | + | "url": "https://opencollective.com/browserslist" | |
| 2411 | + | }, | |
| 2412 | + | { | |
| 2413 | + | "type": "tidelift", | |
| 2414 | + | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" | |
| 2415 | + | }, | |
| 2416 | + | { | |
| 2417 | + | "type": "github", | |
| 2418 | + | "url": "https://github.com/sponsors/ai" | |
| 2419 | + | } | |
| 2420 | + | ], | |
| 2421 | + | "license": "CC-BY-4.0" | |
| 2422 | + | }, | |
| 2423 | + | "node_modules/chokidar": { | |
| 2424 | + | "version": "5.0.0", | |
| 2425 | + | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", | |
| 2426 | + | "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", | |
| 2427 | + | "dev": true, | |
| 2428 | + | "license": "MIT", | |
| 2429 | + | "dependencies": { | |
| 2430 | + | "readdirp": "^5.0.0" | |
| 2431 | + | }, | |
| 2432 | + | "engines": { | |
| 2433 | + | "node": ">= 20.19.0" | |
| 2434 | + | }, | |
| 2435 | + | "funding": { | |
| 2436 | + | "url": "https://paulmillr.com/funding/" | |
| 2437 | + | } | |
| 2438 | + | }, | |
| 2439 | + | "node_modules/confbox": { | |
| 2440 | + | "version": "0.3.1", | |
| 2441 | + | "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", | |
| 2442 | + | "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", | |
| 2443 | + | "dev": true, | |
| 2444 | + | "license": "MIT" | |
| 2445 | + | }, | |
| 2446 | + | "node_modules/convert-source-map": { | |
| 2447 | + | "version": "2.0.0", | |
| 2448 | + | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 2449 | + | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 2450 | + | "dev": true, | |
| 2451 | + | "license": "MIT" | |
| 2452 | + | }, | |
| 2453 | + | "node_modules/cookie": { | |
| 2454 | + | "version": "1.1.1", | |
| 2455 | + | "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", | |
| 2456 | + | "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", | |
| 2457 | + | "dev": true, | |
| 2458 | + | "license": "MIT", | |
| 2459 | + | "engines": { | |
| 2460 | + | "node": ">=18" | |
| 2461 | + | }, | |
| 2462 | + | "funding": { | |
| 2463 | + | "type": "opencollective", | |
| 2464 | + | "url": "https://opencollective.com/express" | |
| 2465 | + | } | |
| 2466 | + | }, | |
| 2467 | + | "node_modules/cookie-es": { | |
| 2468 | + | "version": "3.1.1", | |
| 2469 | + | "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", | |
| 2470 | + | "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", | |
| 2471 | + | "license": "MIT" | |
| 2472 | + | }, | |
| 2473 | + | "node_modules/csstype": { | |
| 2474 | + | "version": "3.2.3", | |
| 2475 | + | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", | |
| 2476 | + | "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", | |
| 2477 | + | "dev": true, | |
| 2478 | + | "license": "MIT" | |
| 2479 | + | }, | |
| 2480 | + | "node_modules/debug": { | |
| 2481 | + | "version": "4.4.3", | |
| 2482 | + | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", | |
| 2483 | + | "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", | |
| 2484 | + | "dev": true, | |
| 2485 | + | "license": "MIT", | |
| 2486 | + | "dependencies": { | |
| 2487 | + | "ms": "^2.1.3" | |
| 2488 | + | }, | |
| 2489 | + | "engines": { | |
| 2490 | + | "node": ">=6.0" | |
| 2491 | + | }, | |
| 2492 | + | "peerDependenciesMeta": { | |
| 2493 | + | "supports-color": { | |
| 2494 | + | "optional": true | |
| 2495 | + | } | |
| 2496 | + | } | |
| 2497 | + | }, | |
| 2498 | + | "node_modules/dedent": { | |
| 2499 | + | "version": "1.7.2", | |
| 2500 | + | "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", | |
| 2501 | + | "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", | |
| 2502 | + | "dev": true, | |
| 2503 | + | "license": "MIT", | |
| 2504 | + | "peerDependencies": { | |
| 2505 | + | "babel-plugin-macros": "^3.1.0" | |
| 2506 | + | }, | |
| 2507 | + | "peerDependenciesMeta": { | |
| 2508 | + | "babel-plugin-macros": { | |
| 2509 | + | "optional": true | |
| 2510 | + | } | |
| 2511 | + | } | |
| 2512 | + | }, | |
| 2513 | + | "node_modules/detect-libc": { | |
| 2514 | + | "version": "2.1.2", | |
| 2515 | + | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", | |
| 2516 | + | "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", | |
| 2517 | + | "dev": true, | |
| 2518 | + | "license": "Apache-2.0", | |
| 2519 | + | "engines": { | |
| 2520 | + | "node": ">=8" | |
| 2521 | + | } | |
| 2522 | + | }, | |
| 2523 | + | "node_modules/electron-to-chromium": { | |
| 2524 | + | "version": "1.5.444", | |
| 2525 | + | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz", | |
| 2526 | + | "integrity": "sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==", | |
| 2527 | + | "dev": true, | |
| 2528 | + | "license": "ISC" | |
| 2529 | + | }, | |
| 2530 | + | "node_modules/enhanced-resolve": { | |
| 2531 | + | "version": "5.26.0", | |
| 2532 | + | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", | |
| 2533 | + | "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", | |
| 2534 | + | "dev": true, | |
| 2535 | + | "license": "MIT", | |
| 2536 | + | "dependencies": { | |
| 2537 | + | "graceful-fs": "^4.2.4", | |
| 2538 | + | "tapable": "^2.3.3" | |
| 2539 | + | }, | |
| 2540 | + | "engines": { | |
| 2541 | + | "node": ">=10.13.0" | |
| 2542 | + | } | |
| 2543 | + | }, | |
| 2544 | + | "node_modules/error-stack-parser-es": { | |
| 2545 | + | "version": "1.0.5", | |
| 2546 | + | "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", | |
| 2547 | + | "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", | |
| 2548 | + | "dev": true, | |
| 2549 | + | "license": "MIT", | |
| 2550 | + | "funding": { | |
| 2551 | + | "url": "https://github.com/sponsors/antfu" | |
| 2552 | + | } | |
| 2553 | + | }, | |
| 2554 | + | "node_modules/es-module-lexer": { | |
| 2555 | + | "version": "2.3.2", | |
| 2556 | + | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", | |
| 2557 | + | "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", | |
| 2558 | + | "dev": true, | |
| 2559 | + | "license": "MIT" | |
| 2560 | + | }, | |
| 2561 | + | "node_modules/esbuild": { | |
| 2562 | + | "version": "0.28.1", | |
| 2563 | + | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", | |
| 2564 | + | "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", | |
| 2565 | + | "dev": true, | |
| 2566 | + | "hasInstallScript": true, | |
| 2567 | + | "license": "MIT", | |
| 2568 | + | "bin": { | |
| 2569 | + | "esbuild": "bin/esbuild" | |
| 2570 | + | }, | |
| 2571 | + | "engines": { | |
| 2572 | + | "node": ">=18" | |
| 2573 | + | }, | |
| 2574 | + | "optionalDependencies": { | |
| 2575 | + | "@esbuild/aix-ppc64": "0.28.1", | |
| 2576 | + | "@esbuild/android-arm": "0.28.1", | |
| 2577 | + | "@esbuild/android-arm64": "0.28.1", | |
| 2578 | + | "@esbuild/android-x64": "0.28.1", | |
| 2579 | + | "@esbuild/darwin-arm64": "0.28.1", | |
| 2580 | + | "@esbuild/darwin-x64": "0.28.1", | |
| 2581 | + | "@esbuild/freebsd-arm64": "0.28.1", | |
| 2582 | + | "@esbuild/freebsd-x64": "0.28.1", | |
| 2583 | + | "@esbuild/linux-arm": "0.28.1", | |
| 2584 | + | "@esbuild/linux-arm64": "0.28.1", | |
| 2585 | + | "@esbuild/linux-ia32": "0.28.1", | |
| 2586 | + | "@esbuild/linux-loong64": "0.28.1", | |
| 2587 | + | "@esbuild/linux-mips64el": "0.28.1", | |
| 2588 | + | "@esbuild/linux-ppc64": "0.28.1", | |
| 2589 | + | "@esbuild/linux-riscv64": "0.28.1", | |
| 2590 | + | "@esbuild/linux-s390x": "0.28.1", | |
| 2591 | + | "@esbuild/linux-x64": "0.28.1", | |
| 2592 | + | "@esbuild/netbsd-arm64": "0.28.1", | |
| 2593 | + | "@esbuild/netbsd-x64": "0.28.1", | |
| 2594 | + | "@esbuild/openbsd-arm64": "0.28.1", | |
| 2595 | + | "@esbuild/openbsd-x64": "0.28.1", | |
| 2596 | + | "@esbuild/openharmony-arm64": "0.28.1", | |
| 2597 | + | "@esbuild/sunos-x64": "0.28.1", | |
| 2598 | + | "@esbuild/win32-arm64": "0.28.1", | |
| 2599 | + | "@esbuild/win32-ia32": "0.28.1", | |
| 2600 | + | "@esbuild/win32-x64": "0.28.1" | |
| 2601 | + | } | |
| 2602 | + | }, | |
| 2603 | + | "node_modules/escalade": { | |
| 2604 | + | "version": "3.2.0", | |
| 2605 | + | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", | |
| 2606 | + | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", | |
| 2607 | + | "dev": true, | |
| 2608 | + | "license": "MIT", | |
| 2609 | + | "engines": { | |
| 2610 | + | "node": ">=6" | |
| 2611 | + | } | |
| 2612 | + | }, | |
| 2613 | + | "node_modules/exit-hook": { | |
| 2614 | + | "version": "5.1.0", | |
| 2615 | + | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-5.1.0.tgz", | |
| 2616 | + | "integrity": "sha512-INjr2xyxHo7bhAqf5ong++GZPPnpcuBcaXUKt03yf7Fie9yWD7FapL4teOU0+awQazGs5ucBh7xWs/AD+6nhog==", | |
| 2617 | + | "dev": true, | |
| 2618 | + | "license": "MIT", | |
| 2619 | + | "engines": { | |
| 2620 | + | "node": ">=20" | |
| 2621 | + | }, | |
| 2622 | + | "funding": { | |
| 2623 | + | "url": "https://github.com/sponsors/sindresorhus" | |
| 2624 | + | } | |
| 2625 | + | }, | |
| 2626 | + | "node_modules/exsolve": { | |
| 2627 | + | "version": "1.1.1", | |
| 2628 | + | "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", | |
| 2629 | + | "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", | |
| 2630 | + | "dev": true, | |
| 2631 | + | "license": "MIT" | |
| 2632 | + | }, | |
| 2633 | + | "node_modules/fdir": { | |
| 2634 | + | "version": "6.5.0", | |
| 2635 | + | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", | |
| 2636 | + | "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", | |
| 2637 | + | "dev": true, | |
| 2638 | + | "license": "MIT", | |
| 2639 | + | "engines": { | |
| 2640 | + | "node": ">=12.0.0" | |
| 2641 | + | }, | |
| 2642 | + | "peerDependencies": { | |
| 2643 | + | "picomatch": "^3 || ^4" | |
| 2644 | + | }, | |
| 2645 | + | "peerDependenciesMeta": { | |
| 2646 | + | "picomatch": { | |
| 2647 | + | "optional": true | |
| 2648 | + | } | |
| 2649 | + | } | |
| 2650 | + | }, | |
| 2651 | + | "node_modules/fsevents": { | |
| 2652 | + | "version": "2.3.3", | |
| 2653 | + | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", | |
| 2654 | + | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", | |
| 2655 | + | "dev": true, | |
| 2656 | + | "hasInstallScript": true, | |
| 2657 | + | "license": "MIT", | |
| 2658 | + | "optional": true, | |
| 2659 | + | "os": [ | |
| 2660 | + | "darwin" | |
| 2661 | + | ], | |
| 2662 | + | "engines": { | |
| 2663 | + | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" | |
| 2664 | + | } | |
| 2665 | + | }, | |
| 2666 | + | "node_modules/gensync": { | |
| 2667 | + | "version": "1.0.0-beta.2", | |
| 2668 | + | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", | |
| 2669 | + | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", | |
| 2670 | + | "dev": true, | |
| 2671 | + | "license": "MIT", | |
| 2672 | + | "engines": { | |
| 2673 | + | "node": ">=6.9.0" | |
| 2674 | + | } | |
| 2675 | + | }, | |
| 2676 | + | "node_modules/graceful-fs": { | |
| 2677 | + | "version": "4.2.11", | |
| 2678 | + | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", | |
| 2679 | + | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", | |
| 2680 | + | "dev": true, | |
| 2681 | + | "license": "ISC" | |
| 2682 | + | }, | |
| 2683 | + | "node_modules/isbot": { | |
| 2684 | + | "version": "5.2.2", | |
| 2685 | + | "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.2.2.tgz", | |
| 2686 | + | "integrity": "sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==", | |
| 2687 | + | "license": "Unlicense", | |
| 2688 | + | "engines": { | |
| 2689 | + | "node": ">=18" | |
| 2690 | + | } | |
| 2691 | + | }, | |
| 2692 | + | "node_modules/jiti": { | |
| 2693 | + | "version": "2.7.0", | |
| 2694 | + | "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", | |
| 2695 | + | "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", | |
| 2696 | + | "dev": true, | |
| 2697 | + | "license": "MIT", | |
| 2698 | + | "bin": { | |
| 2699 | + | "jiti": "lib/jiti-cli.mjs" | |
| 2700 | + | } | |
| 2701 | + | }, | |
| 2702 | + | "node_modules/js-tokens": { | |
| 2703 | + | "version": "4.0.0", | |
| 2704 | + | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", | |
| 2705 | + | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", | |
| 2706 | + | "dev": true, | |
| 2707 | + | "license": "MIT" | |
| 2708 | + | }, | |
| 2709 | + | "node_modules/jsesc": { | |
| 2710 | + | "version": "3.1.0", | |
| 2711 | + | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", | |
| 2712 | + | "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", | |
| 2713 | + | "dev": true, | |
| 2714 | + | "license": "MIT", | |
| 2715 | + | "bin": { | |
| 2716 | + | "jsesc": "bin/jsesc" | |
| 2717 | + | }, | |
| 2718 | + | "engines": { | |
| 2719 | + | "node": ">=6" | |
| 2720 | + | } | |
| 2721 | + | }, | |
| 2722 | + | "node_modules/json5": { | |
| 2723 | + | "version": "2.2.3", | |
| 2724 | + | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", | |
| 2725 | + | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", | |
| 2726 | + | "dev": true, | |
| 2727 | + | "license": "MIT", | |
| 2728 | + | "bin": { | |
| 2729 | + | "json5": "lib/cli.js" | |
| 2730 | + | }, | |
| 2731 | + | "engines": { | |
| 2732 | + | "node": ">=6" | |
| 2733 | + | } | |
| 2734 | + | }, | |
| 2735 | + | "node_modules/kleur": { | |
| 2736 | + | "version": "4.1.5", | |
| 2737 | + | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", | |
| 2738 | + | "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", | |
| 2739 | + | "dev": true, | |
| 2740 | + | "license": "MIT", | |
| 2741 | + | "engines": { | |
| 2742 | + | "node": ">=6" | |
| 2743 | + | } | |
| 2744 | + | }, | |
| 2745 | + | "node_modules/lightningcss": { | |
| 2746 | + | "version": "1.32.0", | |
| 2747 | + | "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", | |
| 2748 | + | "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", | |
| 2749 | + | "dev": true, | |
| 2750 | + | "license": "MPL-2.0", | |
| 2751 | + | "dependencies": { | |
| 2752 | + | "detect-libc": "^2.0.3" | |
| 2753 | + | }, | |
| 2754 | + | "engines": { | |
| 2755 | + | "node": ">= 12.0.0" | |
| 2756 | + | }, | |
| 2757 | + | "funding": { | |
| 2758 | + | "type": "opencollective", | |
| 2759 | + | "url": "https://opencollective.com/parcel" | |
| 2760 | + | }, | |
| 2761 | + | "optionalDependencies": { | |
| 2762 | + | "lightningcss-android-arm64": "1.32.0", | |
| 2763 | + | "lightningcss-darwin-arm64": "1.32.0", | |
| 2764 | + | "lightningcss-darwin-x64": "1.32.0", | |
| 2765 | + | "lightningcss-freebsd-x64": "1.32.0", | |
| 2766 | + | "lightningcss-linux-arm-gnueabihf": "1.32.0", | |
| 2767 | + | "lightningcss-linux-arm64-gnu": "1.32.0", | |
| 2768 | + | "lightningcss-linux-arm64-musl": "1.32.0", | |
| 2769 | + | "lightningcss-linux-x64-gnu": "1.32.0", | |
| 2770 | + | "lightningcss-linux-x64-musl": "1.32.0", | |
| 2771 | + | "lightningcss-win32-arm64-msvc": "1.32.0", | |
| 2772 | + | "lightningcss-win32-x64-msvc": "1.32.0" | |
| 2773 | + | } | |
| 2774 | + | }, | |
| 2775 | + | "node_modules/lightningcss-android-arm64": { | |
| 2776 | + | "version": "1.32.0", | |
| 2777 | + | "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", | |
| 2778 | + | "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", | |
| 2779 | + | "cpu": [ | |
| 2780 | + | "arm64" | |
| 2781 | + | ], | |
| 2782 | + | "dev": true, | |
| 2783 | + | "license": "MPL-2.0", | |
| 2784 | + | "optional": true, | |
| 2785 | + | "os": [ | |
| 2786 | + | "android" | |
| 2787 | + | ], | |
| 2788 | + | "engines": { | |
| 2789 | + | "node": ">= 12.0.0" | |
| 2790 | + | }, | |
| 2791 | + | "funding": { | |
| 2792 | + | "type": "opencollective", | |
| 2793 | + | "url": "https://opencollective.com/parcel" | |
| 2794 | + | } | |
| 2795 | + | }, | |
| 2796 | + | "node_modules/lightningcss-darwin-arm64": { | |
| 2797 | + | "version": "1.32.0", | |
| 2798 | + | "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", | |
| 2799 | + | "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", | |
| 2800 | + | "cpu": [ | |
| 2801 | + | "arm64" | |
| 2802 | + | ], | |
| 2803 | + | "dev": true, | |
| 2804 | + | "license": "MPL-2.0", | |
| 2805 | + | "optional": true, | |
| 2806 | + | "os": [ | |
| 2807 | + | "darwin" | |
| 2808 | + | ], | |
| 2809 | + | "engines": { | |
| 2810 | + | "node": ">= 12.0.0" | |
| 2811 | + | }, | |
| 2812 | + | "funding": { | |
| 2813 | + | "type": "opencollective", | |
| 2814 | + | "url": "https://opencollective.com/parcel" | |
| 2815 | + | } | |
| 2816 | + | }, | |
| 2817 | + | "node_modules/lightningcss-darwin-x64": { | |
| 2818 | + | "version": "1.32.0", | |
| 2819 | + | "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", | |
| 2820 | + | "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", | |
| 2821 | + | "cpu": [ | |
| 2822 | + | "x64" | |
| 2823 | + | ], | |
| 2824 | + | "dev": true, | |
| 2825 | + | "license": "MPL-2.0", | |
| 2826 | + | "optional": true, | |
| 2827 | + | "os": [ | |
| 2828 | + | "darwin" | |
| 2829 | + | ], | |
| 2830 | + | "engines": { | |
| 2831 | + | "node": ">= 12.0.0" | |
| 2832 | + | }, | |
| 2833 | + | "funding": { | |
| 2834 | + | "type": "opencollective", | |
| 2835 | + | "url": "https://opencollective.com/parcel" | |
| 2836 | + | } | |
| 2837 | + | }, | |
| 2838 | + | "node_modules/lightningcss-freebsd-x64": { | |
| 2839 | + | "version": "1.32.0", | |
| 2840 | + | "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", | |
| 2841 | + | "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", | |
| 2842 | + | "cpu": [ | |
| 2843 | + | "x64" | |
| 2844 | + | ], | |
| 2845 | + | "dev": true, | |
| 2846 | + | "license": "MPL-2.0", | |
| 2847 | + | "optional": true, | |
| 2848 | + | "os": [ | |
| 2849 | + | "freebsd" | |
| 2850 | + | ], | |
| 2851 | + | "engines": { | |
| 2852 | + | "node": ">= 12.0.0" | |
| 2853 | + | }, | |
| 2854 | + | "funding": { | |
| 2855 | + | "type": "opencollective", | |
| 2856 | + | "url": "https://opencollective.com/parcel" | |
| 2857 | + | } | |
| 2858 | + | }, | |
| 2859 | + | "node_modules/lightningcss-linux-arm-gnueabihf": { | |
| 2860 | + | "version": "1.32.0", | |
| 2861 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", | |
| 2862 | + | "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", | |
| 2863 | + | "cpu": [ | |
| 2864 | + | "arm" | |
| 2865 | + | ], | |
| 2866 | + | "dev": true, | |
| 2867 | + | "license": "MPL-2.0", | |
| 2868 | + | "optional": true, | |
| 2869 | + | "os": [ | |
| 2870 | + | "linux" | |
| 2871 | + | ], | |
| 2872 | + | "engines": { | |
| 2873 | + | "node": ">= 12.0.0" | |
| 2874 | + | }, | |
| 2875 | + | "funding": { | |
| 2876 | + | "type": "opencollective", | |
| 2877 | + | "url": "https://opencollective.com/parcel" | |
| 2878 | + | } | |
| 2879 | + | }, | |
| 2880 | + | "node_modules/lightningcss-linux-arm64-gnu": { | |
| 2881 | + | "version": "1.32.0", | |
| 2882 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", | |
| 2883 | + | "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", | |
| 2884 | + | "cpu": [ | |
| 2885 | + | "arm64" | |
| 2886 | + | ], | |
| 2887 | + | "dev": true, | |
| 2888 | + | "libc": [ | |
| 2889 | + | "glibc" | |
| 2890 | + | ], | |
| 2891 | + | "license": "MPL-2.0", | |
| 2892 | + | "optional": true, | |
| 2893 | + | "os": [ | |
| 2894 | + | "linux" | |
| 2895 | + | ], | |
| 2896 | + | "engines": { | |
| 2897 | + | "node": ">= 12.0.0" | |
| 2898 | + | }, | |
| 2899 | + | "funding": { | |
| 2900 | + | "type": "opencollective", | |
| 2901 | + | "url": "https://opencollective.com/parcel" | |
| 2902 | + | } | |
| 2903 | + | }, | |
| 2904 | + | "node_modules/lightningcss-linux-arm64-musl": { | |
| 2905 | + | "version": "1.32.0", | |
| 2906 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", | |
| 2907 | + | "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", | |
| 2908 | + | "cpu": [ | |
| 2909 | + | "arm64" | |
| 2910 | + | ], | |
| 2911 | + | "dev": true, | |
| 2912 | + | "libc": [ | |
| 2913 | + | "musl" | |
| 2914 | + | ], | |
| 2915 | + | "license": "MPL-2.0", | |
| 2916 | + | "optional": true, | |
| 2917 | + | "os": [ | |
| 2918 | + | "linux" | |
| 2919 | + | ], | |
| 2920 | + | "engines": { | |
| 2921 | + | "node": ">= 12.0.0" | |
| 2922 | + | }, | |
| 2923 | + | "funding": { | |
| 2924 | + | "type": "opencollective", | |
| 2925 | + | "url": "https://opencollective.com/parcel" | |
| 2926 | + | } | |
| 2927 | + | }, | |
| 2928 | + | "node_modules/lightningcss-linux-x64-gnu": { | |
| 2929 | + | "version": "1.32.0", | |
| 2930 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", | |
| 2931 | + | "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", | |
| 2932 | + | "cpu": [ | |
| 2933 | + | "x64" | |
| 2934 | + | ], | |
| 2935 | + | "dev": true, | |
| 2936 | + | "libc": [ | |
| 2937 | + | "glibc" | |
| 2938 | + | ], | |
| 2939 | + | "license": "MPL-2.0", | |
| 2940 | + | "optional": true, | |
| 2941 | + | "os": [ | |
| 2942 | + | "linux" | |
| 2943 | + | ], | |
| 2944 | + | "engines": { | |
| 2945 | + | "node": ">= 12.0.0" | |
| 2946 | + | }, | |
| 2947 | + | "funding": { | |
| 2948 | + | "type": "opencollective", | |
| 2949 | + | "url": "https://opencollective.com/parcel" | |
| 2950 | + | } | |
| 2951 | + | }, | |
| 2952 | + | "node_modules/lightningcss-linux-x64-musl": { | |
| 2953 | + | "version": "1.32.0", | |
| 2954 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", | |
| 2955 | + | "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", | |
| 2956 | + | "cpu": [ | |
| 2957 | + | "x64" | |
| 2958 | + | ], | |
| 2959 | + | "dev": true, | |
| 2960 | + | "libc": [ | |
| 2961 | + | "musl" | |
| 2962 | + | ], | |
| 2963 | + | "license": "MPL-2.0", | |
| 2964 | + | "optional": true, | |
| 2965 | + | "os": [ | |
| 2966 | + | "linux" | |
| 2967 | + | ], | |
| 2968 | + | "engines": { | |
| 2969 | + | "node": ">= 12.0.0" | |
| 2970 | + | }, | |
| 2971 | + | "funding": { | |
| 2972 | + | "type": "opencollective", | |
| 2973 | + | "url": "https://opencollective.com/parcel" | |
| 2974 | + | } | |
| 2975 | + | }, | |
| 2976 | + | "node_modules/lightningcss-win32-arm64-msvc": { | |
| 2977 | + | "version": "1.32.0", | |
| 2978 | + | "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", | |
| 2979 | + | "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", | |
| 2980 | + | "cpu": [ | |
| 2981 | + | "arm64" | |
| 2982 | + | ], | |
| 2983 | + | "dev": true, | |
| 2984 | + | "license": "MPL-2.0", | |
| 2985 | + | "optional": true, | |
| 2986 | + | "os": [ | |
| 2987 | + | "win32" | |
| 2988 | + | ], | |
| 2989 | + | "engines": { | |
| 2990 | + | "node": ">= 12.0.0" | |
| 2991 | + | }, | |
| 2992 | + | "funding": { | |
| 2993 | + | "type": "opencollective", | |
| 2994 | + | "url": "https://opencollective.com/parcel" | |
| 2995 | + | } | |
| 2996 | + | }, | |
| 2997 | + | "node_modules/lightningcss-win32-x64-msvc": { | |
| 2998 | + | "version": "1.32.0", | |
| 2999 | + | "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", | |
| 3000 | + | "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", | |
| 3001 | + | "cpu": [ | |
| 3002 | + | "x64" | |
| 3003 | + | ], | |
| 3004 | + | "dev": true, | |
| 3005 | + | "license": "MPL-2.0", | |
| 3006 | + | "optional": true, | |
| 3007 | + | "os": [ | |
| 3008 | + | "win32" | |
| 3009 | + | ], | |
| 3010 | + | "engines": { | |
| 3011 | + | "node": ">= 12.0.0" | |
| 3012 | + | }, | |
| 3013 | + | "funding": { | |
| 3014 | + | "type": "opencollective", | |
| 3015 | + | "url": "https://opencollective.com/parcel" | |
| 3016 | + | } | |
| 3017 | + | }, | |
| 3018 | + | "node_modules/lodash": { | |
| 3019 | + | "version": "4.18.1", | |
| 3020 | + | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", | |
| 3021 | + | "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", | |
| 3022 | + | "dev": true, | |
| 3023 | + | "license": "MIT" | |
| 3024 | + | }, | |
| 3025 | + | "node_modules/lru-cache": { | |
| 3026 | + | "version": "5.1.1", | |
| 3027 | + | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", | |
| 3028 | + | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", | |
| 3029 | + | "dev": true, | |
| 3030 | + | "license": "ISC", | |
| 3031 | + | "dependencies": { | |
| 3032 | + | "yallist": "^3.0.2" | |
| 3033 | + | } | |
| 3034 | + | }, | |
| 3035 | + | "node_modules/magic-string": { | |
| 3036 | + | "version": "0.30.21", | |
| 3037 | + | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", | |
| 3038 | + | "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", | |
| 3039 | + | "dev": true, | |
| 3040 | + | "license": "MIT", | |
| 3041 | + | "dependencies": { | |
| 3042 | + | "@jridgewell/sourcemap-codec": "^1.5.5" | |
| 3043 | + | } | |
| 3044 | + | }, | |
| 3045 | + | "node_modules/miniflare": { | |
| 3046 | + | "version": "5.20261001.0-alpha", | |
| 3047 | + | "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261001.0-alpha.tgz", | |
| 3048 | + | "integrity": "sha512-GaimS5mSIOMyvd16ga+e1/QkI8cmp3z35QPHFex0DktqNQf2RVZQwMPQXdyoUreUiFP/0Gpe2MoQInTyMAD5xA==", | |
| 3049 | + | "dev": true, | |
| 3050 | + | "license": "MIT", | |
| 3051 | + | "dependencies": { | |
| 3052 | + | "@cspotcode/source-map-support": "0.8.1", | |
| 3053 | + | "sharp": "0.35.4", | |
| 3054 | + | "undici": "7.29.1", | |
| 3055 | + | "workerd": "1.20261001.1", | |
| 3056 | + | "ws": "8.21.0", | |
| 3057 | + | "youch": "4.1.0-beta.10" | |
| 3058 | + | }, | |
| 3059 | + | "engines": { | |
| 3060 | + | "node": ">=22.0.0" | |
| 3061 | + | } | |
| 3062 | + | }, | |
| 3063 | + | "node_modules/ms": { | |
| 3064 | + | "version": "2.1.3", | |
| 3065 | + | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | |
| 3066 | + | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | |
| 3067 | + | "dev": true, | |
| 3068 | + | "license": "MIT" | |
| 3069 | + | }, | |
| 3070 | + | "node_modules/nanoid": { | |
| 3071 | + | "version": "3.3.19", | |
| 3072 | + | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", | |
| 3073 | + | "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", | |
| 3074 | + | "dev": true, | |
| 3075 | + | "funding": [ | |
| 3076 | + | { | |
| 3077 | + | "type": "github", | |
| 3078 | + | "url": "https://github.com/sponsors/ai" | |
| 3079 | + | } | |
| 3080 | + | ], | |
| 3081 | + | "license": "MIT", | |
| 3082 | + | "bin": { | |
| 3083 | + | "nanoid": "bin/nanoid.cjs" | |
| 3084 | + | }, | |
| 3085 | + | "engines": { | |
| 3086 | + | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" | |
| 3087 | + | } | |
| 3088 | + | }, | |
| 3089 | + | "node_modules/node-releases": { | |
| 3090 | + | "version": "2.0.57", | |
| 3091 | + | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", | |
| 3092 | + | "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", | |
| 3093 | + | "dev": true, | |
| 3094 | + | "license": "MIT", | |
| 3095 | + | "engines": { | |
| 3096 | + | "node": ">=18" | |
| 3097 | + | } | |
| 3098 | + | }, | |
| 3099 | + | "node_modules/p-map": { | |
| 3100 | + | "version": "7.0.8", | |
| 3101 | + | "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", | |
| 3102 | + | "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==", | |
| 3103 | + | "dev": true, | |
| 3104 | + | "license": "MIT", | |
| 3105 | + | "engines": { | |
| 3106 | + | "node": ">=18" | |
| 3107 | + | }, | |
| 3108 | + | "funding": { | |
| 3109 | + | "url": "https://github.com/sponsors/sindresorhus" | |
| 3110 | + | } | |
| 3111 | + | }, | |
| 3112 | + | "node_modules/path-to-regexp": { | |
| 3113 | + | "version": "6.3.0", | |
| 3114 | + | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", | |
| 3115 | + | "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", | |
| 3116 | + | "dev": true, | |
| 3117 | + | "license": "MIT" | |
| 3118 | + | }, | |
| 3119 | + | "node_modules/pathe": { | |
| 3120 | + | "version": "2.0.3", | |
| 3121 | + | "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", | |
| 3122 | + | "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", | |
| 3123 | + | "dev": true, | |
| 3124 | + | "license": "MIT" | |
| 3125 | + | }, | |
| 3126 | + | "node_modules/picocolors": { | |
| 3127 | + | "version": "1.1.1", | |
| 3128 | + | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | |
| 3129 | + | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", | |
| 3130 | + | "dev": true, | |
| 3131 | + | "license": "ISC" | |
| 3132 | + | }, | |
| 3133 | + | "node_modules/picomatch": { | |
| 3134 | + | "version": "4.0.7", | |
| 3135 | + | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", | |
| 3136 | + | "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", | |
| 3137 | + | "dev": true, | |
| 3138 | + | "license": "MIT", | |
| 3139 | + | "engines": { | |
| 3140 | + | "node": ">=12" | |
| 3141 | + | }, | |
| 3142 | + | "funding": { | |
| 3143 | + | "url": "https://github.com/sponsors/jonschlinkert" | |
| 3144 | + | } | |
| 3145 | + | }, | |
| 3146 | + | "node_modules/pkg-types": { | |
| 3147 | + | "version": "2.3.3", | |
| 3148 | + | "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", | |
| 3149 | + | "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", | |
| 3150 | + | "dev": true, | |
| 3151 | + | "license": "MIT", | |
| 3152 | + | "dependencies": { | |
| 3153 | + | "confbox": "^0.3.1", | |
| 3154 | + | "exsolve": "^1.1.1", | |
| 3155 | + | "pathe": "^2.0.3" | |
| 3156 | + | } | |
| 3157 | + | }, | |
| 3158 | + | "node_modules/postcss": { | |
| 3159 | + | "version": "8.5.28", | |
| 3160 | + | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", | |
| 3161 | + | "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", | |
| 3162 | + | "dev": true, | |
| 3163 | + | "funding": [ | |
| 3164 | + | { | |
| 3165 | + | "type": "opencollective", | |
| 3166 | + | "url": "https://opencollective.com/postcss/" | |
| 3167 | + | }, | |
| 3168 | + | { | |
| 3169 | + | "type": "tidelift", | |
| 3170 | + | "url": "https://tidelift.com/funding/github/npm/postcss" | |
| 3171 | + | }, | |
| 3172 | + | { | |
| 3173 | + | "type": "github", | |
| 3174 | + | "url": "https://github.com/sponsors/ai" | |
| 3175 | + | } | |
| 3176 | + | ], | |
| 3177 | + | "license": "MIT", | |
| 3178 | + | "dependencies": { | |
| 3179 | + | "nanoid": "^3.3.18", | |
| 3180 | + | "picocolors": "^1.1.1", | |
| 3181 | + | "source-map-js": "^1.2.1" | |
| 3182 | + | }, | |
| 3183 | + | "engines": { | |
| 3184 | + | "node": "^10 || ^12 || >=14" | |
| 3185 | + | } | |
| 3186 | + | }, | |
| 3187 | + | "node_modules/react": { | |
| 3188 | + | "version": "19.3.0", | |
| 3189 | + | "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", | |
| 3190 | + | "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", | |
| 3191 | + | "license": "MIT", | |
| 3192 | + | "engines": { | |
| 3193 | + | "node": ">=0.10.0" | |
| 3194 | + | } | |
| 3195 | + | }, | |
| 3196 | + | "node_modules/react-dom": { | |
| 3197 | + | "version": "19.3.0", | |
| 3198 | + | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", | |
| 3199 | + | "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", | |
| 3200 | + | "license": "MIT", | |
| 3201 | + | "dependencies": { | |
| 3202 | + | "scheduler": "^0.28.0" | |
| 3203 | + | }, | |
| 3204 | + | "peerDependencies": { | |
| 3205 | + | "react": "^19.3.0" | |
| 3206 | + | } | |
| 3207 | + | }, | |
| 3208 | + | "node_modules/react-refresh": { | |
| 3209 | + | "version": "0.18.0", | |
| 3210 | + | "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", | |
| 3211 | + | "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", | |
| 3212 | + | "dev": true, | |
| 3213 | + | "license": "MIT", | |
| 3214 | + | "engines": { | |
| 3215 | + | "node": ">=0.10.0" | |
| 3216 | + | } | |
| 3217 | + | }, | |
| 3218 | + | "node_modules/react-router": { | |
| 3219 | + | "version": "8.4.0", | |
| 3220 | + | "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.4.0.tgz", | |
| 3221 | + | "integrity": "sha512-JtydAkBvU9UTEe5qNC+POhu+ZBNM7rlKY2IegwXc7Idj7xfRG16x5RZrw3mju+XlqBxfvb2ky9P3jUp9WyWC1g==", | |
| 3222 | + | "license": "MIT", | |
| 3223 | + | "dependencies": { | |
| 3224 | + | "@remix-run/route-pattern": "^0.22.1", | |
| 3225 | + | "cookie-es": "^3.1.1" | |
| 3226 | + | }, | |
| 3227 | + | "engines": { | |
| 3228 | + | "node": ">=22.22.0" | |
| 3229 | + | }, | |
| 3230 | + | "peerDependencies": { | |
| 3231 | + | "react": ">=19.2.7", | |
| 3232 | + | "react-dom": ">=19.2.7" | |
| 3233 | + | }, | |
| 3234 | + | "peerDependenciesMeta": { | |
| 3235 | + | "react-dom": { | |
| 3236 | + | "optional": true | |
| 3237 | + | } | |
| 3238 | + | } | |
| 3239 | + | }, | |
| 3240 | + | "node_modules/readdirp": { | |
| 3241 | + | "version": "5.1.1", | |
| 3242 | + | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", | |
| 3243 | + | "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", | |
| 3244 | + | "dev": true, | |
| 3245 | + | "license": "MIT", | |
| 3246 | + | "engines": { | |
| 3247 | + | "node": ">= 20.19.0" | |
| 3248 | + | }, | |
| 3249 | + | "funding": { | |
| 3250 | + | "type": "individual", | |
| 3251 | + | "url": "https://paulmillr.com/funding/" | |
| 3252 | + | } | |
| 3253 | + | }, | |
| 3254 | + | "node_modules/rolldown": { | |
| 3255 | + | "version": "1.2.12", | |
| 3256 | + | "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", | |
| 3257 | + | "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", | |
| 3258 | + | "dev": true, | |
| 3259 | + | "license": "MIT", | |
| 3260 | + | "dependencies": { | |
| 3261 | + | "@oxc-project/types": "=0.152.0", | |
| 3262 | + | "@rolldown/pluginutils": "^1.0.0" | |
| 3263 | + | }, | |
| 3264 | + | "bin": { | |
| 3265 | + | "rolldown": "bin/cli.mjs" | |
| 3266 | + | }, | |
| 3267 | + | "engines": { | |
| 3268 | + | "node": "^20.19.0 || >=22.12.0" | |
| 3269 | + | }, | |
| 3270 | + | "optionalDependencies": { | |
| 3271 | + | "@rolldown/binding-android-arm-eabi": "1.2.12", | |
| 3272 | + | "@rolldown/binding-android-arm64": "1.2.12", | |
| 3273 | + | "@rolldown/binding-darwin-arm64": "1.2.12", | |
| 3274 | + | "@rolldown/binding-darwin-x64": "1.2.12", | |
| 3275 | + | "@rolldown/binding-freebsd-x64": "1.2.12", | |
| 3276 | + | "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", | |
| 3277 | + | "@rolldown/binding-linux-arm64-gnu": "1.2.12", | |
| 3278 | + | "@rolldown/binding-linux-arm64-musl": "1.2.12", | |
| 3279 | + | "@rolldown/binding-linux-ppc64-gnu": "1.2.12", | |
| 3280 | + | "@rolldown/binding-linux-s390x-gnu": "1.2.12", | |
| 3281 | + | "@rolldown/binding-linux-x64-gnu": "1.2.12", | |
| 3282 | + | "@rolldown/binding-linux-x64-musl": "1.2.12", | |
| 3283 | + | "@rolldown/binding-openharmony-arm64": "1.2.12", | |
| 3284 | + | "@rolldown/binding-win32-arm64-msvc": "1.2.12", | |
| 3285 | + | "@rolldown/binding-win32-x64-msvc": "1.2.12" | |
| 3286 | + | } | |
| 3287 | + | }, | |
| 3288 | + | "node_modules/scheduler": { | |
| 3289 | + | "version": "0.28.0", | |
| 3290 | + | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", | |
| 3291 | + | "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", | |
| 3292 | + | "license": "MIT" | |
| 3293 | + | }, | |
| 3294 | + | "node_modules/semver": { | |
| 3295 | + | "version": "7.8.5", | |
| 3296 | + | "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", | |
| 3297 | + | "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", | |
| 3298 | + | "dev": true, | |
| 3299 | + | "license": "ISC", | |
| 3300 | + | "bin": { | |
| 3301 | + | "semver": "bin/semver.js" | |
| 3302 | + | }, | |
| 3303 | + | "engines": { | |
| 3304 | + | "node": ">=10" | |
| 3305 | + | } | |
| 3306 | + | }, | |
| 3307 | + | "node_modules/sharp": { | |
| 3308 | + | "version": "0.35.4", | |
| 3309 | + | "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", | |
| 3310 | + | "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", | |
| 3311 | + | "dev": true, | |
| 3312 | + | "license": "Apache-2.0", | |
| 3313 | + | "dependencies": { | |
| 3314 | + | "@img/colour": "^1.1.0", | |
| 3315 | + | "detect-libc": "^2.1.2", | |
| 3316 | + | "semver": "^7.8.5" | |
| 3317 | + | }, | |
| 3318 | + | "engines": { | |
| 3319 | + | "node": ">=20.9.0" | |
| 3320 | + | }, | |
| 3321 | + | "funding": { | |
| 3322 | + | "url": "https://opencollective.com/libvips" | |
| 3323 | + | }, | |
| 3324 | + | "optionalDependencies": { | |
| 3325 | + | "@img/sharp-darwin-arm64": "0.35.4", | |
| 3326 | + | "@img/sharp-darwin-x64": "0.35.4", | |
| 3327 | + | "@img/sharp-freebsd-wasm32": "0.35.4", | |
| 3328 | + | "@img/sharp-libvips-darwin-arm64": "1.3.3", | |
| 3329 | + | "@img/sharp-libvips-darwin-x64": "1.3.3", | |
| 3330 | + | "@img/sharp-libvips-linux-arm": "1.3.3", | |
| 3331 | + | "@img/sharp-libvips-linux-arm64": "1.3.3", | |
| 3332 | + | "@img/sharp-libvips-linux-ppc64": "1.3.3", | |
| 3333 | + | "@img/sharp-libvips-linux-riscv64": "1.3.3", | |
| 3334 | + | "@img/sharp-libvips-linux-s390x": "1.3.3", | |
| 3335 | + | "@img/sharp-libvips-linux-x64": "1.3.3", | |
| 3336 | + | "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", | |
| 3337 | + | "@img/sharp-libvips-linuxmusl-x64": "1.3.3", | |
| 3338 | + | "@img/sharp-linux-arm": "0.35.4", | |
| 3339 | + | "@img/sharp-linux-arm64": "0.35.4", | |
| 3340 | + | "@img/sharp-linux-ppc64": "0.35.4", | |
| 3341 | + | "@img/sharp-linux-riscv64": "0.35.4", | |
| 3342 | + | "@img/sharp-linux-s390x": "0.35.4", | |
| 3343 | + | "@img/sharp-linux-x64": "0.35.4", | |
| 3344 | + | "@img/sharp-linuxmusl-arm64": "0.35.4", | |
| 3345 | + | "@img/sharp-linuxmusl-x64": "0.35.4", | |
| 3346 | + | "@img/sharp-webcontainers-wasm32": "0.35.4", | |
| 3347 | + | "@img/sharp-win32-arm64": "0.35.4", | |
| 3348 | + | "@img/sharp-win32-ia32": "0.35.4", | |
| 3349 | + | "@img/sharp-win32-x64": "0.35.4" | |
| 3350 | + | }, | |
| 3351 | + | "peerDependenciesMeta": { | |
| 3352 | + | "@types/node": { | |
| 3353 | + | "optional": true | |
| 3354 | + | } | |
| 3355 | + | } | |
| 3356 | + | }, | |
| 3357 | + | "node_modules/source-map-js": { | |
| 3358 | + | "version": "1.2.2", | |
| 3359 | + | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", | |
| 3360 | + | "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", | |
| 3361 | + | "dev": true, | |
| 3362 | + | "license": "BSD-3-Clause", | |
| 3363 | + | "engines": { | |
| 3364 | + | "node": ">=0.10.0" | |
| 3365 | + | } | |
| 3366 | + | }, | |
| 3367 | + | "node_modules/supports-color": { | |
| 3368 | + | "version": "10.2.2", | |
| 3369 | + | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", | |
| 3370 | + | "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", | |
| 3371 | + | "dev": true, | |
| 3372 | + | "license": "MIT", | |
| 3373 | + | "engines": { | |
| 3374 | + | "node": ">=18" | |
| 3375 | + | }, | |
| 3376 | + | "funding": { | |
| 3377 | + | "url": "https://github.com/chalk/supports-color?sponsor=1" | |
| 3378 | + | } | |
| 3379 | + | }, | |
| 3380 | + | "node_modules/tailwindcss": { | |
| 3381 | + | "version": "4.3.3", | |
| 3382 | + | "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", | |
| 3383 | + | "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", | |
| 3384 | + | "dev": true, | |
| 3385 | + | "license": "MIT" | |
| 3386 | + | }, | |
| 3387 | + | "node_modules/tapable": { | |
| 3388 | + | "version": "2.3.3", | |
| 3389 | + | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", | |
| 3390 | + | "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", | |
| 3391 | + | "dev": true, | |
| 3392 | + | "license": "MIT", | |
| 3393 | + | "engines": { | |
| 3394 | + | "node": ">=6" | |
| 3395 | + | }, | |
| 3396 | + | "funding": { | |
| 3397 | + | "type": "opencollective", | |
| 3398 | + | "url": "https://opencollective.com/webpack" | |
| 3399 | + | } | |
| 3400 | + | }, | |
| 3401 | + | "node_modules/tinyglobby": { | |
| 3402 | + | "version": "0.2.17", | |
| 3403 | + | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", | |
| 3404 | + | "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", | |
| 3405 | + | "dev": true, | |
| 3406 | + | "license": "MIT", | |
| 3407 | + | "dependencies": { | |
| 3408 | + | "fdir": "^6.5.0", | |
| 3409 | + | "picomatch": "^4.0.4" | |
| 3410 | + | }, | |
| 3411 | + | "engines": { | |
| 3412 | + | "node": ">=12.0.0" | |
| 3413 | + | }, | |
| 3414 | + | "funding": { | |
| 3415 | + | "url": "https://github.com/sponsors/SuperchupuDev" | |
| 3416 | + | } | |
| 3417 | + | }, | |
| 3418 | + | "node_modules/tslib": { | |
| 3419 | + | "version": "2.8.1", | |
| 3420 | + | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", | |
| 3421 | + | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", | |
| 3422 | + | "dev": true, | |
| 3423 | + | "license": "0BSD", | |
| 3424 | + | "optional": true | |
| 3425 | + | }, | |
| 3426 | + | "node_modules/typescript": { | |
| 3427 | + | "version": "5.9.3", | |
| 3428 | + | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", | |
| 3429 | + | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", | |
| 3430 | + | "dev": true, | |
| 3431 | + | "license": "Apache-2.0", | |
| 3432 | + | "bin": { | |
| 3433 | + | "tsc": "bin/tsc", | |
| 3434 | + | "tsserver": "bin/tsserver" | |
| 3435 | + | }, | |
| 3436 | + | "engines": { | |
| 3437 | + | "node": ">=14.17" | |
| 3438 | + | } | |
| 3439 | + | }, | |
| 3440 | + | "node_modules/undici": { | |
| 3441 | + | "version": "7.29.1", | |
| 3442 | + | "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", | |
| 3443 | + | "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", | |
| 3444 | + | "dev": true, | |
| 3445 | + | "license": "MIT", | |
| 3446 | + | "engines": { | |
| 3447 | + | "node": ">=20.18.1" | |
| 3448 | + | } | |
| 3449 | + | }, | |
| 3450 | + | "node_modules/undici-types": { | |
| 3451 | + | "version": "6.21.0", | |
| 3452 | + | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", | |
| 3453 | + | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", | |
| 3454 | + | "dev": true, | |
| 3455 | + | "license": "MIT" | |
| 3456 | + | }, | |
| 3457 | + | "node_modules/unenv": { | |
| 3458 | + | "version": "2.0.0-rc.24", | |
| 3459 | + | "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", | |
| 3460 | + | "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", | |
| 3461 | + | "dev": true, | |
| 3462 | + | "license": "MIT", | |
| 3463 | + | "dependencies": { | |
| 3464 | + | "pathe": "^2.0.3" | |
| 3465 | + | } | |
| 3466 | + | }, | |
| 3467 | + | "node_modules/update-browserslist-db": { | |
| 3468 | + | "version": "1.3.3", | |
| 3469 | + | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", | |
| 3470 | + | "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", | |
| 3471 | + | "dev": true, | |
| 3472 | + | "funding": [ | |
| 3473 | + | { | |
| 3474 | + | "type": "opencollective", | |
| 3475 | + | "url": "https://opencollective.com/browserslist" | |
| 3476 | + | }, | |
| 3477 | + | { | |
| 3478 | + | "type": "tidelift", | |
| 3479 | + | "url": "https://tidelift.com/funding/github/npm/browserslist" | |
| 3480 | + | }, | |
| 3481 | + | { | |
| 3482 | + | "type": "github", | |
| 3483 | + | "url": "https://github.com/sponsors/ai" | |
| 3484 | + | } | |
| 3485 | + | ], | |
| 3486 | + | "license": "MIT", | |
| 3487 | + | "dependencies": { | |
| 3488 | + | "escalade": "^3.2.0", | |
| 3489 | + | "picocolors": "^1.1.1" | |
| 3490 | + | }, | |
| 3491 | + | "bin": { | |
| 3492 | + | "update-browserslist-db": "cli.js" | |
| 3493 | + | }, | |
| 3494 | + | "peerDependencies": { | |
| 3495 | + | "browserslist": ">= 4.21.0" | |
| 3496 | + | } | |
| 3497 | + | }, | |
| 3498 | + | "node_modules/valibot": { | |
| 3499 | + | "version": "1.5.0", | |
| 3500 | + | "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.5.0.tgz", | |
| 3501 | + | "integrity": "sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==", | |
| 3502 | + | "dev": true, | |
| 3503 | + | "license": "MIT", | |
| 3504 | + | "peerDependencies": { | |
| 3505 | + | "typescript": ">=5" | |
| 3506 | + | }, | |
| 3507 | + | "peerDependenciesMeta": { | |
| 3508 | + | "typescript": { | |
| 3509 | + | "optional": true | |
| 3510 | + | } | |
| 3511 | + | } | |
| 3512 | + | }, | |
| 3513 | + | "node_modules/vite": { | |
| 3514 | + | "version": "8.3.2", | |
| 3515 | + | "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", | |
| 3516 | + | "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", | |
| 3517 | + | "dev": true, | |
| 3518 | + | "license": "MIT", | |
| 3519 | + | "dependencies": { | |
| 3520 | + | "lightningcss": "^1.33.0", | |
| 3521 | + | "picomatch": "^4.0.7", | |
| 3522 | + | "postcss": "^8.5.28", | |
| 3523 | + | "rolldown": "~1.2.11", | |
| 3524 | + | "tinyglobby": "^0.2.17" | |
| 3525 | + | }, | |
| 3526 | + | "bin": { | |
| 3527 | + | "vite": "bin/vite.js" | |
| 3528 | + | }, | |
| 3529 | + | "engines": { | |
| 3530 | + | "node": "^20.19.0 || >=22.12.0" | |
| 3531 | + | }, | |
| 3532 | + | "funding": { | |
| 3533 | + | "url": "https://github.com/vitejs/vite?sponsor=1" | |
| 3534 | + | }, | |
| 3535 | + | "optionalDependencies": { | |
| 3536 | + | "fsevents": "~2.3.3" | |
| 3537 | + | }, | |
| 3538 | + | "peerDependencies": { | |
| 3539 | + | "@types/node": "^20.19.0 || >=22.12.0", | |
| 3540 | + | "@vitejs/devtools": "^0.7.1", | |
| 3541 | + | "esbuild": "^0.27.0 || ^0.28.0", | |
| 3542 | + | "jiti": ">=1.21.0", | |
| 3543 | + | "less": "^4.0.0", | |
| 3544 | + | "sass": "^1.70.0", | |
| 3545 | + | "sass-embedded": "^1.70.0", | |
| 3546 | + | "stylus": ">=0.54.8", | |
| 3547 | + | "sugarss": "^5.0.0", | |
| 3548 | + | "terser": "^5.16.0", | |
| 3549 | + | "tsx": "^4.8.1", | |
| 3550 | + | "yaml": "^2.4.2" | |
| 3551 | + | }, | |
| 3552 | + | "peerDependenciesMeta": { | |
| 3553 | + | "@types/node": { | |
| 3554 | + | "optional": true | |
| 3555 | + | }, | |
| 3556 | + | "@vitejs/devtools": { | |
| 3557 | + | "optional": true | |
| 3558 | + | }, | |
| 3559 | + | "esbuild": { | |
| 3560 | + | "optional": true | |
| 3561 | + | }, | |
| 3562 | + | "jiti": { | |
| 3563 | + | "optional": true | |
| 3564 | + | }, | |
| 3565 | + | "less": { | |
| 3566 | + | "optional": true | |
| 3567 | + | }, | |
| 3568 | + | "sass": { | |
| 3569 | + | "optional": true | |
| 3570 | + | }, | |
| 3571 | + | "sass-embedded": { | |
| 3572 | + | "optional": true | |
| 3573 | + | }, | |
| 3574 | + | "stylus": { | |
| 3575 | + | "optional": true | |
| 3576 | + | }, | |
| 3577 | + | "sugarss": { | |
| 3578 | + | "optional": true | |
| 3579 | + | }, | |
| 3580 | + | "terser": { | |
| 3581 | + | "optional": true | |
| 3582 | + | }, | |
| 3583 | + | "tsx": { | |
| 3584 | + | "optional": true | |
| 3585 | + | }, | |
| 3586 | + | "yaml": { | |
| 3587 | + | "optional": true | |
| 3588 | + | } | |
| 3589 | + | } | |
| 3590 | + | }, | |
| 3591 | + | "node_modules/vite/node_modules/lightningcss": { | |
| 3592 | + | "version": "1.33.0", | |
| 3593 | + | "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", | |
| 3594 | + | "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", | |
| 3595 | + | "dev": true, | |
| 3596 | + | "license": "MPL-2.0", | |
| 3597 | + | "dependencies": { | |
| 3598 | + | "detect-libc": "^2.0.3" | |
| 3599 | + | }, | |
| 3600 | + | "engines": { | |
| 3601 | + | "node": ">= 12.0.0" | |
| 3602 | + | }, | |
| 3603 | + | "funding": { | |
| 3604 | + | "type": "opencollective", | |
| 3605 | + | "url": "https://opencollective.com/parcel" | |
| 3606 | + | }, | |
| 3607 | + | "optionalDependencies": { | |
| 3608 | + | "lightningcss-android-arm64": "1.33.0", | |
| 3609 | + | "lightningcss-darwin-arm64": "1.33.0", | |
| 3610 | + | "lightningcss-darwin-x64": "1.33.0", | |
| 3611 | + | "lightningcss-freebsd-x64": "1.33.0", | |
| 3612 | + | "lightningcss-linux-arm-gnueabihf": "1.33.0", | |
| 3613 | + | "lightningcss-linux-arm64-gnu": "1.33.0", | |
| 3614 | + | "lightningcss-linux-arm64-musl": "1.33.0", | |
| 3615 | + | "lightningcss-linux-x64-gnu": "1.33.0", | |
| 3616 | + | "lightningcss-linux-x64-musl": "1.33.0", | |
| 3617 | + | "lightningcss-win32-arm64-msvc": "1.33.0", | |
| 3618 | + | "lightningcss-win32-x64-msvc": "1.33.0" | |
| 3619 | + | } | |
| 3620 | + | }, | |
| 3621 | + | "node_modules/vite/node_modules/lightningcss-android-arm64": { | |
| 3622 | + | "version": "1.33.0", | |
| 3623 | + | "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", | |
| 3624 | + | "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", | |
| 3625 | + | "cpu": [ | |
| 3626 | + | "arm64" | |
| 3627 | + | ], | |
| 3628 | + | "dev": true, | |
| 3629 | + | "license": "MPL-2.0", | |
| 3630 | + | "optional": true, | |
| 3631 | + | "os": [ | |
| 3632 | + | "android" | |
| 3633 | + | ], | |
| 3634 | + | "engines": { | |
| 3635 | + | "node": ">= 12.0.0" | |
| 3636 | + | }, | |
| 3637 | + | "funding": { | |
| 3638 | + | "type": "opencollective", | |
| 3639 | + | "url": "https://opencollective.com/parcel" | |
| 3640 | + | } | |
| 3641 | + | }, | |
| 3642 | + | "node_modules/vite/node_modules/lightningcss-darwin-arm64": { | |
| 3643 | + | "version": "1.33.0", | |
| 3644 | + | "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", | |
| 3645 | + | "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", | |
| 3646 | + | "cpu": [ | |
| 3647 | + | "arm64" | |
| 3648 | + | ], | |
| 3649 | + | "dev": true, | |
| 3650 | + | "license": "MPL-2.0", | |
| 3651 | + | "optional": true, | |
| 3652 | + | "os": [ | |
| 3653 | + | "darwin" | |
| 3654 | + | ], | |
| 3655 | + | "engines": { | |
| 3656 | + | "node": ">= 12.0.0" | |
| 3657 | + | }, | |
| 3658 | + | "funding": { | |
| 3659 | + | "type": "opencollective", | |
| 3660 | + | "url": "https://opencollective.com/parcel" | |
| 3661 | + | } | |
| 3662 | + | }, | |
| 3663 | + | "node_modules/vite/node_modules/lightningcss-darwin-x64": { | |
| 3664 | + | "version": "1.33.0", | |
| 3665 | + | "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", | |
| 3666 | + | "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", | |
| 3667 | + | "cpu": [ | |
| 3668 | + | "x64" | |
| 3669 | + | ], | |
| 3670 | + | "dev": true, | |
| 3671 | + | "license": "MPL-2.0", | |
| 3672 | + | "optional": true, | |
| 3673 | + | "os": [ | |
| 3674 | + | "darwin" | |
| 3675 | + | ], | |
| 3676 | + | "engines": { | |
| 3677 | + | "node": ">= 12.0.0" | |
| 3678 | + | }, | |
| 3679 | + | "funding": { | |
| 3680 | + | "type": "opencollective", | |
| 3681 | + | "url": "https://opencollective.com/parcel" | |
| 3682 | + | } | |
| 3683 | + | }, | |
| 3684 | + | "node_modules/vite/node_modules/lightningcss-freebsd-x64": { | |
| 3685 | + | "version": "1.33.0", | |
| 3686 | + | "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", | |
| 3687 | + | "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", | |
| 3688 | + | "cpu": [ | |
| 3689 | + | "x64" | |
| 3690 | + | ], | |
| 3691 | + | "dev": true, | |
| 3692 | + | "license": "MPL-2.0", | |
| 3693 | + | "optional": true, | |
| 3694 | + | "os": [ | |
| 3695 | + | "freebsd" | |
| 3696 | + | ], | |
| 3697 | + | "engines": { | |
| 3698 | + | "node": ">= 12.0.0" | |
| 3699 | + | }, | |
| 3700 | + | "funding": { | |
| 3701 | + | "type": "opencollective", | |
| 3702 | + | "url": "https://opencollective.com/parcel" | |
| 3703 | + | } | |
| 3704 | + | }, | |
| 3705 | + | "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { | |
| 3706 | + | "version": "1.33.0", | |
| 3707 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", | |
| 3708 | + | "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", | |
| 3709 | + | "cpu": [ | |
| 3710 | + | "arm" | |
| 3711 | + | ], | |
| 3712 | + | "dev": true, | |
| 3713 | + | "license": "MPL-2.0", | |
| 3714 | + | "optional": true, | |
| 3715 | + | "os": [ | |
| 3716 | + | "linux" | |
| 3717 | + | ], | |
| 3718 | + | "engines": { | |
| 3719 | + | "node": ">= 12.0.0" | |
| 3720 | + | }, | |
| 3721 | + | "funding": { | |
| 3722 | + | "type": "opencollective", | |
| 3723 | + | "url": "https://opencollective.com/parcel" | |
| 3724 | + | } | |
| 3725 | + | }, | |
| 3726 | + | "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { | |
| 3727 | + | "version": "1.33.0", | |
| 3728 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", | |
| 3729 | + | "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", | |
| 3730 | + | "cpu": [ | |
| 3731 | + | "arm64" | |
| 3732 | + | ], | |
| 3733 | + | "dev": true, | |
| 3734 | + | "libc": [ | |
| 3735 | + | "glibc" | |
| 3736 | + | ], | |
| 3737 | + | "license": "MPL-2.0", | |
| 3738 | + | "optional": true, | |
| 3739 | + | "os": [ | |
| 3740 | + | "linux" | |
| 3741 | + | ], | |
| 3742 | + | "engines": { | |
| 3743 | + | "node": ">= 12.0.0" | |
| 3744 | + | }, | |
| 3745 | + | "funding": { | |
| 3746 | + | "type": "opencollective", | |
| 3747 | + | "url": "https://opencollective.com/parcel" | |
| 3748 | + | } | |
| 3749 | + | }, | |
| 3750 | + | "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { | |
| 3751 | + | "version": "1.33.0", | |
| 3752 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", | |
| 3753 | + | "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", | |
| 3754 | + | "cpu": [ | |
| 3755 | + | "arm64" | |
| 3756 | + | ], | |
| 3757 | + | "dev": true, | |
| 3758 | + | "libc": [ | |
| 3759 | + | "musl" | |
| 3760 | + | ], | |
| 3761 | + | "license": "MPL-2.0", | |
| 3762 | + | "optional": true, | |
| 3763 | + | "os": [ | |
| 3764 | + | "linux" | |
| 3765 | + | ], | |
| 3766 | + | "engines": { | |
| 3767 | + | "node": ">= 12.0.0" | |
| 3768 | + | }, | |
| 3769 | + | "funding": { | |
| 3770 | + | "type": "opencollective", | |
| 3771 | + | "url": "https://opencollective.com/parcel" | |
| 3772 | + | } | |
| 3773 | + | }, | |
| 3774 | + | "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { | |
| 3775 | + | "version": "1.33.0", | |
| 3776 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", | |
| 3777 | + | "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", | |
| 3778 | + | "cpu": [ | |
| 3779 | + | "x64" | |
| 3780 | + | ], | |
| 3781 | + | "dev": true, | |
| 3782 | + | "libc": [ | |
| 3783 | + | "glibc" | |
| 3784 | + | ], | |
| 3785 | + | "license": "MPL-2.0", | |
| 3786 | + | "optional": true, | |
| 3787 | + | "os": [ | |
| 3788 | + | "linux" | |
| 3789 | + | ], | |
| 3790 | + | "engines": { | |
| 3791 | + | "node": ">= 12.0.0" | |
| 3792 | + | }, | |
| 3793 | + | "funding": { | |
| 3794 | + | "type": "opencollective", | |
| 3795 | + | "url": "https://opencollective.com/parcel" | |
| 3796 | + | } | |
| 3797 | + | }, | |
| 3798 | + | "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { | |
| 3799 | + | "version": "1.33.0", | |
| 3800 | + | "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", | |
| 3801 | + | "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", | |
| 3802 | + | "cpu": [ | |
| 3803 | + | "x64" | |
| 3804 | + | ], | |
| 3805 | + | "dev": true, | |
| 3806 | + | "libc": [ | |
| 3807 | + | "musl" | |
| 3808 | + | ], | |
| 3809 | + | "license": "MPL-2.0", | |
| 3810 | + | "optional": true, | |
| 3811 | + | "os": [ | |
| 3812 | + | "linux" | |
| 3813 | + | ], | |
| 3814 | + | "engines": { | |
| 3815 | + | "node": ">= 12.0.0" | |
| 3816 | + | }, | |
| 3817 | + | "funding": { | |
| 3818 | + | "type": "opencollective", | |
| 3819 | + | "url": "https://opencollective.com/parcel" | |
| 3820 | + | } | |
| 3821 | + | }, | |
| 3822 | + | "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { | |
| 3823 | + | "version": "1.33.0", | |
| 3824 | + | "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", | |
| 3825 | + | "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", | |
| 3826 | + | "cpu": [ | |
| 3827 | + | "arm64" | |
| 3828 | + | ], | |
| 3829 | + | "dev": true, | |
| 3830 | + | "license": "MPL-2.0", | |
| 3831 | + | "optional": true, | |
| 3832 | + | "os": [ | |
| 3833 | + | "win32" | |
| 3834 | + | ], | |
| 3835 | + | "engines": { | |
| 3836 | + | "node": ">= 12.0.0" | |
| 3837 | + | }, | |
| 3838 | + | "funding": { | |
| 3839 | + | "type": "opencollective", | |
| 3840 | + | "url": "https://opencollective.com/parcel" | |
| 3841 | + | } | |
| 3842 | + | }, | |
| 3843 | + | "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { | |
| 3844 | + | "version": "1.33.0", | |
| 3845 | + | "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", | |
| 3846 | + | "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", | |
| 3847 | + | "cpu": [ | |
| 3848 | + | "x64" | |
| 3849 | + | ], | |
| 3850 | + | "dev": true, | |
| 3851 | + | "license": "MPL-2.0", | |
| 3852 | + | "optional": true, | |
| 3853 | + | "os": [ | |
| 3854 | + | "win32" | |
| 3855 | + | ], | |
| 3856 | + | "engines": { | |
| 3857 | + | "node": ">= 12.0.0" | |
| 3858 | + | }, | |
| 3859 | + | "funding": { | |
| 3860 | + | "type": "opencollective", | |
| 3861 | + | "url": "https://opencollective.com/parcel" | |
| 3862 | + | } | |
| 3863 | + | }, | |
| 3864 | + | "node_modules/workerd": { | |
| 3865 | + | "version": "1.20261001.1", | |
| 3866 | + | "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261001.1.tgz", | |
| 3867 | + | "integrity": "sha512-d/SIYHFO0PT/wiFZg8in4NpRIxYuFwslX1HdylOtWkBIIUmSpkGFhK820cV84XACFylwJ48xuRoWW/8DWDPsPQ==", | |
| 3868 | + | "dev": true, | |
| 3869 | + | "hasInstallScript": true, | |
| 3870 | + | "license": "Apache-2.0", | |
| 3871 | + | "bin": { | |
| 3872 | + | "workerd": "bin/workerd" | |
| 3873 | + | }, | |
| 3874 | + | "engines": { | |
| 3875 | + | "node": ">=16" | |
| 3876 | + | }, | |
| 3877 | + | "optionalDependencies": { | |
| 3878 | + | "@cloudflare/workerd-darwin-64": "1.20261001.1", | |
| 3879 | + | "@cloudflare/workerd-darwin-arm64": "1.20261001.1", | |
| 3880 | + | "@cloudflare/workerd-linux-64": "1.20261001.1", | |
| 3881 | + | "@cloudflare/workerd-linux-arm64": "1.20261001.1", | |
| 3882 | + | "@cloudflare/workerd-windows-64": "1.20261001.1" | |
| 3883 | + | } | |
| 3884 | + | }, | |
| 3885 | + | "node_modules/wrangler": { | |
| 3886 | + | "version": "4.146.0", | |
| 3887 | + | "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.146.0.tgz", | |
| 3888 | + | "integrity": "sha512-c27eHUH0Isr8HTmgZ6cLtLr/0cxtiBoLren6ywBeTW8ZIzj/dINVNBcqsbZc+iETnI5jrzO7E7Z8mLgLZ/l9iw==", | |
| 3889 | + | "dev": true, | |
| 3890 | + | "license": "MIT OR Apache-2.0", | |
| 3891 | + | "dependencies": { | |
| 3892 | + | "@cloudflare/kv-asset-handler": "0.5.0", | |
| 3893 | + | "@cloudflare/unenv-preset": "2.16.2", | |
| 3894 | + | "blake3-wasm": "2.1.5", | |
| 3895 | + | "esbuild": "0.28.1", | |
| 3896 | + | "miniflare": "5.20261001.0-alpha", | |
| 3897 | + | "path-to-regexp": "6.3.0", | |
| 3898 | + | "unenv": "2.0.0-rc.24", | |
| 3899 | + | "workerd": "1.20261001.1" | |
| 3900 | + | }, | |
| 3901 | + | "bin": { | |
| 3902 | + | "cf-wrangler": "bin/cf-wrangler.js", | |
| 3903 | + | "wrangler": "bin/wrangler.js", | |
| 3904 | + | "wrangler2": "bin/wrangler.js" | |
| 3905 | + | }, | |
| 3906 | + | "engines": { | |
| 3907 | + | "node": ">=22.0.0" | |
| 3908 | + | }, | |
| 3909 | + | "optionalDependencies": { | |
| 3910 | + | "fsevents": "2.3.3" | |
| 3911 | + | }, | |
| 3912 | + | "peerDependencies": { | |
| 3913 | + | "@cloudflare/workers-types": "^5.20261001.1" | |
| 3914 | + | }, | |
| 3915 | + | "peerDependenciesMeta": { | |
| 3916 | + | "@cloudflare/workers-types": { | |
| 3917 | + | "optional": true | |
| 3918 | + | } | |
| 3919 | + | } | |
| 3920 | + | }, | |
| 3921 | + | "node_modules/ws": { | |
| 3922 | + | "version": "8.21.0", | |
| 3923 | + | "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", | |
| 3924 | + | "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", | |
| 3925 | + | "dev": true, | |
| 3926 | + | "license": "MIT", | |
| 3927 | + | "engines": { | |
| 3928 | + | "node": ">=10.0.0" | |
| 3929 | + | }, | |
| 3930 | + | "peerDependencies": { | |
| 3931 | + | "bufferutil": "^4.0.1", | |
| 3932 | + | "utf-8-validate": ">=5.0.2" | |
| 3933 | + | }, | |
| 3934 | + | "peerDependenciesMeta": { | |
| 3935 | + | "bufferutil": { | |
| 3936 | + | "optional": true | |
| 3937 | + | }, | |
| 3938 | + | "utf-8-validate": { | |
| 3939 | + | "optional": true | |
| 3940 | + | } | |
| 3941 | + | } | |
| 3942 | + | }, | |
| 3943 | + | "node_modules/yallist": { | |
| 3944 | + | "version": "3.1.1", | |
| 3945 | + | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", | |
| 3946 | + | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", | |
| 3947 | + | "dev": true, | |
| 3948 | + | "license": "ISC" | |
| 3949 | + | }, | |
| 3950 | + | "node_modules/youch": { | |
| 3951 | + | "version": "4.1.0-beta.10", | |
| 3952 | + | "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", | |
| 3953 | + | "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", | |
| 3954 | + | "dev": true, | |
| 3955 | + | "license": "MIT", | |
| 3956 | + | "dependencies": { | |
| 3957 | + | "@poppinss/colors": "^4.1.5", | |
| 3958 | + | "@poppinss/dumper": "^0.6.4", | |
| 3959 | + | "@speed-highlight/core": "^1.2.7", | |
| 3960 | + | "cookie": "^1.0.2", | |
| 3961 | + | "youch-core": "^0.3.3" | |
| 3962 | + | } | |
| 3963 | + | }, | |
| 3964 | + | "node_modules/youch-core": { | |
| 3965 | + | "version": "0.3.3", | |
| 3966 | + | "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", | |
| 3967 | + | "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", | |
| 3968 | + | "dev": true, | |
| 3969 | + | "license": "MIT", | |
| 3970 | + | "dependencies": { | |
| 3971 | + | "@poppinss/exception": "^1.2.2", | |
| 3972 | + | "error-stack-parser-es": "^1.0.5" | |
| 3973 | + | } | |
| 3974 | + | }, | |
| 3975 | + | "packages/contracts": { | |
| 3976 | + | "name": "@g1t/contracts", | |
| 3977 | + | "version": "0.1.0", | |
| 3978 | + | "license": "MIT" | |
| 3979 | + | }, | |
| 3980 | + | "services/events": { | |
| 3981 | + | "name": "@g1t/events", | |
| 3982 | + | "version": "0.1.0", | |
| 3983 | + | "license": "MIT", | |
| 3984 | + | "dependencies": { | |
| 3985 | + | "@g1t/contracts": "*" | |
| 3986 | + | } | |
| 3987 | + | }, | |
| 3988 | + | "services/identity": { | |
| 3989 | + | "name": "@g1t/identity", | |
| 3990 | + | "version": "0.1.0", | |
| 3991 | + | "license": "MIT", | |
| 3992 | + | "dependencies": { | |
| 3993 | + | "@g1t/contracts": "*" | |
| 3994 | + | } | |
| 3995 | + | }, | |
| 3996 | + | "services/repos": { | |
| 3997 | + | "name": "@g1t/repos", | |
| 3998 | + | "version": "0.1.0", | |
| 3999 | + | "license": "MIT", | |
| 4000 | + | "dependencies": { | |
| 4001 | + | "@g1t/contracts": "*" | |
| 4002 | + | } | |
| 4003 | + | }, | |
| 4004 | + | "services/work": { | |
| 4005 | + | "name": "@g1t/work", | |
| 4006 | + | "version": "0.1.0", | |
| 4007 | + | "license": "MIT", | |
| 4008 | + | "dependencies": { | |
| 4009 | + | "@g1t/contracts": "*" | |
| 4010 | + | } | |
| 4011 | + | } | |
| 4012 | + | } | |
| 4013 | + | } |
| 1 | + | { | |
| 2 | + | "name": "g1t-monorepo", | |
| 3 | + | "private": true, | |
| 4 | + | "type": "module", | |
| 5 | + | "workspaces": ["apps/*", "services/*", "packages/*"], | |
| 6 | + | "scripts": { | |
| 7 | + | "typecheck": "npm run typecheck --workspaces --if-present", | |
| 8 | + | "deploy": "npm run deploy -w @g1t/identity -w @g1t/events -w @g1t/repos -w @g1t/work -w @g1t/web" | |
| 9 | + | }, | |
| 10 | + | "devDependencies": { | |
| 11 | + | "typescript": "^5.9.3", | |
| 12 | + | "wrangler": "^4.146.0" | |
| 13 | + | } | |
| 14 | + | } |
| 1 | + | { | |
| 2 | + | "name": "@g1t/contracts", | |
| 3 | + | "version": "0.1.0", | |
| 4 | + | "private": true, | |
| 5 | + | "type": "module", | |
| 6 | + | "license": "MIT", | |
| 7 | + | "exports": { ".": "./src/index.ts" }, | |
| 8 | + | "scripts": { "typecheck": "tsc -p tsconfig.json" } | |
| 9 | + | } |
| 1 | + | /** | |
| 2 | + | * Every state change in g1t is published as an event. Services react to | |
| 3 | + | * each other through events rather than direct calls, and the same stream | |
| 4 | + | * feeds timelines, webhooks and automations. | |
| 5 | + | * | |
| 6 | + | * The envelope follows CloudEvents: `type` says what happened, `subject` | |
| 7 | + | * says to what, `data` is the type-specific payload. | |
| 8 | + | */ | |
| 9 | + | export type EventPayloads = { | |
| 10 | + | "repo.created": { repoId: string; namespace: string; name: string; isPrivate: boolean }; | |
| 11 | + | "repo.forked": { repoId: string; sourceRepoId: string; attemptId: string }; | |
| 12 | + | /** `after` is the commit the ref points to once the push has landed. */ | |
| 13 | + | "git.push": { repoId: string; ref: string; after: string }; | |
| 14 | + | "intent.opened": { intentId: string; repoId: string; number: number; title: string }; | |
| 15 | + | "intent.closed": { intentId: string; repoId: string; reason: "shipped" | "withdrawn" }; | |
| 16 | + | "attempt.started": { attemptId: string; intentId: string; repoId: string; agent: string }; | |
| 17 | + | "attempt.updated": { attemptId: string; intentId: string; repoId: string; status: string }; | |
| 18 | + | "attempt.submitted": { attemptId: string; intentId: string; repoId: string }; | |
| 19 | + | "session.appended": { attemptId: string; sessionId: string; count: number }; | |
| 20 | + | }; | |
| 21 | + | ||
| 22 | + | export type EventType = keyof EventPayloads; | |
| 23 | + | ||
| 24 | + | export type G1tEvent<T extends EventType = EventType> = { | |
| 25 | + | [K in T]: { | |
| 26 | + | id: string; | |
| 27 | + | type: K; | |
| 28 | + | /** The service that published it. */ | |
| 29 | + | source: string; | |
| 30 | + | /** Milliseconds since the epoch. */ | |
| 31 | + | time: number; | |
| 32 | + | /** The repo the event concerns, used to scope timelines and deliveries. */ | |
| 33 | + | repoId: string | null; | |
| 34 | + | /** The user or agent that caused it, if any. */ | |
| 35 | + | actor: string | null; | |
| 36 | + | data: EventPayloads[K]; | |
| 37 | + | }; | |
| 38 | + | }[T]; | |
| 39 | + | ||
| 40 | + | /** What a publisher supplies; the bus fills in `id` and `time`. */ | |
| 41 | + | export type NewEvent<T extends EventType = EventType> = { | |
| 42 | + | [K in T]: Omit<G1tEvent<K>, "id" | "time">; | |
| 43 | + | }[T]; | |
| 44 | + | ||
| 45 | + | export type EventQuery = { | |
| 46 | + | repoId?: string; | |
| 47 | + | types?: EventType[]; | |
| 48 | + | /** Return events older than this event id. */ | |
| 49 | + | before?: string; | |
| 50 | + | limit?: number; | |
| 51 | + | }; | |
| 52 | + | ||
| 53 | + | /** The event bus and its durable log. */ | |
| 54 | + | export interface EventsApi { | |
| 55 | + | publish(events: NewEvent[]): Promise<void>; | |
| 56 | + | /** Newest first. */ | |
| 57 | + | list(query: EventQuery): Promise<G1tEvent[]>; | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | /** Implemented by services that consume events from the bus. */ | |
| 61 | + | export interface EventSubscriber { | |
| 62 | + | onEvents(events: G1tEvent[]): Promise<void>; | |
| 63 | + | } |
| 1 | + | import type { Result } from "./result"; | |
| 2 | + | ||
| 3 | + | export type User = { id: string; username: string }; | |
| 4 | + | ||
| 5 | + | /** Who is asking. Every read and write in every service takes one. */ | |
| 6 | + | export type Viewer = User | null; | |
| 7 | + | ||
| 8 | + | export type SshKey = { | |
| 9 | + | id: string; | |
| 10 | + | title: string; | |
| 11 | + | fingerprint: string; | |
| 12 | + | createdAt: number; | |
| 13 | + | }; | |
| 14 | + | ||
| 15 | + | export type AccessToken = { id: string; name: string; createdAt: number }; | |
| 16 | + | ||
| 17 | + | /** Accounts, credentials and sessions. */ | |
| 18 | + | export interface IdentityApi { | |
| 19 | + | /** Verifies a username and password for website sign-in. */ | |
| 20 | + | signIn(username: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>; | |
| 21 | + | signOut(sessionToken: string): Promise<void>; | |
| 22 | + | userForSession(sessionToken: string): Promise<Viewer>; | |
| 23 | + | ||
| 24 | + | /** Verifies git credentials: the account password or an access token. */ | |
| 25 | + | userForGitCredentials(username: string, secret: string): Promise<Viewer>; | |
| 26 | + | userForSshKey(fingerprint: string): Promise<Viewer>; | |
| 27 | + | userByUsername(username: string): Promise<Viewer>; | |
| 28 | + | ||
| 29 | + | listSshKeys(user: User): Promise<SshKey[]>; | |
| 30 | + | /** Takes one line in OpenSSH public key format. */ | |
| 31 | + | addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>; | |
| 32 | + | removeSshKey(user: User, id: string): Promise<void>; | |
| 33 | + | ||
| 34 | + | listAccessTokens(user: User): Promise<AccessToken[]>; | |
| 35 | + | /** The plaintext token is returned once and never stored. */ | |
| 36 | + | createAccessToken(user: User, name: string): Promise<{ token: string; info: AccessToken }>; | |
| 37 | + | removeAccessToken(user: User, id: string): Promise<void>; | |
| 38 | + | } |
| 1 | + | const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"; | |
| 2 | + | ||
| 3 | + | export type IdPrefix = "usr" | "ses" | "tok" | "key" | "rep" | "int" | "att" | "evt"; | |
| 4 | + | ||
| 5 | + | /** | |
| 6 | + | * A prefixed, time-sortable id such as `att_01jb2…`: 48 bits of millisecond | |
| 7 | + | * timestamp then 80 random bits, in lowercase Crockford base32. Sorting ids | |
| 8 | + | * as strings sorts them by creation time. | |
| 9 | + | */ | |
| 10 | + | export function newId(prefix: IdPrefix, now: number = Date.now()): string { | |
| 11 | + | let time = ""; | |
| 12 | + | for (let i = 0, t = now; i < 10; i++, t = Math.floor(t / 32)) { | |
| 13 | + | time = ALPHABET[t % 32] + time; | |
| 14 | + | } | |
| 15 | + | let random = ""; | |
| 16 | + | for (const byte of crypto.getRandomValues(new Uint8Array(16))) { | |
| 17 | + | random += ALPHABET[byte % 32]; | |
| 18 | + | } | |
| 19 | + | return `${prefix}_${time}${random}`; | |
| 20 | + | } |
| 1 | + | export * from "./events"; | |
| 2 | + | export * from "./identity"; | |
| 3 | + | export * from "./ids"; | |
| 4 | + | export * from "./names"; | |
| 5 | + | export * from "./repos"; | |
| 6 | + | export * from "./result"; | |
| 7 | + | export * from "./work"; |
| 1 | + | // Namespaces follow GitHub's rules (no leading, trailing or doubled hyphens), | |
| 2 | + | // so "--" can never appear inside one. | |
| 3 | + | const NAMESPACE = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/; | |
| 4 | + | const REPO_NAME = /^[a-z0-9._-]{1,100}$/; | |
| 5 | + | ||
| 6 | + | /** Routes and reserved words that may not be registered as usernames. */ | |
| 7 | + | const RESERVED = new Set([ | |
| 8 | + | "api", "mcp", "login", "logout", "register", "new", "settings", "search", | |
| 9 | + | "admin", "auth", "attempts", "oauth", "assets", "docs", "explore", "g1t", "about", | |
| 10 | + | ]); | |
| 11 | + | ||
| 12 | + | export function isValidNamespace(value: string): boolean { | |
| 13 | + | return NAMESPACE.test(value) && !RESERVED.has(value); | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | export function isValidRepoName(value: string): boolean { | |
| 17 | + | return REPO_NAME.test(value) && !value.startsWith(".") && !value.endsWith(".git"); | |
| 18 | + | } |
| 1 | + | import type { User, Viewer } from "./identity"; | |
| 2 | + | import type { Result } from "./result"; | |
| 3 | + | ||
| 4 | + | export type Repo = { | |
| 5 | + | id: string; | |
| 6 | + | /** The owning user's (later, workspace's) name: the first URL segment. */ | |
| 7 | + | namespace: string; | |
| 8 | + | name: string; | |
| 9 | + | description: string | null; | |
| 10 | + | isPrivate: boolean; | |
| 11 | + | ownerId: string; | |
| 12 | + | defaultBranch: string; | |
| 13 | + | /** Set when this repo is an attempt's working copy of another repo. */ | |
| 14 | + | forkOf: string | null; | |
| 15 | + | createdAt: number; | |
| 16 | + | }; | |
| 17 | + | ||
| 18 | + | export type RepoPath = { namespace: string; name: string }; | |
| 19 | + | ||
| 20 | + | export type Commit = { | |
| 21 | + | hash: string; | |
| 22 | + | treeHash: string; | |
| 23 | + | message: string; | |
| 24 | + | author: { name: string; email: string }; | |
| 25 | + | parents: string[]; | |
| 26 | + | authoredAt: number; | |
| 27 | + | }; | |
| 28 | + | ||
| 29 | + | export type TreeEntry = { | |
| 30 | + | name: string; | |
| 31 | + | hash: string; | |
| 32 | + | kind: "tree" | "blob" | "symlink" | "gitlink" | "exec"; | |
| 33 | + | }; | |
| 34 | + | ||
| 35 | + | export type TreeView = { | |
| 36 | + | repo: Repo; | |
| 37 | + | ref: string; | |
| 38 | + | path: string; | |
| 39 | + | /** Null when the repo has no commits yet. */ | |
| 40 | + | head: Commit | null; | |
| 41 | + | entries: TreeEntry[]; | |
| 42 | + | readme: { name: string; text: string | null } | null; | |
| 43 | + | }; | |
| 44 | + | ||
| 45 | + | export type BlobView = { | |
| 46 | + | repo: Repo; | |
| 47 | + | ref: string; | |
| 48 | + | path: string; | |
| 49 | + | size: number; | |
| 50 | + | /** Null when the file is binary or too large to show. */ | |
| 51 | + | text: string | null; | |
| 52 | + | }; | |
| 53 | + | ||
| 54 | + | /** A git remote and a short-lived credential for it. */ | |
| 55 | + | export type GitAccess = { remote: string; token: string }; | |
| 56 | + | ||
| 57 | + | export type GitService = "git-upload-pack" | "git-receive-pack"; | |
| 58 | + | ||
| 59 | + | export type CreateRepoInput = { | |
| 60 | + | name: string; | |
| 61 | + | description?: string | null; | |
| 62 | + | isPrivate?: boolean; | |
| 63 | + | }; | |
| 64 | + | ||
| 65 | + | /** Repositories: metadata, contents and git access. */ | |
| 66 | + | export interface ReposApi { | |
| 67 | + | get(path: RepoPath, viewer: Viewer): Promise<Result<Repo>>; | |
| 68 | + | getById(id: string, viewer: Viewer): Promise<Result<Repo>>; | |
| 69 | + | /** Repos the viewer may see, newest first, optionally matching `query`. */ | |
| 70 | + | list(viewer: Viewer, options?: { query?: string; namespace?: string }): Promise<Repo[]>; | |
| 71 | + | create(owner: User, input: CreateRepoInput): Promise<Result<Repo>>; | |
| 72 | + | ||
| 73 | + | tree(path: RepoPath, viewer: Viewer, ref: string | null, treePath: string): Promise<Result<TreeView>>; | |
| 74 | + | blob(path: RepoPath, viewer: Viewer, ref: string, filePath: string): Promise<Result<BlobView>>; | |
| 75 | + | log(path: RepoPath, viewer: Viewer, ref: string | null, limit: number): Promise<Result<Commit[]>>; | |
| 76 | + | ||
| 77 | + | /** | |
| 78 | + | * A copy-on-write copy of `source`, hidden from listings, for one attempt | |
| 79 | + | * to work in. | |
| 80 | + | */ | |
| 81 | + | forkForAttempt(sourceId: string, attemptId: string, actor: User): Promise<Result<Repo>>; | |
| 82 | + | ||
| 83 | + | /** | |
| 84 | + | * Authorizes a git operation and returns where to send it. Pushing to a | |
| 85 | + | * repo that does not exist creates it in the pusher's own namespace. | |
| 86 | + | */ | |
| 87 | + | gitAccess(path: RepoPath, viewer: Viewer, service: GitService): Promise<Result<GitAccess>>; | |
| 88 | + | } |
| 1 | + | /** | |
| 2 | + | * Expected failures cross service boundaries as values, not exceptions: | |
| 3 | + | * Worker RPC does not preserve error classes, and callers should have to | |
| 4 | + | * handle them. | |
| 5 | + | */ | |
| 6 | + | export type FailureCode = | |
| 7 | + | | "not_found" | |
| 8 | + | | "forbidden" | |
| 9 | + | | "unauthenticated" | |
| 10 | + | | "conflict" | |
| 11 | + | | "invalid"; | |
| 12 | + | ||
| 13 | + | export type Failure = { code: FailureCode; message: string }; | |
| 14 | + | ||
| 15 | + | export type Result<T> = { ok: true; value: T } | { ok: false; error: Failure }; | |
| 16 | + | ||
| 17 | + | export function ok<T>(value: T): Result<T> { | |
| 18 | + | return { ok: true, value }; | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | export function fail(code: FailureCode, message: string): Result<never> { | |
| 22 | + | return { ok: false, error: { code, message } }; | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | const HTTP_STATUS: Record<FailureCode, number> = { | |
| 26 | + | not_found: 404, | |
| 27 | + | forbidden: 403, | |
| 28 | + | unauthenticated: 401, | |
| 29 | + | conflict: 409, | |
| 30 | + | invalid: 422, | |
| 31 | + | }; | |
| 32 | + | ||
| 33 | + | export function httpStatus(failure: Failure): number { | |
| 34 | + | return HTTP_STATUS[failure.code]; | |
| 35 | + | } |
| 1 | + | import type { EventSubscriber } from "./events"; | |
| 2 | + | import type { User, Viewer } from "./identity"; | |
| 3 | + | import type { RepoPath } from "./repos"; | |
| 4 | + | import type { Result } from "./result"; | |
| 5 | + | ||
| 6 | + | export type IntentStatus = "open" | "shipped" | "withdrawn"; | |
| 7 | + | ||
| 8 | + | /** A goal stated against a repo. Replaces the issue and the pull request. */ | |
| 9 | + | export type Intent = { | |
| 10 | + | id: string; | |
| 11 | + | repoId: string; | |
| 12 | + | /** Sequential per repo, shown as `#12`. */ | |
| 13 | + | number: number; | |
| 14 | + | title: string; | |
| 15 | + | /** The goal in prose: what an agent is given to work from. */ | |
| 16 | + | brief: string; | |
| 17 | + | /** Commands that must pass for an attempt to be accepted. */ | |
| 18 | + | checks: string[]; | |
| 19 | + | status: IntentStatus; | |
| 20 | + | author: User; | |
| 21 | + | createdAt: number; | |
| 22 | + | attemptCount: number; | |
| 23 | + | }; | |
| 24 | + | ||
| 25 | + | export type AttemptStatus = "working" | "submitted" | "shipped" | "abandoned"; | |
| 26 | + | ||
| 27 | + | /** Where the agent runs: on g1t's sandboxes, or in someone's own session. */ | |
| 28 | + | export type AttemptRuntime = "hosted" | "external"; | |
| 29 | + | ||
| 30 | + | /** One agent's run at an intent, in its own fork. */ | |
| 31 | + | export type Attempt = { | |
| 32 | + | id: string; | |
| 33 | + | intentId: string; | |
| 34 | + | repoId: string; | |
| 35 | + | /** Sequential per intent. */ | |
| 36 | + | number: number; | |
| 37 | + | /** A label for the agent doing the work, e.g. `claude-code`. */ | |
| 38 | + | agent: string; | |
| 39 | + | runtime: AttemptRuntime; | |
| 40 | + | status: AttemptStatus; | |
| 41 | + | /** The agent's own account of what it did, set on submit. */ | |
| 42 | + | summary: string | null; | |
| 43 | + | fork: RepoPath; | |
| 44 | + | headCommit: string | null; | |
| 45 | + | startedBy: User; | |
| 46 | + | createdAt: number; | |
| 47 | + | updatedAt: number; | |
| 48 | + | }; | |
| 49 | + | ||
| 50 | + | export type SessionEntryKind = "prompt" | "message" | "tool_call" | "tool_result" | "note"; | |
| 51 | + | ||
| 52 | + | /** One step of an agent's session: the "why" behind an attempt's commits. */ | |
| 53 | + | export type SessionEntry = { | |
| 54 | + | seq: number; | |
| 55 | + | kind: SessionEntryKind; | |
| 56 | + | text: string; | |
| 57 | + | /** For tool calls and results. */ | |
| 58 | + | tool: string | null; | |
| 59 | + | /** The fork's head commit when this entry was recorded, if known. */ | |
| 60 | + | commit: string | null; | |
| 61 | + | at: number; | |
| 62 | + | }; | |
| 63 | + | ||
| 64 | + | export type NewSessionEntry = Pick<SessionEntry, "kind" | "text"> & | |
| 65 | + | Partial<Pick<SessionEntry, "tool" | "commit" | "at">>; | |
| 66 | + | ||
| 67 | + | export type IntentDetail = { intent: Intent; attempts: Attempt[] }; | |
| 68 | + | ||
| 69 | + | export type OpenIntentInput = { title: string; brief: string; checks?: string[] }; | |
| 70 | + | ||
| 71 | + | export type StartAttemptInput = { agent: string; runtime: AttemptRuntime }; | |
| 72 | + | ||
| 73 | + | /** Intents, attempts and sessions. */ | |
| 74 | + | export interface WorkApi extends EventSubscriber { | |
| 75 | + | openIntent(actor: User, repo: RepoPath, input: OpenIntentInput): Promise<Result<Intent>>; | |
| 76 | + | listIntents(repo: RepoPath, viewer: Viewer, status?: IntentStatus): Promise<Result<Intent[]>>; | |
| 77 | + | getIntent(repo: RepoPath, number: number, viewer: Viewer): Promise<Result<IntentDetail>>; | |
| 78 | + | withdrawIntent(actor: User, intentId: string): Promise<Result<Intent>>; | |
| 79 | + | ||
| 80 | + | /** Forks the repo for the agent and returns the attempt to push to. */ | |
| 81 | + | startAttempt(actor: User, intentId: string, input: StartAttemptInput): Promise<Result<Attempt>>; | |
| 82 | + | getAttempt(attemptId: string, viewer: Viewer): Promise<Result<{ attempt: Attempt; intent: Intent }>>; | |
| 83 | + | submitAttempt(actor: User, attemptId: string, summary: string): Promise<Result<Attempt>>; | |
| 84 | + | abandonAttempt(actor: User, attemptId: string): Promise<Result<Attempt>>; | |
| 85 | + | /** Attempts in progress that the viewer started, newest first. */ | |
| 86 | + | listActiveAttempts(viewer: Viewer): Promise<{ attempt: Attempt; intent: Intent }[]>; | |
| 87 | + | ||
| 88 | + | appendSession(actor: User, attemptId: string, entries: NewSessionEntry[]): Promise<Result<{ count: number }>>; | |
| 89 | + | readSession(attemptId: string, viewer: Viewer, afterSeq?: number): Promise<Result<SessionEntry[]>>; | |
| 90 | + | } |
| 1 | + | { | |
| 2 | + | "extends": "../../tsconfig.base.json", | |
| 3 | + | "include": ["src/**/*"] | |
| 4 | + | } |
| 1 | + | -- Append-only log of every event published on the bus. Ids are | |
| 2 | + | -- time-sortable, so ordering by id is ordering by time. | |
| 3 | + | CREATE TABLE events ( | |
| 4 | + | id TEXT PRIMARY KEY, | |
| 5 | + | type TEXT NOT NULL, | |
| 6 | + | source TEXT NOT NULL, | |
| 7 | + | time INTEGER NOT NULL, | |
| 8 | + | repo_id TEXT, | |
| 9 | + | actor TEXT, | |
| 10 | + | data TEXT NOT NULL | |
| 11 | + | ); | |
| 12 | + | CREATE INDEX events_repo ON events (repo_id, id); |
| 1 | + | { | |
| 2 | + | "name": "@g1t/events", | |
| 3 | + | "version": "0.1.0", | |
| 4 | + | "private": true, | |
| 5 | + | "type": "module", | |
| 6 | + | "license": "MIT", | |
| 7 | + | "scripts": { | |
| 8 | + | "types": "wrangler types --include-env=false", | |
| 9 | + | "typecheck": "wrangler types --include-env=false && tsc -p tsconfig.json", | |
| 10 | + | "deploy": "wrangler deploy", | |
| 11 | + | "migrate": "wrangler d1 migrations apply DB --remote" | |
| 12 | + | }, | |
| 13 | + | "dependencies": { | |
| 14 | + | "@g1t/contracts": "*" | |
| 15 | + | } | |
| 16 | + | } |
| 1 | + | import { WorkerEntrypoint } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import { | |
| 4 | + | type EventQuery, | |
| 5 | + | type EventsApi, | |
| 6 | + | type G1tEvent, | |
| 7 | + | type NewEvent, | |
| 8 | + | newId, | |
| 9 | + | } from "@g1t/contracts"; | |
| 10 | + | ||
| 11 | + | export interface EventsEnv { | |
| 12 | + | DB: D1Database; | |
| 13 | + | BUS: Queue<G1tEvent>; | |
| 14 | + | /** Every `SUBSCRIBER_*` binding is a queue that receives all events. */ | |
| 15 | + | [subscriber: `SUBSCRIBER_${string}`]: Queue<G1tEvent>; | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | type EventRow = { | |
| 19 | + | id: string; | |
| 20 | + | type: string; | |
| 21 | + | source: string; | |
| 22 | + | time: number; | |
| 23 | + | repo_id: string | null; | |
| 24 | + | actor: string | null; | |
| 25 | + | data: string; | |
| 26 | + | }; | |
| 27 | + | ||
| 28 | + | const DEFAULT_PAGE = 50; | |
| 29 | + | const MAX_PAGE = 200; | |
| 30 | + | ||
| 31 | + | function toEvent(row: EventRow): G1tEvent { | |
| 32 | + | return { | |
| 33 | + | id: row.id, | |
| 34 | + | type: row.type, | |
| 35 | + | source: row.source, | |
| 36 | + | time: row.time, | |
| 37 | + | repoId: row.repo_id, | |
| 38 | + | actor: row.actor, | |
| 39 | + | data: JSON.parse(row.data), | |
| 40 | + | } as G1tEvent; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | /** | |
| 44 | + | * The event bus. Publishing enqueues; the queue consumer below writes the | |
| 45 | + | * durable log and fans each batch out to every subscriber's own queue. | |
| 46 | + | */ | |
| 47 | + | export default class EventsService | |
| 48 | + | extends WorkerEntrypoint<EventsEnv> | |
| 49 | + | implements EventsApi | |
| 50 | + | { | |
| 51 | + | async publish(events: NewEvent[]): Promise<void> { | |
| 52 | + | if (events.length === 0) return; | |
| 53 | + | const now = Date.now(); | |
| 54 | + | await this.env.BUS.sendBatch( | |
| 55 | + | events.map((event) => ({ | |
| 56 | + | body: { ...event, id: newId("evt", now), time: now } as G1tEvent, | |
| 57 | + | })), | |
| 58 | + | ); | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | /** Callers must have checked that the viewer may see `query.repoId`. */ | |
| 62 | + | async list(query: EventQuery): Promise<G1tEvent[]> { | |
| 63 | + | const where: string[] = []; | |
| 64 | + | const params: unknown[] = []; | |
| 65 | + | if (query.repoId) { | |
| 66 | + | where.push("repo_id = ?"); | |
| 67 | + | params.push(query.repoId); | |
| 68 | + | } | |
| 69 | + | if (query.types?.length) { | |
| 70 | + | where.push(`type IN (${query.types.map(() => "?").join(", ")})`); | |
| 71 | + | params.push(...query.types); | |
| 72 | + | } | |
| 73 | + | if (query.before) { | |
| 74 | + | where.push("id < ?"); | |
| 75 | + | params.push(query.before); | |
| 76 | + | } | |
| 77 | + | const limit = Math.min(query.limit ?? DEFAULT_PAGE, MAX_PAGE); | |
| 78 | + | const { results } = await this.env.DB.prepare( | |
| 79 | + | `SELECT * FROM events ${where.length ? `WHERE ${where.join(" AND ")}` : ""} | |
| 80 | + | ORDER BY id DESC LIMIT ?`, | |
| 81 | + | ) | |
| 82 | + | .bind(...params, limit) | |
| 83 | + | .all<EventRow>(); | |
| 84 | + | return results.map(toEvent); | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | async queue(batch: MessageBatch<G1tEvent>): Promise<void> { | |
| 88 | + | const events = batch.messages.map((message) => message.body); | |
| 89 | + | const insert = this.env.DB.prepare( | |
| 90 | + | // Redelivered batches must not duplicate log rows. | |
| 91 | + | `INSERT OR IGNORE INTO events (id, type, source, time, repo_id, actor, data) | |
| 92 | + | VALUES (?, ?, ?, ?, ?, ?, ?)`, | |
| 93 | + | ); | |
| 94 | + | await this.env.DB.batch( | |
| 95 | + | events.map((event) => | |
| 96 | + | insert.bind( | |
| 97 | + | event.id, | |
| 98 | + | event.type, | |
| 99 | + | event.source, | |
| 100 | + | event.time, | |
| 101 | + | event.repoId, | |
| 102 | + | event.actor, | |
| 103 | + | JSON.stringify(event.data), | |
| 104 | + | ), | |
| 105 | + | ), | |
| 106 | + | ); | |
| 107 | + | ||
| 108 | + | const subscribers = Object.entries(this.env) | |
| 109 | + | .filter(([name]) => name.startsWith("SUBSCRIBER_")) | |
| 110 | + | .map(([, queue]) => queue as Queue<G1tEvent>); | |
| 111 | + | await Promise.all( | |
| 112 | + | subscribers.map((queue) => | |
| 113 | + | queue.sendBatch(events.map((event) => ({ body: event }))), | |
| 114 | + | ), | |
| 115 | + | ); | |
| 116 | + | } | |
| 117 | + | } |
| 1 | + | { | |
| 2 | + | "extends": "../../tsconfig.base.json", | |
| 3 | + | "include": ["src/**/*", "worker-configuration.d.ts"] | |
| 4 | + | } |
| 1 | + | { | |
| 2 | + | "$schema": "../../node_modules/wrangler/config-schema.json", | |
| 3 | + | "name": "g1t-events", | |
| 4 | + | "account_id": "1e6f2cffa3f445920836e8ebe446bb58", | |
| 5 | + | "compatibility_date": "2026-09-26", | |
| 6 | + | "main": "./src/index.ts", | |
| 7 | + | "workers_dev": false, | |
| 8 | + | "d1_databases": [ | |
| 9 | + | { | |
| 10 | + | "binding": "DB", | |
| 11 | + | "database_name": "g1t-events", | |
| 12 | + | "database_id": "dd889749-0d01-4c94-8b9b-d789dec1c0e4", | |
| 13 | + | "migrations_dir": "migrations" | |
| 14 | + | } | |
| 15 | + | ], | |
| 16 | + | "queues": { | |
| 17 | + | "producers": [ | |
| 18 | + | { "binding": "BUS", "queue": "g1t-events" }, | |
| 19 | + | // One queue per subscribing service, so each consumes, retries | |
| 20 | + | // and scales on its own. Bindings named SUBSCRIBER_* receive | |
| 21 | + | // every event. | |
| 22 | + | { "binding": "SUBSCRIBER_WORK", "queue": "g1t-events-work" } | |
| 23 | + | ], | |
| 24 | + | "consumers": [{ "queue": "g1t-events", "max_batch_size": 100, "max_batch_timeout": 1 }] | |
| 25 | + | }, | |
| 26 | + | "observability": { "enabled": true } | |
| 27 | + | } |
| 1 | + | CREATE TABLE users ( | |
| 2 | + | id INTEGER PRIMARY KEY, | |
| 3 | + | username TEXT NOT NULL UNIQUE, | |
| 4 | + | email TEXT, | |
| 5 | + | password_hash TEXT NOT NULL, | |
| 6 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 7 | + | ); | |
| 8 | + | ||
| 9 | + | -- id is the SHA-256 of the session cookie value. | |
| 10 | + | CREATE TABLE sessions ( | |
| 11 | + | id TEXT PRIMARY KEY, | |
| 12 | + | user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 13 | + | expires_at INTEGER NOT NULL | |
| 14 | + | ); | |
| 15 | + | ||
| 16 | + | CREATE TABLE access_tokens ( | |
| 17 | + | id INTEGER PRIMARY KEY, | |
| 18 | + | user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 19 | + | name TEXT NOT NULL, | |
| 20 | + | token_hash TEXT NOT NULL UNIQUE, | |
| 21 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 22 | + | ); | |
| 23 | + | ||
| 24 | + | CREATE TABLE ssh_keys ( | |
| 25 | + | id INTEGER PRIMARY KEY, | |
| 26 | + | user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 27 | + | title TEXT NOT NULL, | |
| 28 | + | public_key TEXT NOT NULL, | |
| 29 | + | fingerprint TEXT NOT NULL UNIQUE, | |
| 30 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 31 | + | ); | |
| 32 | + | ||
| 33 | + | -- Stored in Artifacts as "<owner username>--<name>". | |
| 34 | + | CREATE TABLE repos ( | |
| 35 | + | id INTEGER PRIMARY KEY, | |
| 36 | + | owner_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 37 | + | name TEXT NOT NULL, | |
| 38 | + | description TEXT, | |
| 39 | + | is_private INTEGER NOT NULL DEFAULT 0, | |
| 40 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()), | |
| 41 | + | UNIQUE (owner_id, name) | |
| 42 | + | ); |
| 1 | + | -- Identity now owns only accounts and credentials, with prefixed text ids. | |
| 2 | + | -- Repositories moved to the repos service. | |
| 3 | + | DROP TABLE repos; | |
| 4 | + | DROP TABLE sessions; | |
| 5 | + | DROP TABLE access_tokens; | |
| 6 | + | DROP TABLE ssh_keys; | |
| 7 | + | ||
| 8 | + | ALTER TABLE users RENAME TO users_old; | |
| 9 | + | ||
| 10 | + | CREATE TABLE users ( | |
| 11 | + | id TEXT PRIMARY KEY, | |
| 12 | + | username TEXT NOT NULL UNIQUE, | |
| 13 | + | email TEXT, | |
| 14 | + | password_hash TEXT NOT NULL, | |
| 15 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 16 | + | ); | |
| 17 | + | ||
| 18 | + | INSERT INTO users (id, username, email, password_hash, created_at) | |
| 19 | + | SELECT 'usr_' || lower(hex(randomblob(13))), username, email, password_hash, created_at | |
| 20 | + | FROM users_old; | |
| 21 | + | ||
| 22 | + | DROP TABLE users_old; | |
| 23 | + | ||
| 24 | + | -- id is the SHA-256 of the session token. | |
| 25 | + | CREATE TABLE sessions ( | |
| 26 | + | id TEXT PRIMARY KEY, | |
| 27 | + | user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 28 | + | expires_at INTEGER NOT NULL | |
| 29 | + | ); | |
| 30 | + | ||
| 31 | + | CREATE TABLE access_tokens ( | |
| 32 | + | id TEXT PRIMARY KEY, | |
| 33 | + | user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 34 | + | name TEXT NOT NULL, | |
| 35 | + | token_hash TEXT NOT NULL UNIQUE, | |
| 36 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 37 | + | ); | |
| 38 | + | CREATE INDEX access_tokens_user ON access_tokens (user_id); | |
| 39 | + | ||
| 40 | + | CREATE TABLE ssh_keys ( | |
| 41 | + | id TEXT PRIMARY KEY, | |
| 42 | + | user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE, | |
| 43 | + | title TEXT NOT NULL, | |
| 44 | + | public_key TEXT NOT NULL, | |
| 45 | + | fingerprint TEXT NOT NULL UNIQUE, | |
| 46 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 47 | + | ); | |
| 48 | + | CREATE INDEX ssh_keys_user ON ssh_keys (user_id); |
| 1 | + | { | |
| 2 | + | "name": "@g1t/identity", | |
| 3 | + | "version": "0.1.0", | |
| 4 | + | "private": true, | |
| 5 | + | "type": "module", | |
| 6 | + | "license": "MIT", | |
| 7 | + | "scripts": { | |
| 8 | + | "types": "wrangler types --include-env=false", | |
| 9 | + | "typecheck": "wrangler types --include-env=false && tsc -p tsconfig.json", | |
| 10 | + | "deploy": "wrangler deploy", | |
| 11 | + | "migrate": "wrangler d1 migrations apply DB --remote" | |
| 12 | + | }, | |
| 13 | + | "dependencies": { | |
| 14 | + | "@g1t/contracts": "*" | |
| 15 | + | } | |
| 16 | + | } |
| 1 | + | // Creates a user (or resets their password) in the remote D1 database and | |
| 2 | + | // writes the generated password to the gitignored .credentials directory. | |
| 3 | + | // | |
| 4 | + | // node scripts/create-user.mjs <username> [email] | |
| 5 | + | import { execSync } from "node:child_process"; | |
| 6 | + | import { pbkdf2Sync, randomBytes } from "node:crypto"; | |
| 7 | + | import { mkdirSync, rmSync, writeFileSync } from "node:fs"; | |
| 8 | + | import { tmpdir } from "node:os"; | |
| 9 | + | import { join } from "node:path"; | |
| 10 | + | ||
| 11 | + | const [username, email] = process.argv.slice(2); | |
| 12 | + | if (!/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/.test(username ?? "")) { | |
| 13 | + | console.error("usage: node scripts/create-user.mjs <username> [email]"); | |
| 14 | + | process.exit(1); | |
| 15 | + | } | |
| 16 | + | if (email && !/^[^\s'@]+@[^\s'@]+$/.test(email)) { | |
| 17 | + | console.error("invalid email"); | |
| 18 | + | process.exit(1); | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // Must match hashPassword in src/crypto.ts. | |
| 22 | + | const iterations = 100_000; | |
| 23 | + | const password = randomBytes(18).toString("base64url"); | |
| 24 | + | const salt = randomBytes(16); | |
| 25 | + | const hash = pbkdf2Sync(password, salt, iterations, 32, "sha256"); | |
| 26 | + | const stored = `pbkdf2$${iterations}$${salt.toString("base64")}$${hash.toString("base64")}`; | |
| 27 | + | ||
| 28 | + | const sqlFile = join(tmpdir(), `g1t-create-user-${process.pid}.sql`); | |
| 29 | + | writeFileSync( | |
| 30 | + | sqlFile, | |
| 31 | + | `INSERT INTO users (id, username, email, password_hash) | |
| 32 | + | VALUES ('usr_' || lower(hex(randomblob(13))), '${username}', ${email ? `'${email}'` : "NULL"}, '${stored}') | |
| 33 | + | ON CONFLICT (username) DO UPDATE SET password_hash = excluded.password_hash;`, | |
| 34 | + | ); | |
| 35 | + | try { | |
| 36 | + | execSync(`npx wrangler d1 execute g1t --remote --yes --file "${sqlFile}"`, { | |
| 37 | + | cwd: join(import.meta.dirname, ".."), | |
| 38 | + | stdio: ["ignore", "ignore", "inherit"], | |
| 39 | + | }); | |
| 40 | + | } finally { | |
| 41 | + | rmSync(sqlFile, { force: true }); | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | const directory = join(import.meta.dirname, "../../../.credentials"); | |
| 45 | + | mkdirSync(directory, { recursive: true }); | |
| 46 | + | const file = join(directory, `${username}.txt`); | |
| 47 | + | writeFileSync(file, `username: ${username}\npassword: ${password}\n`); | |
| 48 | + | console.log(`Password for ${username} written to ${file}`); |
| 1 | + | // Workers cap PBKDF2 at 100,000 iterations. | |
| 2 | + | const PBKDF2_ITERATIONS = 100_000; | |
| 3 | + | ||
| 4 | + | const encoder = new TextEncoder(); | |
| 5 | + | ||
| 6 | + | export function toHex(bytes: ArrayBuffer | Uint8Array): string { | |
| 7 | + | return [...new Uint8Array(bytes)] | |
| 8 | + | .map((byte) => byte.toString(16).padStart(2, "0")) | |
| 9 | + | .join(""); | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | export function fromBase64(value: string): Uint8Array<ArrayBuffer> { | |
| 13 | + | return Uint8Array.from(atob(value), (char) => char.charCodeAt(0)); | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | export function toBase64(bytes: ArrayBuffer | Uint8Array): string { | |
| 17 | + | return btoa(String.fromCharCode(...new Uint8Array(bytes))); | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | export async function sha256Hex(value: string): Promise<string> { | |
| 21 | + | return toHex(await crypto.subtle.digest("SHA-256", encoder.encode(value))); | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | export function randomHex(bytes: number): string { | |
| 25 | + | return toHex(crypto.getRandomValues(new Uint8Array(bytes))); | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | async function pbkdf2( | |
| 29 | + | password: string, | |
| 30 | + | salt: Uint8Array<ArrayBuffer>, | |
| 31 | + | iterations: number, | |
| 32 | + | ): Promise<Uint8Array> { | |
| 33 | + | const key = await crypto.subtle.importKey( | |
| 34 | + | "raw", | |
| 35 | + | encoder.encode(password), | |
| 36 | + | "PBKDF2", | |
| 37 | + | false, | |
| 38 | + | ["deriveBits"], | |
| 39 | + | ); | |
| 40 | + | return new Uint8Array( | |
| 41 | + | await crypto.subtle.deriveBits( | |
| 42 | + | { name: "PBKDF2", hash: "SHA-256", salt, iterations }, | |
| 43 | + | key, | |
| 44 | + | 256, | |
| 45 | + | ), | |
| 46 | + | ); | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | /** Format: `pbkdf2$<iterations>$<salt base64>$<hash base64>`. */ | |
| 50 | + | export async function hashPassword(password: string): Promise<string> { | |
| 51 | + | const salt = crypto.getRandomValues(new Uint8Array(16)); | |
| 52 | + | const hash = await pbkdf2(password, salt, PBKDF2_ITERATIONS); | |
| 53 | + | return `pbkdf2$${PBKDF2_ITERATIONS}$${toBase64(salt)}$${toBase64(hash)}`; | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | export async function verifyPassword( | |
| 57 | + | password: string, | |
| 58 | + | stored: string, | |
| 59 | + | ): Promise<boolean> { | |
| 60 | + | const [scheme, iterations, salt, hash] = stored.split("$"); | |
| 61 | + | if (scheme !== "pbkdf2") return false; | |
| 62 | + | const expected = fromBase64(hash); | |
| 63 | + | const given = await pbkdf2(password, fromBase64(salt), Number(iterations)); | |
| 64 | + | // Constant-time comparison. | |
| 65 | + | let diff = given.length ^ expected.length; | |
| 66 | + | for (let i = 0; i < expected.length; i++) diff |= given[i] ^ expected[i]; | |
| 67 | + | return diff === 0; | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | /** | |
| 71 | + | * Parses an OpenSSH public key line. The fingerprint matches | |
| 72 | + | * `ssh-keygen -lf` (SHA256, unpadded base64). | |
| 73 | + | */ | |
| 74 | + | export async function parseSshKey( | |
| 75 | + | line: string, | |
| 76 | + | ): Promise<{ publicKey: string; fingerprint: string; comment: string } | null> { | |
| 77 | + | const [type, blob, ...comment] = line.trim().split(/\s+/); | |
| 78 | + | if (!/^(ssh-(ed25519|rsa)|ecdsa-sha2-nistp(256|384|521))$/.test(type ?? "")) { | |
| 79 | + | return null; | |
| 80 | + | } | |
| 81 | + | let bytes: Uint8Array<ArrayBuffer>; | |
| 82 | + | try { | |
| 83 | + | bytes = fromBase64(blob ?? ""); | |
| 84 | + | // The blob starts with its own length-prefixed copy of the key type. | |
| 85 | + | const typeLength = new DataView(bytes.buffer).getUint32(0); | |
| 86 | + | if (new TextDecoder().decode(bytes.slice(4, 4 + typeLength)) !== type) { | |
| 87 | + | return null; | |
| 88 | + | } | |
| 89 | + | } catch { | |
| 90 | + | return null; | |
| 91 | + | } | |
| 92 | + | const digest = await crypto.subtle.digest("SHA-256", bytes); | |
| 93 | + | return { | |
| 94 | + | publicKey: `${type} ${blob}`, | |
| 95 | + | fingerprint: `SHA256:${toBase64(digest).replace(/=+$/, "")}`, | |
| 96 | + | comment: comment.join(" "), | |
| 97 | + | }; | |
| 98 | + | } |
| 1 | + | import { WorkerEntrypoint } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import { | |
| 4 | + | type AccessToken, | |
| 5 | + | type IdentityApi, | |
| 6 | + | type Result, | |
| 7 | + | type SshKey, | |
| 8 | + | type User, | |
| 9 | + | type Viewer, | |
| 10 | + | fail, | |
| 11 | + | newId, | |
| 12 | + | ok, | |
| 13 | + | } from "@g1t/contracts"; | |
| 14 | + | ||
| 15 | + | import { | |
| 16 | + | parseSshKey, | |
| 17 | + | randomHex, | |
| 18 | + | sha256Hex, | |
| 19 | + | verifyPassword, | |
| 20 | + | } from "./crypto"; | |
| 21 | + | ||
| 22 | + | export interface IdentityEnv { | |
| 23 | + | DB: D1Database; | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | const SESSION_TTL_SECONDS = 30 * 24 * 60 * 60; | |
| 27 | + | const TOKEN_PREFIX = "g1t_"; | |
| 28 | + | ||
| 29 | + | type SshKeyRow = { id: string; title: string; fingerprint: string; created_at: number }; | |
| 30 | + | type TokenRow = { id: string; name: string; created_at: number }; | |
| 31 | + | ||
| 32 | + | function toSshKey(row: SshKeyRow): SshKey { | |
| 33 | + | return { | |
| 34 | + | id: row.id, | |
| 35 | + | title: row.title, | |
| 36 | + | fingerprint: row.fingerprint, | |
| 37 | + | createdAt: row.created_at * 1000, | |
| 38 | + | }; | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | function toAccessToken(row: TokenRow): AccessToken { | |
| 42 | + | return { id: row.id, name: row.name, createdAt: row.created_at * 1000 }; | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | export default class IdentityService | |
| 46 | + | extends WorkerEntrypoint<IdentityEnv> | |
| 47 | + | implements IdentityApi | |
| 48 | + | { | |
| 49 | + | private get db(): D1Database { | |
| 50 | + | return this.env.DB; | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | /** A Worker must have an event handler; this service is RPC-only. */ | |
| 54 | + | fetch(): Response { | |
| 55 | + | return new Response("Not found\n", { status: 404 }); | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | private async userForPassword(username: string, password: string): Promise<Viewer> { | |
| 59 | + | const row = await this.db | |
| 60 | + | .prepare("SELECT id, username, password_hash FROM users WHERE username = ?") | |
| 61 | + | .bind(username.toLowerCase()) | |
| 62 | + | .first<User & { password_hash: string }>(); | |
| 63 | + | if (!row || !(await verifyPassword(password, row.password_hash))) return null; | |
| 64 | + | return { id: row.id, username: row.username }; | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | async signIn( | |
| 68 | + | username: string, | |
| 69 | + | password: string, | |
| 70 | + | ): Promise<Result<{ user: User; sessionToken: string }>> { | |
| 71 | + | const user = await this.userForPassword(username, password); | |
| 72 | + | if (!user) return fail("unauthenticated", "Incorrect username or password."); | |
| 73 | + | const sessionToken = randomHex(32); | |
| 74 | + | await this.db | |
| 75 | + | .prepare( | |
| 76 | + | "INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, unixepoch() + ?)", | |
| 77 | + | ) | |
| 78 | + | .bind(await sha256Hex(sessionToken), user.id, SESSION_TTL_SECONDS) | |
| 79 | + | .run(); | |
| 80 | + | return ok({ user, sessionToken }); | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | async signOut(sessionToken: string): Promise<void> { | |
| 84 | + | await this.db | |
| 85 | + | .prepare("DELETE FROM sessions WHERE id = ?") | |
| 86 | + | .bind(await sha256Hex(sessionToken)) | |
| 87 | + | .run(); | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | async userForSession(sessionToken: string): Promise<Viewer> { | |
| 91 | + | return this.db | |
| 92 | + | .prepare( | |
| 93 | + | `SELECT users.id, users.username FROM sessions | |
| 94 | + | JOIN users ON users.id = sessions.user_id | |
| 95 | + | WHERE sessions.id = ? AND sessions.expires_at > unixepoch()`, | |
| 96 | + | ) | |
| 97 | + | .bind(await sha256Hex(sessionToken)) | |
| 98 | + | .first<User>(); | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | async userForGitCredentials(username: string, secret: string): Promise<Viewer> { | |
| 102 | + | if (!secret.startsWith(TOKEN_PREFIX)) { | |
| 103 | + | return this.userForPassword(username, secret); | |
| 104 | + | } | |
| 105 | + | // Like GitHub, a token alone identifies its user. | |
| 106 | + | return this.db | |
| 107 | + | .prepare( | |
| 108 | + | `SELECT users.id, users.username FROM access_tokens | |
| 109 | + | JOIN users ON users.id = access_tokens.user_id | |
| 110 | + | WHERE token_hash = ?`, | |
| 111 | + | ) | |
| 112 | + | .bind(await sha256Hex(secret)) | |
| 113 | + | .first<User>(); | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | async userForSshKey(fingerprint: string): Promise<Viewer> { | |
| 117 | + | return this.db | |
| 118 | + | .prepare( | |
| 119 | + | `SELECT users.id, users.username FROM ssh_keys | |
| 120 | + | JOIN users ON users.id = ssh_keys.user_id | |
| 121 | + | WHERE fingerprint = ?`, | |
| 122 | + | ) | |
| 123 | + | .bind(fingerprint) | |
| 124 | + | .first<User>(); | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | async userByUsername(username: string): Promise<Viewer> { | |
| 128 | + | return this.db | |
| 129 | + | .prepare("SELECT id, username FROM users WHERE username = ?") | |
| 130 | + | .bind(username.toLowerCase()) | |
| 131 | + | .first<User>(); | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | async listSshKeys(user: User): Promise<SshKey[]> { | |
| 135 | + | const { results } = await this.db | |
| 136 | + | .prepare( | |
| 137 | + | "SELECT id, title, fingerprint, created_at FROM ssh_keys WHERE user_id = ? ORDER BY id", | |
| 138 | + | ) | |
| 139 | + | .bind(user.id) | |
| 140 | + | .all<SshKeyRow>(); | |
| 141 | + | return results.map(toSshKey); | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | async addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>> { | |
| 145 | + | const key = await parseSshKey(publicKey); | |
| 146 | + | if (!key) return fail("invalid", "That is not a valid OpenSSH public key."); | |
| 147 | + | const taken = await this.db | |
| 148 | + | .prepare("SELECT 1 FROM ssh_keys WHERE fingerprint = ?") | |
| 149 | + | .bind(key.fingerprint) | |
| 150 | + | .first(); | |
| 151 | + | if (taken) return fail("conflict", "That key is already registered."); | |
| 152 | + | const row: SshKeyRow = { | |
| 153 | + | id: newId("key"), | |
| 154 | + | title: title.trim() || key.comment || "SSH key", | |
| 155 | + | fingerprint: key.fingerprint, | |
| 156 | + | created_at: Math.floor(Date.now() / 1000), | |
| 157 | + | }; | |
| 158 | + | await this.db | |
| 159 | + | .prepare( | |
| 160 | + | "INSERT INTO ssh_keys (id, user_id, title, public_key, fingerprint, created_at) VALUES (?, ?, ?, ?, ?, ?)", | |
| 161 | + | ) | |
| 162 | + | .bind(row.id, user.id, row.title, key.publicKey, row.fingerprint, row.created_at) | |
| 163 | + | .run(); | |
| 164 | + | return ok(toSshKey(row)); | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | async removeSshKey(user: User, id: string): Promise<void> { | |
| 168 | + | await this.db | |
| 169 | + | .prepare("DELETE FROM ssh_keys WHERE id = ? AND user_id = ?") | |
| 170 | + | .bind(id, user.id) | |
| 171 | + | .run(); | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | async listAccessTokens(user: User): Promise<AccessToken[]> { | |
| 175 | + | const { results } = await this.db | |
| 176 | + | .prepare( | |
| 177 | + | "SELECT id, name, created_at FROM access_tokens WHERE user_id = ? ORDER BY id", | |
| 178 | + | ) | |
| 179 | + | .bind(user.id) | |
| 180 | + | .all<TokenRow>(); | |
| 181 | + | return results.map(toAccessToken); | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | async createAccessToken( | |
| 185 | + | user: User, | |
| 186 | + | name: string, | |
| 187 | + | ): Promise<{ token: string; info: AccessToken }> { | |
| 188 | + | const token = TOKEN_PREFIX + randomHex(20); | |
| 189 | + | const row: TokenRow = { | |
| 190 | + | id: newId("tok"), | |
| 191 | + | name: name.trim() || "Access token", | |
| 192 | + | created_at: Math.floor(Date.now() / 1000), | |
| 193 | + | }; | |
| 194 | + | await this.db | |
| 195 | + | .prepare( | |
| 196 | + | "INSERT INTO access_tokens (id, user_id, name, token_hash, created_at) VALUES (?, ?, ?, ?, ?)", | |
| 197 | + | ) | |
| 198 | + | .bind(row.id, user.id, row.name, await sha256Hex(token), row.created_at) | |
| 199 | + | .run(); | |
| 200 | + | return { token, info: toAccessToken(row) }; | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | async removeAccessToken(user: User, id: string): Promise<void> { | |
| 204 | + | await this.db | |
| 205 | + | .prepare("DELETE FROM access_tokens WHERE id = ? AND user_id = ?") | |
| 206 | + | .bind(id, user.id) | |
| 207 | + | .run(); | |
| 208 | + | } | |
| 209 | + | } |
| 1 | + | { | |
| 2 | + | "extends": "../../tsconfig.base.json", | |
| 3 | + | "include": ["src/**/*", "worker-configuration.d.ts"] | |
| 4 | + | } |
| 1 | + | { | |
| 2 | + | "$schema": "../../node_modules/wrangler/config-schema.json", | |
| 3 | + | "name": "g1t-identity", | |
| 4 | + | "account_id": "1e6f2cffa3f445920836e8ebe446bb58", | |
| 5 | + | "compatibility_date": "2026-09-26", | |
| 6 | + | "main": "./src/index.ts", | |
| 7 | + | // Reached only through service bindings. | |
| 8 | + | "workers_dev": false, | |
| 9 | + | "d1_databases": [ | |
| 10 | + | { | |
| 11 | + | "binding": "DB", | |
| 12 | + | "database_name": "g1t", | |
| 13 | + | "database_id": "b7d49c93-2666-4006-a3c3-073a01838dc9", | |
| 14 | + | "migrations_dir": "migrations" | |
| 15 | + | } | |
| 16 | + | ], | |
| 17 | + | "observability": { "enabled": true } | |
| 18 | + | } |
| 1 | + | -- Stored in Artifacts as "<namespace>--<name>". | |
| 2 | + | CREATE TABLE repos ( | |
| 3 | + | id TEXT PRIMARY KEY, | |
| 4 | + | namespace TEXT NOT NULL, | |
| 5 | + | name TEXT NOT NULL, | |
| 6 | + | description TEXT, | |
| 7 | + | is_private INTEGER NOT NULL DEFAULT 0, | |
| 8 | + | owner_id TEXT NOT NULL, | |
| 9 | + | default_branch TEXT NOT NULL DEFAULT 'main', | |
| 10 | + | -- Set on an attempt's working copy; those are hidden from listings. | |
| 11 | + | fork_of TEXT REFERENCES repos (id), | |
| 12 | + | created_at INTEGER NOT NULL DEFAULT (unixepoch()), | |
| 13 | + | UNIQUE (namespace, name) | |
| 14 | + | ); | |
| 15 | + | CREATE INDEX repos_owner ON repos (owner_id); |
| 1 | + | { | |
| 2 | + | "name": "@g1t/repos", | |
| 3 | + | "version": "0.1.0", | |
| 4 | + | "private": true, | |
| 5 | + | "type": "module", | |
| 6 | + | "license": "MIT", | |
| 7 | + | "scripts": { | |
| 8 | + | "types": "wrangler types --include-env=false", | |
| 9 | + | "typecheck": "wrangler types --include-env=false && tsc -p tsconfig.json", | |
| 10 | + | "deploy": "wrangler deploy", | |
| 11 | + | "migrate": "wrangler d1 migrations apply DB --remote" | |
| 12 | + | }, | |
| 13 | + | "dependencies": { | |
| 14 | + | "@g1t/contracts": "*" | |
| 15 | + | } | |
| 16 | + | } |
| 1 | + | import type { Commit, GitAccess, TreeEntry } from "@g1t/contracts"; | |
| 2 | + | ||
| 3 | + | import type { GitStore } from "./git-store"; | |
| 4 | + | ||
| 5 | + | const TOKEN_TTL_SECONDS = 300; | |
| 6 | + | ||
| 7 | + | function isCode(error: unknown, code: ArtifactsErrorCode): boolean { | |
| 8 | + | return (error as Partial<ArtifactsError> | null)?.code === code; | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | function toCommit(commit: ArtifactsCommitMetadata): Commit { | |
| 12 | + | return { | |
| 13 | + | hash: commit.hash, | |
| 14 | + | treeHash: commit.treeHash, | |
| 15 | + | message: commit.message, | |
| 16 | + | author: commit.author, | |
| 17 | + | parents: commit.parents, | |
| 18 | + | authoredAt: commit.authoredAt * 1000, | |
| 19 | + | }; | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | /** {@link GitStore} backed by Cloudflare Artifacts. */ | |
| 23 | + | export class ArtifactsGitStore implements GitStore { | |
| 24 | + | constructor(private readonly artifacts: Artifacts) {} | |
| 25 | + | ||
| 26 | + | private async use<T>(key: string, fn: (repo: ArtifactsRepo) => Promise<T>): Promise<T> { | |
| 27 | + | const repo = await this.artifacts.get(key); | |
| 28 | + | try { | |
| 29 | + | return await fn(repo); | |
| 30 | + | } finally { | |
| 31 | + | repo[Symbol.dispose](); | |
| 32 | + | } | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | async create( | |
| 36 | + | key: string, | |
| 37 | + | options: { description?: string; defaultBranch: string }, | |
| 38 | + | ): Promise<void> { | |
| 39 | + | try { | |
| 40 | + | await this.artifacts.create(key, { | |
| 41 | + | description: options.description, | |
| 42 | + | setDefaultBranch: options.defaultBranch, | |
| 43 | + | }); | |
| 44 | + | } catch (error) { | |
| 45 | + | // Left behind by an earlier failed attempt; adopt it. | |
| 46 | + | if (!isCode(error, "ALREADY_EXISTS")) throw error; | |
| 47 | + | } | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | async fork(sourceKey: string, targetKey: string): Promise<void> { | |
| 51 | + | await this.use(sourceKey, async (repo) => { | |
| 52 | + | try { | |
| 53 | + | await repo.fork(targetKey, { defaultBranchOnly: true }); | |
| 54 | + | } catch (error) { | |
| 55 | + | if (!isCode(error, "ALREADY_EXISTS")) throw error; | |
| 56 | + | } | |
| 57 | + | }); | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | async access(key: string, scope: "read" | "write"): Promise<GitAccess> { | |
| 61 | + | return this.use(key, async (repo) => { | |
| 62 | + | const [info, token] = await Promise.all([ | |
| 63 | + | repo.info(), | |
| 64 | + | repo.createToken(scope, TOKEN_TTL_SECONDS), | |
| 65 | + | ]); | |
| 66 | + | return { remote: info.remote, token: token.plaintext }; | |
| 67 | + | }); | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | async log(key: string, ref: string, limit: number): Promise<Commit[]> { | |
| 71 | + | return this.use(key, async (repo) => | |
| 72 | + | (await repo.log({ ref, limit })).map(toCommit), | |
| 73 | + | ); | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | async readTree(key: string, treeHash: string): Promise<TreeEntry[] | null> { | |
| 77 | + | return this.use(key, async (repo) => { | |
| 78 | + | const entries = await repo.readTree(treeHash); | |
| 79 | + | return ( | |
| 80 | + | entries?.map((entry) => ({ | |
| 81 | + | name: entry.name, | |
| 82 | + | hash: entry.hash, | |
| 83 | + | kind: entry.type, | |
| 84 | + | })) ?? null | |
| 85 | + | ); | |
| 86 | + | }); | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | async readBlob(key: string, blobHash: string): Promise<Blob | null> { | |
| 90 | + | return this.use(key, (repo) => repo.readBlob(blobHash)); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | async readFile(key: string, ref: string, path: string): Promise<Blob | null> { | |
| 94 | + | return this.use(key, (repo) => repo.readFile({ ref, path })); | |
| 95 | + | } | |
| 96 | + | } |
| 1 | + | import { | |
| 2 | + | type GitService, | |
| 3 | + | type IdentityApi, | |
| 4 | + | type RepoPath, | |
| 5 | + | type ReposApi, | |
| 6 | + | type Viewer, | |
| 7 | + | httpStatus, | |
| 8 | + | } from "@g1t/contracts"; | |
| 9 | + | ||
| 10 | + | const GIT_ROUTE = | |
| 11 | + | /^\/([^/]+)\/([^/]+?)(?:\.git)?\/(info\/refs|git-upload-pack|git-receive-pack)$/; | |
| 12 | + | const FORWARDED_HEADERS = [ | |
| 13 | + | "accept", | |
| 14 | + | "content-encoding", | |
| 15 | + | "content-type", | |
| 16 | + | "git-protocol", | |
| 17 | + | "user-agent", | |
| 18 | + | ]; | |
| 19 | + | ||
| 20 | + | async function viewerFromBasicAuth( | |
| 21 | + | request: Request, | |
| 22 | + | identity: IdentityApi, | |
| 23 | + | ): Promise<Viewer> { | |
| 24 | + | const [scheme, encoded] = (request.headers.get("authorization") ?? "").split(" "); | |
| 25 | + | if (scheme?.toLowerCase() !== "basic" || !encoded) return null; | |
| 26 | + | let decoded: string; | |
| 27 | + | try { | |
| 28 | + | decoded = atob(encoded); | |
| 29 | + | } catch { | |
| 30 | + | return null; | |
| 31 | + | } | |
| 32 | + | const separator = decoded.indexOf(":"); | |
| 33 | + | if (separator < 0) return null; | |
| 34 | + | return identity.userForGitCredentials( | |
| 35 | + | decoded.slice(0, separator), | |
| 36 | + | decoded.slice(separator + 1), | |
| 37 | + | ); | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | /** | |
| 41 | + | * Smart HTTP git remote at `/<namespace>/<repo>.git`, proxied to the git | |
| 42 | + | * store with a short-lived token. Returns null for requests that are not git. | |
| 43 | + | */ | |
| 44 | + | export async function handleGitHttp( | |
| 45 | + | request: Request, | |
| 46 | + | identity: IdentityApi, | |
| 47 | + | repos: Pick<ReposApi, "gitAccess">, | |
| 48 | + | onPush: (path: RepoPath) => void, | |
| 49 | + | ): Promise<Response | null> { | |
| 50 | + | const url = new URL(request.url); | |
| 51 | + | const match = GIT_ROUTE.exec(url.pathname); | |
| 52 | + | if (!match) return null; | |
| 53 | + | const [, namespace, name, endpoint] = match; | |
| 54 | + | const service = | |
| 55 | + | endpoint === "info/refs" ? url.searchParams.get("service") : endpoint; | |
| 56 | + | if (service !== "git-upload-pack" && service !== "git-receive-pack") { | |
| 57 | + | return null; | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | const viewer = await viewerFromBasicAuth(request, identity); | |
| 61 | + | const access = await repos.gitAccess( | |
| 62 | + | { namespace, name }, | |
| 63 | + | viewer, | |
| 64 | + | service as GitService, | |
| 65 | + | ); | |
| 66 | + | if (!access.ok) { | |
| 67 | + | const status = httpStatus(access.error); | |
| 68 | + | return new Response(`${access.error.message}\n`, { | |
| 69 | + | status, | |
| 70 | + | headers: status === 401 ? { "www-authenticate": 'Basic realm="g1t"' } : {}, | |
| 71 | + | }); | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | const headers = new Headers({ authorization: `Bearer ${access.value.token}` }); | |
| 75 | + | for (const header of FORWARDED_HEADERS) { | |
| 76 | + | const value = request.headers.get(header); | |
| 77 | + | if (value) headers.set(header, value); | |
| 78 | + | } | |
| 79 | + | const response = await fetch(`${access.value.remote}/${endpoint}${url.search}`, { | |
| 80 | + | method: request.method, | |
| 81 | + | headers, | |
| 82 | + | body: request.body, | |
| 83 | + | }); | |
| 84 | + | if (endpoint === "git-receive-pack" && response.ok) { | |
| 85 | + | onPush({ namespace, name }); | |
| 86 | + | } | |
| 87 | + | return response; | |
| 88 | + | } |
| 1 | + | import type { Commit, GitAccess, TreeEntry } from "@g1t/contracts"; | |
| 2 | + | ||
| 3 | + | /** | |
| 4 | + | * The storage that actually holds git repositories. The repos service | |
| 5 | + | * depends on this port; Artifacts is one adapter for it. | |
| 6 | + | * | |
| 7 | + | * `key` is the store's own name for a repo. | |
| 8 | + | */ | |
| 9 | + | export interface GitStore { | |
| 10 | + | create(key: string, options: { description?: string; defaultBranch: string }): Promise<void>; | |
| 11 | + | /** A copy-on-write copy of `sourceKey`. */ | |
| 12 | + | fork(sourceKey: string, targetKey: string): Promise<void>; | |
| 13 | + | /** A remote URL and short-lived credential for git itself. */ | |
| 14 | + | access(key: string, scope: "read" | "write"): Promise<GitAccess>; | |
| 15 | + | ||
| 16 | + | /** Newest first along the first-parent chain; empty for an unknown ref. */ | |
| 17 | + | log(key: string, ref: string, limit: number): Promise<Commit[]>; | |
| 18 | + | /** Null when the tree does not exist. */ | |
| 19 | + | readTree(key: string, treeHash: string): Promise<TreeEntry[] | null>; | |
| 20 | + | readBlob(key: string, blobHash: string): Promise<Blob | null>; | |
| 21 | + | /** Null when the ref or path does not resolve to a file. */ | |
| 22 | + | readFile(key: string, ref: string, path: string): Promise<Blob | null>; | |
| 23 | + | } |
| 1 | + | import { WorkerEntrypoint } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import { | |
| 4 | + | type BlobView, | |
| 5 | + | type Commit, | |
| 6 | + | type CreateRepoInput, | |
| 7 | + | type EventsApi, | |
| 8 | + | type GitAccess, | |
| 9 | + | type GitService, | |
| 10 | + | type IdentityApi, | |
| 11 | + | type NewEvent, | |
| 12 | + | type Repo, | |
| 13 | + | type RepoPath, | |
| 14 | + | type ReposApi, | |
| 15 | + | type Result, | |
| 16 | + | type TreeView, | |
| 17 | + | type User, | |
| 18 | + | type Viewer, | |
| 19 | + | fail, | |
| 20 | + | isValidNamespace, | |
| 21 | + | isValidRepoName, | |
| 22 | + | newId, | |
| 23 | + | ok, | |
| 24 | + | } from "@g1t/contracts"; | |
| 25 | + | ||
| 26 | + | import { ArtifactsGitStore } from "./artifacts-git-store"; | |
| 27 | + | import { handleGitHttp } from "./git-http"; | |
| 28 | + | import type { GitStore } from "./git-store"; | |
| 29 | + | import { | |
| 30 | + | RepoRegistry, | |
| 31 | + | canRead, | |
| 32 | + | canWrite, | |
| 33 | + | storeKey, | |
| 34 | + | } from "./registry"; | |
| 35 | + | ||
| 36 | + | export interface ReposEnv { | |
| 37 | + | DB: D1Database; | |
| 38 | + | ARTIFACTS: Artifacts; | |
| 39 | + | IDENTITY: IdentityApi; | |
| 40 | + | EVENTS: EventsApi; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | /** Namespace that holds every attempt's fork: `attempts/<attempt id>`. */ | |
| 44 | + | const ATTEMPTS_NAMESPACE = "attempts"; | |
| 45 | + | const MAX_TEXT_BYTES = 512 * 1024; | |
| 46 | + | const README = /^readme(\.(md|markdown|txt))?$/i; | |
| 47 | + | const SOURCE = "repos"; | |
| 48 | + | ||
| 49 | + | const NOT_FOUND = fail("not_found", "Repository not found."); | |
| 50 | + | ||
| 51 | + | /** Decoded text, or null when the blob is too large or looks binary. */ | |
| 52 | + | async function blobText(blob: Blob): Promise<string | null> { | |
| 53 | + | if (blob.size > MAX_TEXT_BYTES) return null; | |
| 54 | + | const bytes = new Uint8Array(await blob.arrayBuffer()); | |
| 55 | + | if (bytes.includes(0)) return null; | |
| 56 | + | return new TextDecoder().decode(bytes); | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | export default class ReposService | |
| 60 | + | extends WorkerEntrypoint<ReposEnv> | |
| 61 | + | implements ReposApi | |
| 62 | + | { | |
| 63 | + | private readonly registry = new RepoRegistry(this.env.DB); | |
| 64 | + | private readonly store: GitStore = new ArtifactsGitStore(this.env.ARTIFACTS); | |
| 65 | + | ||
| 66 | + | /** Resolves a repo the viewer may read; private repos look missing. */ | |
| 67 | + | private async readable(path: RepoPath, viewer: Viewer): Promise<Repo | null> { | |
| 68 | + | const repo = await this.registry.byPath(path); | |
| 69 | + | return repo && canRead(repo, viewer) ? repo : null; | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | async get(path: RepoPath, viewer: Viewer): Promise<Result<Repo>> { | |
| 73 | + | const repo = await this.readable(path, viewer); | |
| 74 | + | return repo ? ok(repo) : NOT_FOUND; | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | async getById(id: string, viewer: Viewer): Promise<Result<Repo>> { | |
| 78 | + | const repo = await this.registry.byId(id); | |
| 79 | + | return repo && canRead(repo, viewer) ? ok(repo) : NOT_FOUND; | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | async list( | |
| 83 | + | viewer: Viewer, | |
| 84 | + | options: { query?: string; namespace?: string } = {}, | |
| 85 | + | ): Promise<Repo[]> { | |
| 86 | + | return this.registry.list(viewer, options); | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | async create(owner: User, input: CreateRepoInput): Promise<Result<Repo>> { | |
| 90 | + | const name = input.name.trim().toLowerCase(); | |
| 91 | + | if (!isValidRepoName(name)) { | |
| 92 | + | return fail("invalid", "Use letters, digits, dots, hyphens and underscores only."); | |
| 93 | + | } | |
| 94 | + | if (!isValidNamespace(owner.username)) { | |
| 95 | + | return fail("invalid", "This account cannot own repositories."); | |
| 96 | + | } | |
| 97 | + | const path = { namespace: owner.username, name }; | |
| 98 | + | if (await this.registry.byPath(path)) { | |
| 99 | + | return fail("conflict", "You already have a repository with that name."); | |
| 100 | + | } | |
| 101 | + | const repo: Repo = { | |
| 102 | + | id: newId("rep"), | |
| 103 | + | ...path, | |
| 104 | + | description: input.description?.trim() || null, | |
| 105 | + | isPrivate: input.isPrivate ?? false, | |
| 106 | + | ownerId: owner.id, | |
| 107 | + | defaultBranch: "main", | |
| 108 | + | forkOf: null, | |
| 109 | + | createdAt: Date.now(), | |
| 110 | + | }; | |
| 111 | + | await this.store.create(storeKey(repo), { | |
| 112 | + | description: repo.description ?? undefined, | |
| 113 | + | defaultBranch: repo.defaultBranch, | |
| 114 | + | }); | |
| 115 | + | await this.registry.insert(repo); | |
| 116 | + | await this.publish({ | |
| 117 | + | type: "repo.created", | |
| 118 | + | source: SOURCE, | |
| 119 | + | repoId: repo.id, | |
| 120 | + | actor: owner.id, | |
| 121 | + | data: { | |
| 122 | + | repoId: repo.id, | |
| 123 | + | namespace: repo.namespace, | |
| 124 | + | name: repo.name, | |
| 125 | + | isPrivate: repo.isPrivate, | |
| 126 | + | }, | |
| 127 | + | }); | |
| 128 | + | return ok(repo); | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | async tree( | |
| 132 | + | path: RepoPath, | |
| 133 | + | viewer: Viewer, | |
| 134 | + | ref: string | null, | |
| 135 | + | treePath: string, | |
| 136 | + | ): Promise<Result<TreeView>> { | |
| 137 | + | const repo = await this.readable(path, viewer); | |
| 138 | + | if (!repo) return NOT_FOUND; | |
| 139 | + | const key = storeKey(repo); | |
| 140 | + | const resolvedRef = ref ?? repo.defaultBranch; | |
| 141 | + | const base = { repo, ref: resolvedRef, path: treePath }; | |
| 142 | + | ||
| 143 | + | const [head] = await this.store.log(key, resolvedRef, 1); | |
| 144 | + | if (!head) { | |
| 145 | + | // An unknown ref is an error; a repo with no commits is just empty. | |
| 146 | + | if (ref) return fail("not_found", "No such branch, tag or commit."); | |
| 147 | + | return ok({ ...base, head: null, entries: [], readme: null }); | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | let entries = await this.store.readTree(key, head.treeHash); | |
| 151 | + | for (const segment of treePath.split("/").filter(Boolean)) { | |
| 152 | + | const next = entries?.find( | |
| 153 | + | (entry) => entry.name === segment && entry.kind === "tree", | |
| 154 | + | ); | |
| 155 | + | if (!next) return fail("not_found", "No such directory."); | |
| 156 | + | entries = await this.store.readTree(key, next.hash); | |
| 157 | + | } | |
| 158 | + | if (!entries) return fail("not_found", "No such directory."); | |
| 159 | + | entries.sort( | |
| 160 | + | (a, b) => | |
| 161 | + | Number(b.kind === "tree") - Number(a.kind === "tree") || | |
| 162 | + | a.name.localeCompare(b.name), | |
| 163 | + | ); | |
| 164 | + | ||
| 165 | + | const readmeEntry = entries.find( | |
| 166 | + | (entry) => entry.kind === "blob" && README.test(entry.name), | |
| 167 | + | ); | |
| 168 | + | const readmeBlob = readmeEntry | |
| 169 | + | ? await this.store.readBlob(key, readmeEntry.hash) | |
| 170 | + | : null; | |
| 171 | + | const readme = | |
| 172 | + | readmeEntry && readmeBlob | |
| 173 | + | ? { name: readmeEntry.name, text: await blobText(readmeBlob) } | |
| 174 | + | : null; | |
| 175 | + | return ok({ ...base, head, entries, readme }); | |
| 176 | + | } | |
| 177 | + | ||
| 178 | + | async blob( | |
| 179 | + | path: RepoPath, | |
| 180 | + | viewer: Viewer, | |
| 181 | + | ref: string, | |
| 182 | + | filePath: string, | |
| 183 | + | ): Promise<Result<BlobView>> { | |
| 184 | + | const repo = await this.readable(path, viewer); | |
| 185 | + | if (!repo) return NOT_FOUND; | |
| 186 | + | const blob = filePath | |
| 187 | + | ? await this.store.readFile(storeKey(repo), ref, filePath) | |
| 188 | + | : null; | |
| 189 | + | if (!blob) return fail("not_found", "No such file."); | |
| 190 | + | return ok({ | |
| 191 | + | repo, | |
| 192 | + | ref, | |
| 193 | + | path: filePath, | |
| 194 | + | size: blob.size, | |
| 195 | + | text: await blobText(blob), | |
| 196 | + | }); | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | async log( | |
| 200 | + | path: RepoPath, | |
| 201 | + | viewer: Viewer, | |
| 202 | + | ref: string | null, | |
| 203 | + | limit: number, | |
| 204 | + | ): Promise<Result<Commit[]>> { | |
| 205 | + | const repo = await this.readable(path, viewer); | |
| 206 | + | if (!repo) return NOT_FOUND; | |
| 207 | + | return ok( | |
| 208 | + | await this.store.log(storeKey(repo), ref ?? repo.defaultBranch, limit), | |
| 209 | + | ); | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | async forkForAttempt( | |
| 213 | + | sourceId: string, | |
| 214 | + | attemptId: string, | |
| 215 | + | actor: User, | |
| 216 | + | ): Promise<Result<Repo>> { | |
| 217 | + | const source = await this.registry.byId(sourceId); | |
| 218 | + | if (!source || !canRead(source, actor)) return NOT_FOUND; | |
| 219 | + | const fork: Repo = { | |
| 220 | + | id: newId("rep"), | |
| 221 | + | namespace: ATTEMPTS_NAMESPACE, | |
| 222 | + | name: attemptId, | |
| 223 | + | description: null, | |
| 224 | + | // A fork is exactly as visible as the repo it came from. | |
| 225 | + | isPrivate: source.isPrivate, | |
| 226 | + | ownerId: actor.id, | |
| 227 | + | defaultBranch: source.defaultBranch, | |
| 228 | + | forkOf: source.id, | |
| 229 | + | createdAt: Date.now(), | |
| 230 | + | }; | |
| 231 | + | await this.store.fork(storeKey(source), storeKey(fork)); | |
| 232 | + | await this.registry.insert(fork); | |
| 233 | + | await this.publish({ | |
| 234 | + | type: "repo.forked", | |
| 235 | + | source: SOURCE, | |
| 236 | + | repoId: source.id, | |
| 237 | + | actor: actor.id, | |
| 238 | + | data: { repoId: fork.id, sourceRepoId: source.id, attemptId }, | |
| 239 | + | }); | |
| 240 | + | return ok(fork); | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | async gitAccess( | |
| 244 | + | path: RepoPath, | |
| 245 | + | viewer: Viewer, | |
| 246 | + | service: GitService, | |
| 247 | + | ): Promise<Result<GitAccess>> { | |
| 248 | + | const write = service === "git-receive-pack"; | |
| 249 | + | let repo = await this.registry.byPath(path); | |
| 250 | + | if (!repo) { | |
| 251 | + | // Push to create, in the pusher's own namespace only. | |
| 252 | + | if (!write || !viewer || viewer.username !== path.namespace.toLowerCase()) { | |
| 253 | + | return this.denied(viewer); | |
| 254 | + | } | |
| 255 | + | const created = await this.create(viewer, { name: path.name }); | |
| 256 | + | if (!created.ok) return created; | |
| 257 | + | repo = created.value; | |
| 258 | + | } else if (write ? !canWrite(repo, viewer) : !canRead(repo, viewer)) { | |
| 259 | + | return this.denied(viewer); | |
| 260 | + | } | |
| 261 | + | return ok(await this.store.access(storeKey(repo), write ? "write" : "read")); | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | /** | |
| 265 | + | * Anonymous callers are asked to authenticate whether or not the repo | |
| 266 | + | * exists, so private repos cannot be told apart from missing ones. | |
| 267 | + | */ | |
| 268 | + | private denied(viewer: Viewer): Result<never> { | |
| 269 | + | return viewer | |
| 270 | + | ? NOT_FOUND | |
| 271 | + | : fail("unauthenticated", "Authentication required."); | |
| 272 | + | } | |
| 273 | + | ||
| 274 | + | private async publish(event: NewEvent): Promise<void> { | |
| 275 | + | await this.env.EVENTS.publish([event]); | |
| 276 | + | } | |
| 277 | + | ||
| 278 | + | /** Git over HTTPS. */ | |
| 279 | + | async fetch(request: Request): Promise<Response> { | |
| 280 | + | const response = await handleGitHttp(request, this.env.IDENTITY, this, (path) => | |
| 281 | + | this.ctx.waitUntil(this.pushed(path)), | |
| 282 | + | ); | |
| 283 | + | return response ?? new Response("Not found\n", { status: 404 }); | |
| 284 | + | } | |
| 285 | + | ||
| 286 | + | /** | |
| 287 | + | * Publishes `git.push` after a push has gone through the git front end. | |
| 288 | + | * Artifacts' own push subscriptions are per repository, which does not fit | |
| 289 | + | * a repo per attempt, so the front end reports pushes itself. | |
| 290 | + | */ | |
| 291 | + | private async pushed(path: RepoPath): Promise<void> { | |
| 292 | + | const repo = await this.registry.byPath(path); | |
| 293 | + | if (!repo) return; | |
| 294 | + | const [head] = await this.store.log(storeKey(repo), repo.defaultBranch, 1); | |
| 295 | + | if (!head) return; | |
| 296 | + | await this.publish({ | |
| 297 | + | type: "git.push", | |
| 298 | + | source: SOURCE, | |
| 299 | + | repoId: repo.id, | |
| 300 | + | actor: null, | |
| 301 | + | data: { | |
| 302 | + | repoId: repo.id, | |
| 303 | + | ref: `refs/heads/${repo.defaultBranch}`, | |
| 304 | + | after: head.hash, | |
| 305 | + | }, | |
| 306 | + | }); | |
| 307 | + | } | |
| 308 | + | } |
| 1 | + | import type { Repo, RepoPath, Viewer } from "@g1t/contracts"; | |
| 2 | + | ||
| 3 | + | type RepoRow = { | |
| 4 | + | id: string; | |
| 5 | + | namespace: string; | |
| 6 | + | name: string; | |
| 7 | + | description: string | null; | |
| 8 | + | is_private: number; | |
| 9 | + | owner_id: string; | |
| 10 | + | default_branch: string; | |
| 11 | + | fork_of: string | null; | |
| 12 | + | created_at: number; | |
| 13 | + | }; | |
| 14 | + | ||
| 15 | + | function toRepo(row: RepoRow): Repo { | |
| 16 | + | return { | |
| 17 | + | id: row.id, | |
| 18 | + | namespace: row.namespace, | |
| 19 | + | name: row.name, | |
| 20 | + | description: row.description, | |
| 21 | + | isPrivate: row.is_private === 1, | |
| 22 | + | ownerId: row.owner_id, | |
| 23 | + | defaultBranch: row.default_branch, | |
| 24 | + | forkOf: row.fork_of, | |
| 25 | + | createdAt: row.created_at * 1000, | |
| 26 | + | }; | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | /** The key a repo is stored under in the git store. */ | |
| 30 | + | export function storeKey(repo: RepoPath): string { | |
| 31 | + | return `${repo.namespace}--${repo.name}`; | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | export function canRead(repo: Repo, viewer: Viewer): boolean { | |
| 35 | + | return !repo.isPrivate || repo.ownerId === viewer?.id; | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | export function canWrite(repo: Repo, viewer: Viewer): boolean { | |
| 39 | + | return repo.ownerId === viewer?.id; | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | /** Repository metadata in D1. */ | |
| 43 | + | export class RepoRegistry { | |
| 44 | + | constructor(private readonly db: D1Database) {} | |
| 45 | + | ||
| 46 | + | async byPath(path: RepoPath): Promise<Repo | null> { | |
| 47 | + | const row = await this.db | |
| 48 | + | .prepare("SELECT * FROM repos WHERE namespace = ? AND name = ?") | |
| 49 | + | .bind(path.namespace.toLowerCase(), path.name.toLowerCase()) | |
| 50 | + | .first<RepoRow>(); | |
| 51 | + | return row ? toRepo(row) : null; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | async byId(id: string): Promise<Repo | null> { | |
| 55 | + | const row = await this.db | |
| 56 | + | .prepare("SELECT * FROM repos WHERE id = ?") | |
| 57 | + | .bind(id) | |
| 58 | + | .first<RepoRow>(); | |
| 59 | + | return row ? toRepo(row) : null; | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | /** Excludes attempt forks. */ | |
| 63 | + | async list( | |
| 64 | + | viewer: Viewer, | |
| 65 | + | options: { query?: string; namespace?: string }, | |
| 66 | + | ): Promise<Repo[]> { | |
| 67 | + | const where = ["fork_of IS NULL", "(is_private = 0 OR owner_id = ?)"]; | |
| 68 | + | const params: unknown[] = [viewer?.id ?? ""]; | |
| 69 | + | if (options.namespace) { | |
| 70 | + | where.push("namespace = ?"); | |
| 71 | + | params.push(options.namespace.toLowerCase()); | |
| 72 | + | } | |
| 73 | + | if (options.query?.trim()) { | |
| 74 | + | where.push("(name LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')"); | |
| 75 | + | const pattern = `%${options.query.trim().replace(/[\\%_]/g, "\\$&")}%`; | |
| 76 | + | params.push(pattern, pattern); | |
| 77 | + | } | |
| 78 | + | const { results } = await this.db | |
| 79 | + | .prepare( | |
| 80 | + | `SELECT * FROM repos WHERE ${where.join(" AND ")} | |
| 81 | + | ORDER BY created_at DESC, id DESC LIMIT 50`, | |
| 82 | + | ) | |
| 83 | + | .bind(...params) | |
| 84 | + | .all<RepoRow>(); | |
| 85 | + | return results.map(toRepo); | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | async insert(repo: Repo): Promise<void> { | |
| 89 | + | await this.db | |
| 90 | + | .prepare( | |
| 91 | + | `INSERT INTO repos | |
| 92 | + | (id, namespace, name, description, is_private, owner_id, default_branch, fork_of, created_at) | |
| 93 | + | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, | |
| 94 | + | ) | |
| 95 | + | .bind( | |
| 96 | + | repo.id, | |
| 97 | + | repo.namespace, | |
| 98 | + | repo.name, | |
| 99 | + | repo.description, | |
| 100 | + | Number(repo.isPrivate), | |
| 101 | + | repo.ownerId, | |
| 102 | + | repo.defaultBranch, | |
| 103 | + | repo.forkOf, | |
| 104 | + | Math.floor(repo.createdAt / 1000), | |
| 105 | + | ) | |
| 106 | + | .run(); | |
| 107 | + | } | |
| 108 | + | } |
| 1 | + | { | |
| 2 | + | "extends": "../../tsconfig.base.json", | |
| 3 | + | "include": ["src/**/*", "worker-configuration.d.ts"] | |
| 4 | + | } |
| 1 | + | { | |
| 2 | + | "$schema": "../../node_modules/wrangler/config-schema.json", | |
| 3 | + | "name": "g1t-repos", | |
| 4 | + | "account_id": "1e6f2cffa3f445920836e8ebe446bb58", | |
| 5 | + | "compatibility_date": "2026-09-26", | |
| 6 | + | "main": "./src/index.ts", | |
| 7 | + | "workers_dev": false, | |
| 8 | + | "d1_databases": [ | |
| 9 | + | { | |
| 10 | + | "binding": "DB", | |
| 11 | + | "database_name": "g1t-repos", | |
| 12 | + | "database_id": "f9544c51-c3bf-4621-a96f-8a6d5cf24a97", | |
| 13 | + | "migrations_dir": "migrations" | |
| 14 | + | } | |
| 15 | + | ], | |
| 16 | + | "artifacts": [{ "binding": "ARTIFACTS", "namespace": "g1t" }], | |
| 17 | + | "services": [ | |
| 18 | + | { "binding": "IDENTITY", "service": "g1t-identity" }, | |
| 19 | + | { "binding": "EVENTS", "service": "g1t-events" } | |
| 20 | + | ], | |
| 21 | + | "observability": { "enabled": true } | |
| 22 | + | } |
| 1 | + | CREATE TABLE intents ( | |
| 2 | + | id TEXT PRIMARY KEY, | |
| 3 | + | repo_id TEXT NOT NULL, | |
| 4 | + | number INTEGER NOT NULL, | |
| 5 | + | title TEXT NOT NULL, | |
| 6 | + | brief TEXT NOT NULL, | |
| 7 | + | -- JSON array of commands. | |
| 8 | + | checks TEXT NOT NULL DEFAULT '[]', | |
| 9 | + | status TEXT NOT NULL DEFAULT 'open', | |
| 10 | + | author_id TEXT NOT NULL, | |
| 11 | + | author_name TEXT NOT NULL, | |
| 12 | + | created_at INTEGER NOT NULL, | |
| 13 | + | UNIQUE (repo_id, number) | |
| 14 | + | ); | |
| 15 | + | ||
| 16 | + | CREATE TABLE attempts ( | |
| 17 | + | id TEXT PRIMARY KEY, | |
| 18 | + | intent_id TEXT NOT NULL REFERENCES intents (id), | |
| 19 | + | repo_id TEXT NOT NULL, | |
| 20 | + | number INTEGER NOT NULL, | |
| 21 | + | agent TEXT NOT NULL, | |
| 22 | + | runtime TEXT NOT NULL, | |
| 23 | + | status TEXT NOT NULL DEFAULT 'working', | |
| 24 | + | summary TEXT, | |
| 25 | + | fork_repo_id TEXT NOT NULL UNIQUE, | |
| 26 | + | fork_namespace TEXT NOT NULL, | |
| 27 | + | fork_name TEXT NOT NULL, | |
| 28 | + | head_commit TEXT, | |
| 29 | + | started_by_id TEXT NOT NULL, | |
| 30 | + | started_by_name TEXT NOT NULL, | |
| 31 | + | created_at INTEGER NOT NULL, | |
| 32 | + | updated_at INTEGER NOT NULL, | |
| 33 | + | UNIQUE (intent_id, number) | |
| 34 | + | ); | |
| 35 | + | CREATE INDEX attempts_starter ON attempts (started_by_id, status); | |
| 36 | + | ||
| 37 | + | CREATE TABLE session_entries ( | |
| 38 | + | attempt_id TEXT NOT NULL REFERENCES attempts (id), | |
| 39 | + | seq INTEGER NOT NULL, | |
| 40 | + | kind TEXT NOT NULL, | |
| 41 | + | text TEXT NOT NULL, | |
| 42 | + | tool TEXT, | |
| 43 | + | -- The fork's head when the entry was recorded: links reasoning to code. | |
| 44 | + | "commit" TEXT, | |
| 45 | + | at INTEGER NOT NULL, | |
| 46 | + | PRIMARY KEY (attempt_id, seq) | |
| 47 | + | ); |
| 1 | + | { | |
| 2 | + | "name": "@g1t/work", | |
| 3 | + | "version": "0.1.0", | |
| 4 | + | "private": true, | |
| 5 | + | "type": "module", | |
| 6 | + | "license": "MIT", | |
| 7 | + | "scripts": { | |
| 8 | + | "types": "wrangler types --include-env=false", | |
| 9 | + | "typecheck": "wrangler types --include-env=false && tsc -p tsconfig.json", | |
| 10 | + | "deploy": "wrangler deploy", | |
| 11 | + | "migrate": "wrangler d1 migrations apply DB --remote" | |
| 12 | + | }, | |
| 13 | + | "dependencies": { | |
| 14 | + | "@g1t/contracts": "*" | |
| 15 | + | } | |
| 16 | + | } |
| 1 | + | import { WorkerEntrypoint } from "cloudflare:workers"; | |
| 2 | + | ||
| 3 | + | import { | |
| 4 | + | type Attempt, | |
| 5 | + | type EventsApi, | |
| 6 | + | type G1tEvent, | |
| 7 | + | type Intent, | |
| 8 | + | type IntentDetail, | |
| 9 | + | type IntentStatus, | |
| 10 | + | type NewEvent, | |
| 11 | + | type NewSessionEntry, | |
| 12 | + | type OpenIntentInput, | |
| 13 | + | type RepoPath, | |
| 14 | + | type ReposApi, | |
| 15 | + | type Result, | |
| 16 | + | type SessionEntry, | |
| 17 | + | type StartAttemptInput, | |
| 18 | + | type User, | |
| 19 | + | type Viewer, | |
| 20 | + | type WorkApi, | |
| 21 | + | fail, | |
| 22 | + | newId, | |
| 23 | + | ok, | |
| 24 | + | } from "@g1t/contracts"; | |
| 25 | + | ||
| 26 | + | import { | |
| 27 | + | type AttemptRow, | |
| 28 | + | type IntentRow, | |
| 29 | + | type SessionRow, | |
| 30 | + | toAttempt, | |
| 31 | + | toIntent, | |
| 32 | + | toSessionEntry, | |
| 33 | + | } from "./rows"; | |
| 34 | + | ||
| 35 | + | export interface WorkEnv { | |
| 36 | + | DB: D1Database; | |
| 37 | + | REPOS: ReposApi; | |
| 38 | + | EVENTS: EventsApi; | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | const SOURCE = "work"; | |
| 42 | + | const MAX_ENTRY_BATCH = 200; | |
| 43 | + | const MAX_ENTRY_CHARS = 64_000; | |
| 44 | + | const SESSION_PAGE = 500; | |
| 45 | + | ||
| 46 | + | const INTENT_COLUMNS = `intents.*, | |
| 47 | + | (SELECT count(*) FROM attempts WHERE attempts.intent_id = intents.id) AS attempt_count`; | |
| 48 | + | ||
| 49 | + | const NO_INTENT = fail("not_found", "Intent not found."); | |
| 50 | + | const NO_ATTEMPT = fail("not_found", "Attempt not found."); | |
| 51 | + | const SIGN_IN = fail("unauthenticated", "Sign in to do that."); | |
| 52 | + | ||
| 53 | + | export default class WorkService | |
| 54 | + | extends WorkerEntrypoint<WorkEnv> | |
| 55 | + | implements WorkApi | |
| 56 | + | { | |
| 57 | + | private get db(): D1Database { | |
| 58 | + | return this.env.DB; | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | private async intentById(id: string): Promise<Intent | null> { | |
| 62 | + | const row = await this.db | |
| 63 | + | .prepare(`SELECT ${INTENT_COLUMNS} FROM intents WHERE id = ?`) | |
| 64 | + | .bind(id) | |
| 65 | + | .first<IntentRow>(); | |
| 66 | + | return row ? toIntent(row) : null; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | private async attemptById(id: string): Promise<Attempt | null> { | |
| 70 | + | const row = await this.db | |
| 71 | + | .prepare("SELECT * FROM attempts WHERE id = ?") | |
| 72 | + | .bind(id) | |
| 73 | + | .first<AttemptRow>(); | |
| 74 | + | return row ? toAttempt(row) : null; | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | /** The attempt, if `actor` is the one running it. */ | |
| 78 | + | private async ownAttempt(actor: User, id: string): Promise<Result<Attempt>> { | |
| 79 | + | const attempt = await this.attemptById(id); | |
| 80 | + | if (!attempt) return NO_ATTEMPT; | |
| 81 | + | // Reading it must be allowed before "forbidden" may reveal it exists. | |
| 82 | + | const repo = await this.env.REPOS.getById(attempt.repoId, actor); | |
| 83 | + | if (!repo.ok) return NO_ATTEMPT; | |
| 84 | + | if (attempt.startedBy.id !== actor.id) { | |
| 85 | + | return fail("forbidden", "Only the person who started an attempt can change it."); | |
| 86 | + | } | |
| 87 | + | return ok(attempt); | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | private publish(...events: NewEvent[]): Promise<void> { | |
| 91 | + | return this.env.EVENTS.publish(events); | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | async openIntent( | |
| 95 | + | actor: User, | |
| 96 | + | repoPath: RepoPath, | |
| 97 | + | input: OpenIntentInput, | |
| 98 | + | ): Promise<Result<Intent>> { | |
| 99 | + | const title = input.title.trim(); | |
| 100 | + | const brief = input.brief.trim(); | |
| 101 | + | if (!title) return fail("invalid", "An intent needs a title."); | |
| 102 | + | const repo = await this.env.REPOS.get(repoPath, actor); | |
| 103 | + | if (!repo.ok) return repo; | |
| 104 | + | const checks = (input.checks ?? []).map((check) => check.trim()).filter(Boolean); | |
| 105 | + | ||
| 106 | + | const id = newId("int"); | |
| 107 | + | // Numbering and insert are one statement, so concurrent opens on the | |
| 108 | + | // same repo cannot take the same number. | |
| 109 | + | await this.db | |
| 110 | + | .prepare( | |
| 111 | + | `INSERT INTO intents | |
| 112 | + | (id, repo_id, number, title, brief, checks, author_id, author_name, created_at) | |
| 113 | + | SELECT ?, ?, COALESCE(MAX(number), 0) + 1, ?, ?, ?, ?, ?, ? | |
| 114 | + | FROM intents WHERE repo_id = ?`, | |
| 115 | + | ) | |
| 116 | + | .bind( | |
| 117 | + | id, | |
| 118 | + | repo.value.id, | |
| 119 | + | title, | |
| 120 | + | brief, | |
| 121 | + | JSON.stringify(checks), | |
| 122 | + | actor.id, | |
| 123 | + | actor.username, | |
| 124 | + | Date.now(), | |
| 125 | + | repo.value.id, | |
| 126 | + | ) | |
| 127 | + | .run(); | |
| 128 | + | const intent = (await this.intentById(id))!; | |
| 129 | + | await this.publish({ | |
| 130 | + | type: "intent.opened", | |
| 131 | + | source: SOURCE, | |
| 132 | + | repoId: intent.repoId, | |
| 133 | + | actor: actor.id, | |
| 134 | + | data: { | |
| 135 | + | intentId: intent.id, | |
| 136 | + | repoId: intent.repoId, | |
| 137 | + | number: intent.number, | |
| 138 | + | title: intent.title, | |
| 139 | + | }, | |
| 140 | + | }); | |
| 141 | + | return ok(intent); | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | async listIntents( | |
| 145 | + | repoPath: RepoPath, | |
| 146 | + | viewer: Viewer, | |
| 147 | + | status?: IntentStatus, | |
| 148 | + | ): Promise<Result<Intent[]>> { | |
| 149 | + | const repo = await this.env.REPOS.get(repoPath, viewer); | |
| 150 | + | if (!repo.ok) return repo; | |
| 151 | + | const { results } = await this.db | |
| 152 | + | .prepare( | |
| 153 | + | `SELECT ${INTENT_COLUMNS} FROM intents | |
| 154 | + | WHERE repo_id = ? AND (? IS NULL OR status = ?) | |
| 155 | + | ORDER BY number DESC LIMIT 100`, | |
| 156 | + | ) | |
| 157 | + | .bind(repo.value.id, status ?? null, status ?? null) | |
| 158 | + | .all<IntentRow>(); | |
| 159 | + | return ok(results.map(toIntent)); | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | async getIntent( | |
| 163 | + | repoPath: RepoPath, | |
| 164 | + | number: number, | |
| 165 | + | viewer: Viewer, | |
| 166 | + | ): Promise<Result<IntentDetail>> { | |
| 167 | + | const repo = await this.env.REPOS.get(repoPath, viewer); | |
| 168 | + | if (!repo.ok) return repo; | |
| 169 | + | const row = await this.db | |
| 170 | + | .prepare( | |
| 171 | + | `SELECT ${INTENT_COLUMNS} FROM intents WHERE repo_id = ? AND number = ?`, | |
| 172 | + | ) | |
| 173 | + | .bind(repo.value.id, number) | |
| 174 | + | .first<IntentRow>(); | |
| 175 | + | if (!row) return NO_INTENT; | |
| 176 | + | const attempts = await this.db | |
| 177 | + | .prepare("SELECT * FROM attempts WHERE intent_id = ? ORDER BY number") | |
| 178 | + | .bind(row.id) | |
| 179 | + | .all<AttemptRow>(); | |
| 180 | + | return ok({ intent: toIntent(row), attempts: attempts.results.map(toAttempt) }); | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | async withdrawIntent(actor: User, intentId: string): Promise<Result<Intent>> { | |
| 184 | + | const intent = await this.intentById(intentId); | |
| 185 | + | if (!intent) return NO_INTENT; | |
| 186 | + | const repo = await this.env.REPOS.getById(intent.repoId, actor); | |
| 187 | + | if (!repo.ok) return NO_INTENT; | |
| 188 | + | if (intent.author.id !== actor.id && repo.value.ownerId !== actor.id) { | |
| 189 | + | return fail("forbidden", "Only the author or the repo owner can withdraw an intent."); | |
| 190 | + | } | |
| 191 | + | if (intent.status !== "open") { | |
| 192 | + | return fail("conflict", `This intent is already ${intent.status}.`); | |
| 193 | + | } | |
| 194 | + | await this.db | |
| 195 | + | .prepare("UPDATE intents SET status = 'withdrawn' WHERE id = ?") | |
| 196 | + | .bind(intent.id) | |
| 197 | + | .run(); | |
| 198 | + | await this.publish({ | |
| 199 | + | type: "intent.closed", | |
| 200 | + | source: SOURCE, | |
| 201 | + | repoId: intent.repoId, | |
| 202 | + | actor: actor.id, | |
| 203 | + | data: { intentId: intent.id, repoId: intent.repoId, reason: "withdrawn" }, | |
| 204 | + | }); | |
| 205 | + | return ok({ ...intent, status: "withdrawn" }); | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | async startAttempt( | |
| 209 | + | actor: User, | |
| 210 | + | intentId: string, | |
| 211 | + | input: StartAttemptInput, | |
| 212 | + | ): Promise<Result<Attempt>> { | |
| 213 | + | const intent = await this.intentById(intentId); | |
| 214 | + | if (!intent) return NO_INTENT; | |
| 215 | + | if (intent.status !== "open") { | |
| 216 | + | return fail("conflict", `This intent is already ${intent.status}.`); | |
| 217 | + | } | |
| 218 | + | const agent = input.agent.trim() || "agent"; | |
| 219 | + | ||
| 220 | + | const id = newId("att"); | |
| 221 | + | const fork = await this.env.REPOS.forkForAttempt(intent.repoId, id, actor); | |
| 222 | + | if (!fork.ok) return fork.error.code === "not_found" ? NO_INTENT : fork; | |
| 223 | + | ||
| 224 | + | const now = Date.now(); | |
| 225 | + | await this.db | |
| 226 | + | .prepare( | |
| 227 | + | `INSERT INTO attempts | |
| 228 | + | (id, intent_id, repo_id, number, agent, runtime, fork_repo_id, | |
| 229 | + | fork_namespace, fork_name, started_by_id, started_by_name, | |
| 230 | + | created_at, updated_at) | |
| 231 | + | SELECT ?, ?, ?, COALESCE(MAX(number), 0) + 1, ?, ?, ?, ?, ?, ?, ?, ?, ? | |
| 232 | + | FROM attempts WHERE intent_id = ?`, | |
| 233 | + | ) | |
| 234 | + | .bind( | |
| 235 | + | id, | |
| 236 | + | intent.id, | |
| 237 | + | intent.repoId, | |
| 238 | + | agent, | |
| 239 | + | input.runtime, | |
| 240 | + | fork.value.id, | |
| 241 | + | fork.value.namespace, | |
| 242 | + | fork.value.name, | |
| 243 | + | actor.id, | |
| 244 | + | actor.username, | |
| 245 | + | now, | |
| 246 | + | now, | |
| 247 | + | intent.id, | |
| 248 | + | ) | |
| 249 | + | .run(); | |
| 250 | + | const attempt = (await this.attemptById(id))!; | |
| 251 | + | await this.publish({ | |
| 252 | + | type: "attempt.started", | |
| 253 | + | source: SOURCE, | |
| 254 | + | repoId: intent.repoId, | |
| 255 | + | actor: actor.id, | |
| 256 | + | data: { | |
| 257 | + | attemptId: attempt.id, | |
| 258 | + | intentId: intent.id, | |
| 259 | + | repoId: intent.repoId, | |
| 260 | + | agent: attempt.agent, | |
| 261 | + | }, | |
| 262 | + | }); | |
| 263 | + | return ok(attempt); | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | async getAttempt( | |
| 267 | + | attemptId: string, | |
| 268 | + | viewer: Viewer, | |
| 269 | + | ): Promise<Result<{ attempt: Attempt; intent: Intent }>> { | |
| 270 | + | const attempt = await this.attemptById(attemptId); | |
| 271 | + | if (!attempt) return NO_ATTEMPT; | |
| 272 | + | const repo = await this.env.REPOS.getById(attempt.repoId, viewer); | |
| 273 | + | if (!repo.ok) return NO_ATTEMPT; | |
| 274 | + | return ok({ attempt, intent: (await this.intentById(attempt.intentId))! }); | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | private async setStatus( | |
| 278 | + | actor: User, | |
| 279 | + | attemptId: string, | |
| 280 | + | status: "submitted" | "abandoned", | |
| 281 | + | summary: string | null, | |
| 282 | + | ): Promise<Result<Attempt>> { | |
| 283 | + | const own = await this.ownAttempt(actor, attemptId); | |
| 284 | + | if (!own.ok) return own; | |
| 285 | + | const attempt = own.value; | |
| 286 | + | if (attempt.status !== "working" && attempt.status !== "submitted") { | |
| 287 | + | return fail("conflict", `This attempt is already ${attempt.status}.`); | |
| 288 | + | } | |
| 289 | + | const now = Date.now(); | |
| 290 | + | await this.db | |
| 291 | + | .prepare( | |
| 292 | + | "UPDATE attempts SET status = ?, summary = COALESCE(?, summary), updated_at = ? WHERE id = ?", | |
| 293 | + | ) | |
| 294 | + | .bind(status, summary, now, attempt.id) | |
| 295 | + | .run(); | |
| 296 | + | const ids = { | |
| 297 | + | attemptId: attempt.id, | |
| 298 | + | intentId: attempt.intentId, | |
| 299 | + | repoId: attempt.repoId, | |
| 300 | + | }; | |
| 301 | + | await this.publish( | |
| 302 | + | status === "submitted" | |
| 303 | + | ? { type: "attempt.submitted", source: SOURCE, repoId: attempt.repoId, actor: actor.id, data: ids } | |
| 304 | + | : { type: "attempt.updated", source: SOURCE, repoId: attempt.repoId, actor: actor.id, data: { ...ids, status } }, | |
| 305 | + | ); | |
| 306 | + | return ok({ | |
| 307 | + | ...attempt, | |
| 308 | + | status, | |
| 309 | + | summary: summary ?? attempt.summary, | |
| 310 | + | updatedAt: now, | |
| 311 | + | }); | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | submitAttempt(actor: User, attemptId: string, summary: string): Promise<Result<Attempt>> { | |
| 315 | + | return this.setStatus(actor, attemptId, "submitted", summary.trim() || null); | |
| 316 | + | } | |
| 317 | + | ||
| 318 | + | abandonAttempt(actor: User, attemptId: string): Promise<Result<Attempt>> { | |
| 319 | + | return this.setStatus(actor, attemptId, "abandoned", null); | |
| 320 | + | } | |
| 321 | + | ||
| 322 | + | async listActiveAttempts( | |
| 323 | + | viewer: Viewer, | |
| 324 | + | ): Promise<{ attempt: Attempt; intent: Intent }[]> { | |
| 325 | + | if (!viewer) return []; | |
| 326 | + | const { results } = await this.db | |
| 327 | + | .prepare( | |
| 328 | + | `SELECT * FROM attempts | |
| 329 | + | WHERE started_by_id = ? AND status IN ('working', 'submitted') | |
| 330 | + | ORDER BY updated_at DESC LIMIT 50`, | |
| 331 | + | ) | |
| 332 | + | .bind(viewer.id) | |
| 333 | + | .all<AttemptRow>(); | |
| 334 | + | return Promise.all( | |
| 335 | + | results.map(async (row) => ({ | |
| 336 | + | attempt: toAttempt(row), | |
| 337 | + | intent: (await this.intentById(row.intent_id))!, | |
| 338 | + | })), | |
| 339 | + | ); | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | async appendSession( | |
| 343 | + | actor: User, | |
| 344 | + | attemptId: string, | |
| 345 | + | entries: NewSessionEntry[], | |
| 346 | + | ): Promise<Result<{ count: number }>> { | |
| 347 | + | if (!actor) return SIGN_IN; | |
| 348 | + | if (entries.length === 0) return ok({ count: 0 }); | |
| 349 | + | if (entries.length > MAX_ENTRY_BATCH) { | |
| 350 | + | return fail("invalid", `Send at most ${MAX_ENTRY_BATCH} entries at a time.`); | |
| 351 | + | } | |
| 352 | + | const own = await this.ownAttempt(actor, attemptId); | |
| 353 | + | if (!own.ok) return own; | |
| 354 | + | const attempt = own.value; | |
| 355 | + | ||
| 356 | + | const now = Date.now(); | |
| 357 | + | // Each insert takes the next sequence number itself, so two writers | |
| 358 | + | // appending at once cannot collide. | |
| 359 | + | const insert = this.db.prepare( | |
| 360 | + | `INSERT INTO session_entries (attempt_id, seq, kind, text, tool, "commit", at) | |
| 361 | + | SELECT ?, COALESCE(MAX(seq), 0) + 1, ?, ?, ?, ?, ? | |
| 362 | + | FROM session_entries WHERE attempt_id = ?`, | |
| 363 | + | ); | |
| 364 | + | await this.db.batch([ | |
| 365 | + | ...entries.map((entry) => | |
| 366 | + | insert.bind( | |
| 367 | + | attempt.id, | |
| 368 | + | entry.kind, | |
| 369 | + | entry.text.slice(0, MAX_ENTRY_CHARS), | |
| 370 | + | entry.tool ?? null, | |
| 371 | + | entry.commit ?? attempt.headCommit, | |
| 372 | + | entry.at ?? now, | |
| 373 | + | attempt.id, | |
| 374 | + | ), | |
| 375 | + | ), | |
| 376 | + | this.db | |
| 377 | + | .prepare("UPDATE attempts SET updated_at = ? WHERE id = ?") | |
| 378 | + | .bind(now, attempt.id), | |
| 379 | + | ]); | |
| 380 | + | await this.publish({ | |
| 381 | + | type: "session.appended", | |
| 382 | + | source: SOURCE, | |
| 383 | + | repoId: attempt.repoId, | |
| 384 | + | actor: actor.id, | |
| 385 | + | data: { attemptId: attempt.id, sessionId: attempt.id, count: entries.length }, | |
| 386 | + | }); | |
| 387 | + | return ok({ count: entries.length }); | |
| 388 | + | } | |
| 389 | + | ||
| 390 | + | async readSession( | |
| 391 | + | attemptId: string, | |
| 392 | + | viewer: Viewer, | |
| 393 | + | afterSeq = 0, | |
| 394 | + | ): Promise<Result<SessionEntry[]>> { | |
| 395 | + | const found = await this.getAttempt(attemptId, viewer); | |
| 396 | + | if (!found.ok) return found; | |
| 397 | + | const { results } = await this.db | |
| 398 | + | .prepare( | |
| 399 | + | "SELECT * FROM session_entries WHERE attempt_id = ? AND seq > ? ORDER BY seq LIMIT ?", | |
| 400 | + | ) | |
| 401 | + | .bind(attemptId, afterSeq, SESSION_PAGE) | |
| 402 | + | .all<SessionRow>(); | |
| 403 | + | return ok(results.map(toSessionEntry)); | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | /** A push to an attempt's fork moves that attempt's head. */ | |
| 407 | + | async onEvents(events: G1tEvent[]): Promise<void> { | |
| 408 | + | for (const event of events) { | |
| 409 | + | if (event.type !== "git.push") continue; | |
| 410 | + | await this.db | |
| 411 | + | .prepare( | |
| 412 | + | "UPDATE attempts SET head_commit = ?, updated_at = ? WHERE fork_repo_id = ?", | |
| 413 | + | ) | |
| 414 | + | .bind(event.data.after, event.time, event.data.repoId) | |
| 415 | + | .run(); | |
| 416 | + | } | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | async queue(batch: MessageBatch<G1tEvent>): Promise<void> { | |
| 420 | + | await this.onEvents(batch.messages.map((message) => message.body)); | |
| 421 | + | } | |
| 422 | + | } |
| 1 | + | import type { | |
| 2 | + | Attempt, | |
| 3 | + | AttemptRuntime, | |
| 4 | + | AttemptStatus, | |
| 5 | + | Intent, | |
| 6 | + | IntentStatus, | |
| 7 | + | SessionEntry, | |
| 8 | + | SessionEntryKind, | |
| 9 | + | } from "@g1t/contracts"; | |
| 10 | + | ||
| 11 | + | export type IntentRow = { | |
| 12 | + | id: string; | |
| 13 | + | repo_id: string; | |
| 14 | + | number: number; | |
| 15 | + | title: string; | |
| 16 | + | brief: string; | |
| 17 | + | checks: string; | |
| 18 | + | status: IntentStatus; | |
| 19 | + | author_id: string; | |
| 20 | + | author_name: string; | |
| 21 | + | created_at: number; | |
| 22 | + | attempt_count: number; | |
| 23 | + | }; | |
| 24 | + | ||
| 25 | + | export type AttemptRow = { | |
| 26 | + | id: string; | |
| 27 | + | intent_id: string; | |
| 28 | + | repo_id: string; | |
| 29 | + | number: number; | |
| 30 | + | agent: string; | |
| 31 | + | runtime: AttemptRuntime; | |
| 32 | + | status: AttemptStatus; | |
| 33 | + | summary: string | null; | |
| 34 | + | fork_repo_id: string; | |
| 35 | + | fork_namespace: string; | |
| 36 | + | fork_name: string; | |
| 37 | + | head_commit: string | null; | |
| 38 | + | started_by_id: string; | |
| 39 | + | started_by_name: string; | |
| 40 | + | created_at: number; | |
| 41 | + | updated_at: number; | |
| 42 | + | }; | |
| 43 | + | ||
| 44 | + | export type SessionRow = { | |
| 45 | + | attempt_id: string; | |
| 46 | + | seq: number; | |
| 47 | + | kind: SessionEntryKind; | |
| 48 | + | text: string; | |
| 49 | + | tool: string | null; | |
| 50 | + | commit: string | null; | |
| 51 | + | at: number; | |
| 52 | + | }; | |
| 53 | + | ||
| 54 | + | export function toIntent(row: IntentRow): Intent { | |
| 55 | + | return { | |
| 56 | + | id: row.id, | |
| 57 | + | repoId: row.repo_id, | |
| 58 | + | number: row.number, | |
| 59 | + | title: row.title, | |
| 60 | + | brief: row.brief, | |
| 61 | + | checks: JSON.parse(row.checks), | |
| 62 | + | status: row.status, | |
| 63 | + | author: { id: row.author_id, username: row.author_name }, | |
| 64 | + | createdAt: row.created_at, | |
| 65 | + | attemptCount: row.attempt_count, | |
| 66 | + | }; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | export function toAttempt(row: AttemptRow): Attempt { | |
| 70 | + | return { | |
| 71 | + | id: row.id, | |
| 72 | + | intentId: row.intent_id, | |
| 73 | + | repoId: row.repo_id, | |
| 74 | + | number: row.number, | |
| 75 | + | agent: row.agent, | |
| 76 | + | runtime: row.runtime, | |
| 77 | + | status: row.status, | |
| 78 | + | summary: row.summary, | |
| 79 | + | fork: { namespace: row.fork_namespace, name: row.fork_name }, | |
| 80 | + | headCommit: row.head_commit, | |
| 81 | + | startedBy: { id: row.started_by_id, username: row.started_by_name }, | |
| 82 | + | createdAt: row.created_at, | |
| 83 | + | updatedAt: row.updated_at, | |
| 84 | + | }; | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | export function toSessionEntry(row: SessionRow): SessionEntry { | |
| 88 | + | return { | |
| 89 | + | seq: row.seq, | |
| 90 | + | kind: row.kind, | |
| 91 | + | text: row.text, | |
| 92 | + | tool: row.tool, | |
| 93 | + | commit: row.commit, | |
| 94 | + | at: row.at, | |
| 95 | + | }; | |
| 96 | + | } |
| 1 | + | { | |
| 2 | + | "extends": "../../tsconfig.base.json", | |
| 3 | + | "include": ["src/**/*", "worker-configuration.d.ts"] | |
| 4 | + | } |
| 1 | + | { | |
| 2 | + | "$schema": "../../node_modules/wrangler/config-schema.json", | |
| 3 | + | "name": "g1t-work", | |
| 4 | + | "account_id": "1e6f2cffa3f445920836e8ebe446bb58", | |
| 5 | + | "compatibility_date": "2026-09-26", | |
| 6 | + | "main": "./src/index.ts", | |
| 7 | + | "workers_dev": false, | |
| 8 | + | "d1_databases": [ | |
| 9 | + | { | |
| 10 | + | "binding": "DB", | |
| 11 | + | "database_name": "g1t-work", | |
| 12 | + | "database_id": "f35a9022-e36b-4547-b596-9ab9d5f1c47a", | |
| 13 | + | "migrations_dir": "migrations" | |
| 14 | + | } | |
| 15 | + | ], | |
| 16 | + | "services": [ | |
| 17 | + | { "binding": "REPOS", "service": "g1t-repos" }, | |
| 18 | + | { "binding": "EVENTS", "service": "g1t-events" } | |
| 19 | + | ], | |
| 20 | + | "queues": { | |
| 21 | + | "consumers": [{ "queue": "g1t-events-work", "max_batch_size": 100, "max_batch_timeout": 1 }] | |
| 22 | + | }, | |
| 23 | + | "observability": { "enabled": true } | |
| 24 | + | } |
| 1 | + | { | |
| 2 | + | "compilerOptions": { | |
| 3 | + | "target": "ES2022", | |
| 4 | + | "module": "ES2022", | |
| 5 | + | "moduleResolution": "bundler", | |
| 6 | + | "lib": ["ES2022", "ESNext.Disposable"], | |
| 7 | + | "strict": true, | |
| 8 | + | "noEmit": true, | |
| 9 | + | "skipLibCheck": true, | |
| 10 | + | "verbatimModuleSyntax": true, | |
| 11 | + | "resolveJsonModule": true | |
| 12 | + | } | |
| 13 | + | } |