diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..75fe446f7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "hyperdrive/dependencies/anthropic-api-key-manager"] + path = hyperdrive/dependencies/anthropic-api-key-manager + url = https://github.com/hyperware-ai/anthropic-api-key-manager diff --git a/Cargo.lock b/Cargo.lock index 12af32c87..2988e3bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -997,6 +997,17 @@ dependencies = [ "serde", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -1174,6 +1185,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "binstring" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef" + [[package]] name = "bit-set" version = "0.8.0" @@ -1433,6 +1450,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + [[package]] name = "cat" version = "0.1.0" @@ -1525,6 +1548,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -1591,6 +1615,17 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "clear-state" +version = "0.1.0" +dependencies = [ + "anyhow", + "hyperware_process_lib 2.1.0", + "serde", + "serde_json", + "wit-bindgen 0.42.1", +] + [[package]] name = "cmake" version = "0.1.54" @@ -1600,6 +1635,17 @@ dependencies = [ "cc", ] +[[package]] +name = "coarsetime" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "cobs" version = "0.2.3" @@ -1662,6 +1708,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-hex" version = "1.14.1" @@ -1675,6 +1730,12 @@ dependencies = [ "serde", ] +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + [[package]] name = "const-oid" version = "0.9.6" @@ -2007,6 +2068,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" + [[package]] name = "ctr" version = "0.9.2" @@ -2016,6 +2083,37 @@ dependencies = [ "cipher", ] +[[package]] +name = "curl" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.6.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "curl-sys" +version = "0.4.83+curl-8.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys 0.59.0", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -2125,16 +2223,50 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid 0.6.2", + "der_derive", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468 0.6.0", + "zeroize", +] + [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", + "pem-rfc7468 0.7.0", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aed3b3c608dc56cf36c45fe979d04eda51242e6703d8d0bb03426ef7c41db6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + [[package]] name = "deranged" version = "0.4.0" @@ -2249,7 +2381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", + "const-oid 0.9.6", "crypto-common", "subtle", ] @@ -2361,12 +2493,30 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.10", "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.3", +] + +[[package]] +name = "ece" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ea1d2f2cc974957a4e2575d8e5bb494549bab66338d6320c2789abcfff5746" +dependencies = [ + "base64 0.21.7", + "byteorder", + "hex", + "hkdf", + "lazy_static", + "once_cell", + "openssl", + "serde", + "sha2", + "thiserror 1.0.69", ] [[package]] @@ -2377,6 +2527,16 @@ dependencies = [ "wit-bindgen 0.42.1", ] +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom 0.2.16", +] + [[package]] name = "either" version = "1.15.0" @@ -2395,7 +2555,9 @@ dependencies = [ "ff", "generic-array", "group", - "pkcs8", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", "rand_core 0.6.4", "sec1", "subtle", @@ -2479,13 +2641,20 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "explorer" version = "0.1.0" dependencies = [ "anyhow", - "hyperprocess_macro", - "hyperware_app_common", + "file_explorer_caller_utils", + "hyperprocess_macro 0.1.0 (git+https://github.com/hyperware-ai/hyperprocess-macro?rev=66884c0)", + "hyperware_process_lib 2.2.0 (git+https://github.com/hyperware-ai/process_lib?rev=4beff93)", "md5", "process_macros", "serde", @@ -2517,6 +2686,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2572,6 +2750,22 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "file_explorer_caller_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "futures-util", + "hyperware_process_lib 2.2.0 (git+https://github.com/hyperware-ai/process_lib?rev=4beff93)", + "once_cell", + "process_macros", + "serde", + "serde_json", + "uuid 1.17.0", + "wit-bindgen 0.41.0", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2721,6 +2915,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -3067,6 +3289,15 @@ dependencies = [ "rusb", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -3076,6 +3307,30 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" + +[[package]] +name = "hmac-sha256" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hns-indexer" version = "0.2.0" @@ -3291,7 +3546,7 @@ dependencies = [ [[package]] name = "hyperdrive" -version = "1.6.0" +version = "1.7.0" dependencies = [ "aes-gcm", "alloy", @@ -3318,6 +3573,7 @@ dependencies = [ "libc", "nohash-hasher", "open", + "p256", "public-ip", "rand 0.8.5", "regex", @@ -3341,12 +3597,13 @@ dependencies = [ "warp", "wasmtime", "wasmtime-wasi", + "web-push", "zip 1.1.4", ] [[package]] name = "hyperdrive_lib" -version = "1.6.0" +version = "1.7.0" dependencies = [ "lib", ] @@ -3373,50 +3630,42 @@ dependencies = [ [[package]] name = "hyperprocess_macro" version = "0.1.0" -source = "git+https://github.com/hyperware-ai/hyperprocess-macro?rev=9836e2a#9836e2abe1da7bd2ccfef837244810438af26a07" +source = "git+https://github.com/hyperware-ai/hyperprocess-macro?rev=66884c0#66884c0a22b845d1db632f0fb8985a7e5bdad3fb" dependencies = [ - "anyhow", - "futures-util", - "hyperware_app_common", - "hyperware_process_lib 2.0.1", - "once_cell", - "paste", + "hyperware_process_lib 2.2.0 (git+https://github.com/hyperware-ai/process_lib?rev=b9f1ead)", "proc-macro2", - "process_macros", "quote", - "rmp-serde", - "serde", - "serde_derive", - "serde_json", "syn 2.0.104", - "uuid 1.17.0", - "wit-bindgen 0.36.0", ] [[package]] -name = "hyperware_app_common" +name = "hyperprocess_macro" version = "0.1.0" -source = "git+https://github.com/hyperware-ai/hyperprocess-macro?rev=9836e2a#9836e2abe1da7bd2ccfef837244810438af26a07" +source = "git+https://github.com/hyperware-ai/hyperprocess-macro?rev=ed99c19#ed99c19c1e4f511786b2c827fb909ba3d3950238" dependencies = [ - "anyhow", - "futures-util", "hyperware_process_lib 2.0.1", - "once_cell", - "paste", - "process_macros", - "rmp-serde", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "hyperware-anthropic-sdk" +version = "0.1.0" +source = "git+https://github.com/hyperware-ai/hyperware-anthropic-sdk?rev=363630c#363630c261c4cc8673b92a1f2e835bab8263c866" +dependencies = [ + "hyperware_process_lib 2.2.0 (git+https://github.com/hyperware-ai/process_lib?rev=753dac3)", + "rand 0.8.5", "serde", - "serde_derive", "serde_json", - "thiserror 2.0.12", - "uuid 1.17.0", - "wit-bindgen 0.36.0", + "thiserror 1.0.69", + "url", ] [[package]] name = "hyperware_process_lib" version = "2.0.1" -source = "git+https://github.com/hyperware-ai/process_lib?rev=cfd6843#cfd6843139bc40ffeefb1304372878c07d3132b7" +source = "git+https://github.com/hyperware-ai/process_lib?rev=c27a881#c27a881e764920636ac025112533a714d622793c" dependencies = [ "alloy", "alloy-primitives", @@ -3426,6 +3675,7 @@ dependencies = [ "base64 0.22.1", "bincode", "color-eyre", + "futures-util", "http 1.3.1", "mime_guess", "rand 0.8.5", @@ -3438,6 +3688,7 @@ dependencies = [ "tracing-error", "tracing-subscriber", "url", + "uuid 1.17.0", "wit-bindgen 0.42.1", ] @@ -3472,6 +3723,96 @@ dependencies = [ "wit-bindgen 0.42.1", ] +[[package]] +name = "hyperware_process_lib" +version = "2.2.0" +source = "git+https://github.com/hyperware-ai/process_lib?rev=4beff93#4beff9389598978d2f97e4cd6d1d0f74a7acac0f" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "anyhow", + "base64 0.22.1", + "bincode", + "color-eyre", + "futures-util", + "http 1.3.1", + "mime_guess", + "rand 0.8.5", + "regex", + "rmp-serde", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "tracing-error", + "tracing-subscriber", + "url", + "uuid 1.17.0", + "wit-bindgen 0.42.1", +] + +[[package]] +name = "hyperware_process_lib" +version = "2.2.0" +source = "git+https://github.com/hyperware-ai/process_lib?rev=753dac3#753dac3d58cbf924264b47d7c0b845e2fcc03e32" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "anyhow", + "base64 0.22.1", + "bincode", + "color-eyre", + "futures-util", + "http 1.3.1", + "mime_guess", + "rand 0.8.5", + "regex", + "rmp-serde", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "tracing-error", + "tracing-subscriber", + "url", + "uuid 1.17.0", + "wit-bindgen 0.42.1", +] + +[[package]] +name = "hyperware_process_lib" +version = "2.2.0" +source = "git+https://github.com/hyperware-ai/process_lib?rev=b9f1ead#b9f1ead63356bfd4b60b337a380fef1be81d81c6" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "anyhow", + "base64 0.22.1", + "bincode", + "color-eyre", + "futures-util", + "http 1.3.1", + "mime_guess", + "rand 0.8.5", + "regex", + "rmp-serde", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "tracing-error", + "tracing-subscriber", + "url", + "uuid 1.17.0", + "wit-bindgen 0.42.1", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -3707,6 +4048,15 @@ dependencies = [ "wit-bindgen 0.42.1", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-extras" version = "0.18.4" @@ -3764,6 +4114,33 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite 1.13.0", + "http 0.2.12", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "itertools" version = "0.10.5" @@ -3861,6 +4238,32 @@ dependencies = [ "sha2", ] +[[package]] +name = "jwt-simple" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357892bb32159d763abdea50733fadcb9a8e1c319a9aa77592db8555d05af83e" +dependencies = [ + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde_json", + "spki 0.6.0", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "k256" version = "0.13.4" @@ -3872,6 +4275,7 @@ dependencies = [ "elliptic-curve", "once_cell", "sha2", + "signature 2.2.0", ] [[package]] @@ -3906,8 +4310,8 @@ dependencies = [ [[package]] name = "kit" -version = "3.0.1" -source = "git+https://github.com/hyperware-ai/kit?rev=79fe678#79fe678bd287bbd949e9469f4a9a5a28339ab10e" +version = "3.1.1" +source = "git+https://github.com/hyperware-ai/kit?rev=aac33b6#aac33b6f25434e12bce8c28072d216a16c7abe52" dependencies = [ "alloy", "alloy-sol-macro", @@ -3932,6 +4336,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "sha3", "syn 2.0.104", "thiserror 1.0.69", "tokio", @@ -3951,6 +4356,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -3972,7 +4380,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lib" -version = "1.6.0" +version = "1.7.0" dependencies = [ "alloy", "anyhow", @@ -4028,6 +4436,16 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libnghttp2-sys" +version = "0.1.11+1.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6c24e48a7167cffa7119da39d577fa482e66c688a4aac016bee862e1a713c4" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libredox" version = "0.1.3" @@ -4439,6 +4857,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -4454,6 +4889,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -4612,6 +5058,30 @@ version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -4640,6 +5110,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -4739,6 +5215,45 @@ dependencies = [ "wit-bindgen 0.42.1", ] +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4798,14 +5313,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", ] [[package]] @@ -4814,6 +5351,22 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -4883,6 +5436,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -5403,6 +5965,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.3" @@ -5675,13 +6258,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.10", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] +[[package]] +name = "sec1_decode" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6326ddc956378a0739200b2c30892dccaf198992dfd7323274690b9e188af23" +dependencies = [ + "der 0.4.5", + "pem 0.8.3", + "thiserror 1.0.69", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5916,6 +6510,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "signature" version = "2.2.0" @@ -5938,6 +6542,17 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -5983,21 +6598,79 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spdx" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" +checksum = "c3e17e880bafaeb362a7b751ec46bdc5b61445a188f80e0606e68167cd540fa3" dependencies = [ "smallvec", ] +[[package]] +name = "spider" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.21.7", + "chrono", + "http 1.3.1", + "hyperprocess_macro 0.1.0 (git+https://github.com/hyperware-ai/hyperprocess-macro?rev=ed99c19)", + "hyperware-anthropic-sdk", + "hyperware_process_lib 2.2.0 (git+https://github.com/hyperware-ai/process_lib?rev=753dac3)", + "process_macros", + "rmp-serde", + "serde", + "serde_json", + "sha2", + "spider_caller_utils", + "url", + "uuid 1.17.0", + "wit-bindgen 0.42.1", +] + +[[package]] +name = "spider_caller_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "futures-util", + "hyperware_process_lib 2.2.0 (git+https://github.com/hyperware-ai/process_lib?rev=753dac3)", + "once_cell", + "process_macros", + "serde", + "serde_json", + "uuid 1.17.0", + "wit-bindgen 0.41.0", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.3" @@ -6005,7 +6678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", ] [[package]] @@ -6159,6 +6832,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -6225,7 +6910,7 @@ version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "fastrand", + "fastrand 2.3.0", "getrandom 0.3.3", "once_cell", "rustix 1.0.7", @@ -6992,6 +7677,7 @@ checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", "js-sys", + "serde", "wasm-bindgen", ] @@ -7022,6 +7708,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -7085,6 +7777,15 @@ dependencies = [ "wit-bindgen-rt 0.39.0", ] +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi 0.11.1+wasi-snapshot-preview1", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -7156,16 +7857,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.220.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e913f9242315ca39eff82aee0e19ee7a372155717ff0eb082c741e435ce25ed1" -dependencies = [ - "leb128", - "wasmparser 0.220.1", -] - [[package]] name = "wasm-encoder" version = "0.227.1" @@ -7206,22 +7897,6 @@ dependencies = [ "wasmparser 0.235.0", ] -[[package]] -name = "wasm-metadata" -version = "0.220.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185dfcd27fa5db2e6a23906b54c28199935f71d9a27a1a27b3a88d6fee2afae7" -dependencies = [ - "anyhow", - "indexmap", - "serde", - "serde_derive", - "serde_json", - "spdx", - "wasm-encoder 0.220.1", - "wasmparser 0.220.1", -] - [[package]] name = "wasm-metadata" version = "0.227.1" @@ -7253,19 +7928,6 @@ dependencies = [ "wasmparser 0.230.0", ] -[[package]] -name = "wasmparser" -version = "0.220.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d07b6a3b550fefa1a914b6d54fc175dd11c3392da11eee604e6ffc759805d25" -dependencies = [ - "ahash", - "bitflags 2.9.1", - "hashbrown 0.14.5", - "indexmap", - "semver 1.0.26", -] - [[package]] name = "wasmparser" version = "0.227.1" @@ -7668,6 +8330,28 @@ dependencies = [ "wast 235.0.0", ] +[[package]] +name = "web-push" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2332e5400bb42c21bcab3ca2cd3400ab4b1d5ecbe276b533ce9acb59c56602" +dependencies = [ + "async-trait", + "base64 0.13.1", + "chrono", + "ece", + "futures-lite 2.6.1", + "http 0.2.12", + "isahc", + "jwt-simple", + "log", + "pem 3.0.5", + "sec1_decode", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -8097,16 +8781,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wit-bindgen" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a2b3e15cd6068f233926e7d8c7c588b2ec4fb7cc7bf3824115e7c7e2a8485a3" -dependencies = [ - "wit-bindgen-rt 0.36.0", - "wit-bindgen-rust-macro 0.36.0", -] - [[package]] name = "wit-bindgen" version = "0.41.0" @@ -8127,17 +8801,6 @@ dependencies = [ "wit-bindgen-rust-macro 0.42.1", ] -[[package]] -name = "wit-bindgen-core" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b632a5a0fa2409489bd49c9e6d99fcc61bb3d4ce9d1907d44662e75a28c71172" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser 0.220.1", -] - [[package]] name = "wit-bindgen-core" version = "0.41.0" @@ -8160,15 +8823,6 @@ dependencies = [ "wit-parser 0.230.0", ] -[[package]] -name = "wit-bindgen-rt" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7947d0131c7c9da3f01dfde0ab8bd4c4cf3c5bd49b6dba0ae640f1fa752572ea" -dependencies = [ - "bitflags 2.9.1", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -8200,22 +8854,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "wit-bindgen-rust" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4329de4186ee30e2ef30a0533f9b3c123c019a237a7c82d692807bf1b3ee2697" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn 2.0.104", - "wasm-metadata 0.220.1", - "wit-bindgen-core 0.36.0", - "wit-component 0.220.1", -] - [[package]] name = "wit-bindgen-rust" version = "0.41.0" @@ -8248,21 +8886,6 @@ dependencies = [ "wit-component 0.230.0", ] -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177fb7ee1484d113b4792cc480b1ba57664bbc951b42a4beebe573502135b1fc" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.104", - "wit-bindgen-core 0.36.0", - "wit-bindgen-rust 0.36.0", -] - [[package]] name = "wit-bindgen-rust-macro" version = "0.41.0" @@ -8293,25 +8916,6 @@ dependencies = [ "wit-bindgen-rust 0.42.1", ] -[[package]] -name = "wit-component" -version = "0.220.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b505603761ed400c90ed30261f44a768317348e49f1864e82ecdc3b2744e5627" -dependencies = [ - "anyhow", - "bitflags 2.9.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.220.1", - "wasm-metadata 0.220.1", - "wasmparser 0.220.1", - "wit-parser 0.220.1", -] - [[package]] name = "wit-component" version = "0.227.1" @@ -8350,24 +8954,6 @@ dependencies = [ "wit-parser 0.230.0", ] -[[package]] -name = "wit-parser" -version = "0.220.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2a7999ed18efe59be8de2db9cb2b7f84d88b27818c79353dfc53131840fe1a" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver 1.0.26", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.220.1", -] - [[package]] name = "wit-parser" version = "0.227.1" @@ -8489,7 +9075,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -8530,7 +9116,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "synstructure", + "synstructure 0.13.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6b9c46f48..b19740ec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hyperdrive_lib" authors = ["Sybil Technologies AG"] -version = "1.6.0" +version = "1.7.0" edition = "2021" description = "A general-purpose sovereign cloud computing platform" homepage = "https://hyperware.ai" @@ -25,9 +25,10 @@ members = [ "hyperdrive/packages/hypermap-cacher/hypermap-cacher", "hyperdrive/packages/hypermap-cacher/reset-cache", "hyperdrive/packages/hypermap-cacher/set-nodes", "hyperdrive/packages/hypermap-cacher/start-providing", "hyperdrive/packages/hypermap-cacher/stop-providing", "hyperdrive/packages/sign/sign", + "hyperdrive/packages/spider/spider", "hyperdrive/packages/terminal/terminal", "hyperdrive/packages/terminal/add-node-provider", "hyperdrive/packages/terminal/add-rpcurl-provider", - "hyperdrive/packages/terminal/alias", "hyperdrive/packages/terminal/cat", "hyperdrive/packages/terminal/echo", "hyperdrive/packages/terminal/get-providers", - "hyperdrive/packages/terminal/help", "hyperdrive/packages/terminal/hfetch", "hyperdrive/packages/terminal/hi", + "hyperdrive/packages/terminal/alias", "hyperdrive/packages/terminal/cat", "hyperdrive/packages/terminal/clear-state", "hyperdrive/packages/terminal/echo", + "hyperdrive/packages/terminal/get-providers", "hyperdrive/packages/terminal/help", "hyperdrive/packages/terminal/hfetch", "hyperdrive/packages/terminal/hi", "hyperdrive/packages/terminal/kill", "hyperdrive/packages/terminal/m", "hyperdrive/packages/terminal/top", "hyperdrive/packages/terminal/net-diagnostics", "hyperdrive/packages/terminal/peer", "hyperdrive/packages/terminal/peers", "hyperdrive/packages/terminal/remove-provider", "hyperdrive/packages/tester/tester", diff --git a/README.md b/README.md index a606e377c..4d08bce27 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Rust must be between versions 1.81 and 1.85.1. ```bash # Clone the repo. -git clone git@github.com:hyperware-ai/hyperware.git +git clone --recurse-submodules git@github.com:hyperware-ai/hyperware.git # Install Rust and some `cargo` tools so we can build the runtime and Wasm. @@ -251,34 +251,40 @@ Subsequent use of the shorthand will then be interpolated as the process ID. A list of the terminal scripts included in this distro: -- `alias `: create an alias for a script. - - Example: `alias get_block get-block:hns-indexer:sys` +- `add-node-provider [--trusted ]`: add a node provider to the providers configuration. + - Examples: + - `add-node-provider 8453 other-node.hypr abc123pubkey 192.168.1.1 9000` (defaults to trusted=false) + - `add-node-provider 1 other-node.hypr abc123pubkey 192.168.1.1 9000 --trusted true` +- `add-rpcurl-provider [--chain-id ] [--trusted ] [--auth-type --auth-value ]`: add an RPC URL provider to the providers configuration. + - Examples: + - `add-rpcurl-provider wss://base-mainnet.infura.io/v3/your-key` (defaults to chain-id=8453, trusted=true) + - `add-rpcurl-provider wss://mainnet.infura.io/v3/your-key --chain-id 1` + - `add-rpcurl-provider wss://base-mainnet.infura.io/ws/v3/your-key --trusted false` + - `add-rpcurl-provider wss://rpc.example.com --auth-type bearer --auth-value your-token` +- `alias `: create an alias for a script. + - Example: `alias get-block get-block:hns-indexer:sys` - note: all of these listed commands are just default aliases for terminal scripts. - `cat `: print the contents of a file in the terminal. - Example: `cat /terminal:sys/pkg/scripts.json` -- `echo `: print text to the terminal. +- `clear-state `: clear the state of the given process. +- `echo : print text to the terminal. - Example: `echo foo` -- `help `: print the help message for a command. - Leave the command blank to print the help message for all commands. +- `get-providers`: display the providers configuration. - `hi `: send a text message to another node's command line. - Example: `hi mothu.hypr hello world` -- `hfetch`: print system information a la neofetch. - No arguments. -- `kill `: terminate a running process. - This will bypass any restart behavior–use judiciously. - - Example: `kill chess:chess:template.os` -- `m
''`: send an inter-process message. -
is formatted as @. - is formatted as ::. - JSON containing spaces must be wrapped in single-quotes (`''`). +- `kfetch`: print system information a la neofetch. No arguments. +- `kill `: terminate a running process. This will bypass any restart behavior; use judiciously. + - Example: `kill chess:chess:sys` +- `m
''`: send an inter-process message. `
` is formatted as `@`. `` is formatted as `::`. JSON containing spaces must be wrapped in single-quotes (''). - Example: `m our@eth:distro:sys "SetPublic" -a 5` - - the '-a' flag is used to expect a response with a given timeout + - the `-a` flag is used to expect a response with a given timeout - `our` will always be interpolated by the system as your node's name - `net-diagnostics`: print some useful networking diagnostic data. - `peer `: print the peer's PKI info, if it exists. - `peers`: print the peers the node currently hold connections with. -- `top `: display kernel debugging info about a process. - Leave the process ID blank to display info about all processes and get the total number of running processes. +- `remove-provider `: remove a provider from the providers configuration. + - Example: `remove-provider 8453 wss://base-mainnet.infura.io/ws/v3/your-key` +- `top `: display kernel debugging info about a process. Leave the process ID blank to display info about all processes and get the total number of running processes. - Example: `top net:distro:sys` - Example: `top` diff --git a/css/hyperware.css b/css/hyperware.css index 37855305e..6548890f1 100644 --- a/css/hyperware.css +++ b/css/hyperware.css @@ -2654,6 +2654,10 @@ left: calc(var(--spacing)*2) } + .right-2 { + right: calc(var(--spacing)*2) + } + .z-0 { z-index: 0 } @@ -2666,6 +2670,86 @@ z-index: 20 } + .bg-white { + background-color: var(--color-white); + } + + @media (prefers-color-scheme: dark) { + .dark\:bg-black { + background-color: var(--color-black); + } + + .dark\:shadow-white\/10 { + box-shadow: 0 10px 15px -3px rgba(255, 255, 255, 0.1), 0 4px 6px -2px rgba(255, 255, 255, 0.05); + } + } + + .shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .p-4 { + padding: calc(var(--spacing)*4); + } + + .rounded-lg { + border-radius: var(--radius-lg); + } + + .relative { + position: relative; + } + + .w-full { + width: 100%; + } + + .max-w-md { + max-width: var(--container-md); + } + + .min-h-0 { + min-height: 0; + } + + .max-h-screen { + max-height: 100vh; + } + + .overflow-y-auto { + overflow-y: auto; + } + + .backdrop-blur-sm { + backdrop-filter: blur(4px); + } + + .items-center { + align-items: center; + } + + .justify-center { + justify-content: center; + } + + .z-50 { + z-index: 50; + } + + .hidden { + display: none; + } + + .fixed { + position: fixed; + } + + .inset-0 { + inset: 0; + } + + + .container { width: 100% } @@ -2946,6 +3030,11 @@ line-height: var(--tw-leading, var(--text-5xl--line-height)) } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)) + } + .text-sm { font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)) diff --git a/hyperdrive/Cargo.toml b/hyperdrive/Cargo.toml index d99c31601..44321e69d 100644 --- a/hyperdrive/Cargo.toml +++ b/hyperdrive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hyperdrive" authors = ["Sybil Technologies AG"] -version = "1.6.0" +version = "1.7.0" edition = "2021" description = "A general-purpose sovereign cloud computing platform" homepage = "https://hyperware.ai" @@ -60,6 +60,7 @@ lazy_static = "1.4.0" libc = "0.2" nohash-hasher = "0.2.0" open = "5.1.4" +p256 = { version = "0.13", features = ["ecdsa"] } public-ip = "0.2.2" rand = "0.8.4" regex = "1.11.0" @@ -85,4 +86,5 @@ url = "2.4.1" warp = "0.3.5" wasmtime = "33.0.0" wasmtime-wasi = "33.0.0" +web-push = "0.10" zip = "1.1.1" diff --git a/hyperdrive/dependencies/anthropic-api-key-manager b/hyperdrive/dependencies/anthropic-api-key-manager new file mode 160000 index 000000000..ccfed5955 --- /dev/null +++ b/hyperdrive/dependencies/anthropic-api-key-manager @@ -0,0 +1 @@ +Subproject commit ccfed5955f2a42658aafc09a73f296fe28f3eb6b diff --git a/hyperdrive/packages-build-parameters.json b/hyperdrive/packages-build-parameters.json index ca605d406..f19ec3ddf 100644 --- a/hyperdrive/packages-build-parameters.json +++ b/hyperdrive/packages-build-parameters.json @@ -5,6 +5,18 @@ ] }, "file-explorer": { - "is_hyperapp": true + "is_hyperapp": true, + "features": [ + "caller-utils" + ] + }, + "spider": { + "is_hyperapp": true, + "local_dependencies": [ + "../dependencies/anthropic-api-key-manager" + ], + "features": [ + "caller-utils" + ] } } diff --git a/hyperdrive/packages/app-store/app-store/src/http_api.rs b/hyperdrive/packages/app-store/app-store/src/http_api.rs index 21366c6c3..c08a38693 100644 --- a/hyperdrive/packages/app-store/app-store/src/http_api.rs +++ b/hyperdrive/packages/app-store/app-store/src/http_api.rs @@ -173,6 +173,83 @@ fn make_widget() -> String {

Top Apps

+ + + + + + + Skeleton App - Hyperware + + +
+ + + diff --git a/hyperdrive/packages/spider/ui/package-lock.json b/hyperdrive/packages/spider/ui/package-lock.json new file mode 100644 index 000000000..eee3a35dc --- /dev/null +++ b/hyperdrive/packages/spider/ui/package-lock.json @@ -0,0 +1,4750 @@ +{ + "name": "skeleton-app-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "skeleton-app-ui", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", + "zustand": "^4.5.5" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react": "^4.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.1", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.14", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.29.tgz", + "integrity": "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.29", + "@swc/core-darwin-x64": "1.11.29", + "@swc/core-linux-arm-gnueabihf": "1.11.29", + "@swc/core-linux-arm64-gnu": "1.11.29", + "@swc/core-linux-arm64-musl": "1.11.29", + "@swc/core-linux-x64-gnu": "1.11.29", + "@swc/core-linux-x64-musl": "1.11.29", + "@swc/core-win32-arm64-msvc": "1.11.29", + "@swc/core-win32-ia32-msvc": "1.11.29", + "@swc/core-win32-x64-msvc": "1.11.29" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.29.tgz", + "integrity": "sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.29.tgz", + "integrity": "sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.29.tgz", + "integrity": "sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.29.tgz", + "integrity": "sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.29.tgz", + "integrity": "sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.29.tgz", + "integrity": "sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.29.tgz", + "integrity": "sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.29.tgz", + "integrity": "sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.29.tgz", + "integrity": "sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.29.tgz", + "integrity": "sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.10.0.tgz", + "integrity": "sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.9", + "@swc/core": "^1.11.22" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.203", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", + "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/hyperdrive/packages/spider/ui/package.json b/hyperdrive/packages/spider/ui/package.json new file mode 100644 index 000000000..e8be791f9 --- /dev/null +++ b/hyperdrive/packages/spider/ui/package.json @@ -0,0 +1,32 @@ +{ + "name": "skeleton-app-ui", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:copy": "npm run build && rm -rf ../pkg/ui && cp -r dist ../pkg/ui", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", + "zustand": "^4.5.5" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react": "^4.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.1", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.14", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } +} diff --git a/hyperdrive/packages/spider/ui/public/spider-256-y.png b/hyperdrive/packages/spider/ui/public/spider-256-y.png new file mode 100644 index 000000000..c2b80003b Binary files /dev/null and b/hyperdrive/packages/spider/ui/public/spider-256-y.png differ diff --git a/hyperdrive/packages/spider/ui/src/App.css b/hyperdrive/packages/spider/ui/src/App.css new file mode 100644 index 000000000..511a85c12 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/App.css @@ -0,0 +1,972 @@ +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #root { + height: 100%; + overflow: hidden; +} + +.app { + height: 100vh; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%); + color: #e0e0e0; + overflow: hidden; +} + +.app-header { + background: rgba(0, 0, 0, 0.3); + padding: 0.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + display: flex; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; + position: relative; +} + +.app-icon { + font-size: 1.2rem; + flex-shrink: 0; + padding: 0 0.25rem; + position: absolute; + right: 0.5rem; + z-index: 1; + opacity: 0.7; +} + +.app-nav { + display: flex; + gap: 0.25rem; + flex: 1; + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; + padding-right: 2.5rem; /* Space for spider icon */ +} + +.app-nav::-webkit-scrollbar { + display: none; +} + +.nav-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #e0e0e0; + padding: 0.4rem 0.6rem; + border-radius: 0.5rem; + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.85rem; + flex-shrink: 0; + display: flex; + align-items: center; + gap: 0.25rem; +} + +.nav-btn-text { + display: none; +} + +.nav-btn-icon { + font-size: 1rem; +} + +@media (min-width: 640px) { + .app-header { + padding: 0.75rem 1.5rem; + } + + .app-icon { + font-size: 2rem; + position: static; + opacity: 1; + padding: 0 0.5rem; + } + + .app-nav { + gap: 0.5rem; + padding-right: 0; + } + + .nav-btn { + padding: 0.5rem 1rem; + font-size: 0.9rem; + } + + .nav-btn-text { + display: inline; + } + + .nav-btn-icon { + display: none; + } +} + +.nav-btn:hover { + background: rgba(255, 255, 255, 0.15); +} + +.nav-btn.active { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-color: transparent; + color: white; +} + +.app-main { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; + min-height: 0; +} + +/* Mobile: Remove padding for chat to use full screen */ +@media (max-width: 639px) { + .app-main.chat-view { + padding: 0; + } + + .app-main.chat-view .chat-container { + height: 100%; + } +} + +/* Desktop: Keep padding */ +@media (min-width: 640px) { + .app-main { + padding: 2rem; + } +} + +.component-container { + max-width: 1200px; + margin: 0 auto; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + padding: 1rem; +} + +@media (min-width: 640px) { + .component-container { + padding: 0; + } +} + +.component-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + flex-wrap: wrap; + gap: 1rem; + flex-shrink: 0; +} + +.component-header h2 { + margin: 0; + font-size: 1.25rem; +} + +@media (min-width: 640px) { + .component-header h2 { + font-size: 1.5rem; + } + + .component-header { + margin-bottom: 1.5rem; + } +} + +/* Scrollable content area for lists */ +.component-content { + flex: 1; + overflow-y: auto; + min-height: 0; +} + +.btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + color: white; + padding: 0.6rem 1rem; + border-radius: 0.5rem; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.3s ease; + white-space: nowrap; +} + +@media (min-width: 640px) { + .btn { + padding: 0.75rem 1.5rem; + font-size: 1rem; + } +} + +.btn:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-danger { + background: linear-gradient(135deg, #f93a5a 0%, #f7778c 100%); +} + +.btn-success { + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); +} + +.error-message { + background: rgba(249, 58, 90, 0.1); + border: 1px solid rgba(249, 58, 90, 0.3); + color: #f7778c; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1rem; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.empty-state { + text-align: center; + color: rgba(255, 255, 255, 0.5); + padding: 2rem; + font-size: 1rem; +} + +@media (min-width: 640px) { + .empty-state { + padding: 3rem; + font-size: 1.1rem; + } +} + +.form-group { + margin-bottom: 1.25rem; +} + +@media (min-width: 640px) { + .form-group { + margin-bottom: 1.5rem; + } +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #b0b0b0; + font-size: 0.9rem; +} + +.form-group input, +.form-group select { + width: 100%; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #e0e0e0; + padding: 0.6rem; + border-radius: 0.5rem; + font-size: 0.95rem; +} + +@media (min-width: 640px) { + .form-group input, + .form-group select { + padding: 0.75rem; + font-size: 1rem; + } +} + +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: #667eea; + background: rgba(255, 255, 255, 0.08); +} + +.api-key-form, +.spider-key-form, +.mcp-server-form, +.settings-form { + background: rgba(255, 255, 255, 0.05); + padding: 1.5rem; + border-radius: 1rem; + margin-bottom: 1.5rem; +} + +@media (min-width: 640px) { + .api-key-form, + .spider-key-form, + .mcp-server-form, + .settings-form { + padding: 2rem; + margin-bottom: 2rem; + } +} + +.api-keys-list, +.spider-keys-list, +.mcp-servers-list, +.conversations-list { + display: grid; + gap: 1rem; +} + +.api-key-item, +.spider-key-item, +.conversation-item { + background: rgba(255, 255, 255, 0.05); + padding: 1rem; + border-radius: 0.75rem; + display: flex; + justify-content: space-between; + align-items: start; + transition: all 0.3s ease; + gap: 1rem; + flex-wrap: wrap; +} + +/* MCP Server item special layout for better mobile display */ +.mcp-server-item { + background: rgba(255, 255, 255, 0.05); + padding: 1rem; + border-radius: 0.75rem; + transition: all 0.3s ease; + display: flex; + flex-direction: column; + gap: 1rem; +} + +@media (min-width: 640px) { + .api-key-item, + .spider-key-item, + .conversation-item { + padding: 1.5rem; + align-items: center; + } + + .mcp-server-item { + padding: 1.5rem; + flex-direction: row; + justify-content: space-between; + align-items: start; + } +} + +.api-key-item:hover, +.spider-key-item:hover, +.mcp-server-item:hover, +.conversation-item:hover { + background: rgba(255, 255, 255, 0.08); +} + +.conversation-item { + cursor: pointer; +} + +.api-key-info, +.spider-key-info, +.mcp-server-info, +.conversation-info { + flex: 1; + min-width: 0; +} + +.mcp-server-info { + width: 100%; +} + +.api-key-info h3, +.spider-key-info h3, +.mcp-server-info h3, +.conversation-info h3 { + margin: 0 0 0.5rem 0; + color: #ffffff; + font-size: 1rem; + word-wrap: break-word; + overflow-wrap: break-word; +} + +@media (min-width: 640px) { + .api-key-info h3, + .spider-key-info h3, + .mcp-server-info h3, + .conversation-info h3 { + font-size: 1.1rem; + } +} + +.api-key-info p, +.spider-key-info p, +.mcp-server-info p, +.conversation-info p { + margin: 0.25rem 0; + color: #b0b0b0; + font-size: 0.85rem; + word-wrap: break-word; + overflow-wrap: break-word; +} + +@media (min-width: 640px) { + .api-key-info p, + .spider-key-info p, + .mcp-server-info p, + .conversation-info p { + font-size: 0.9rem; + } +} + +/* MCP Server actions - stack on mobile */ +.mcp-server-actions { + display: flex; + gap: 0.5rem; + width: 100%; + flex-wrap: wrap; +} + +@media (min-width: 640px) { + .mcp-server-actions { + width: auto; + flex-wrap: nowrap; + } +} + +.key-value { + font-family: monospace; + background: rgba(0, 0, 0, 0.3); + padding: 0.4rem; + border-radius: 0.25rem; + font-size: 0.8rem; + word-break: break-all; + display: block; + overflow-x: auto; +} + +@media (min-width: 640px) { + .key-value { + padding: 0.5rem; + font-size: 0.85rem; + } +} + +.permissions-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + gap: 0.75rem; +} + +@media (min-width: 640px) { + .permissions-grid { + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 1rem; + } +} + +.checkbox-label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + font-size: 0.9rem; +} + +.checkbox-label input { + width: auto; +} + +/* Chat container - full height and width on mobile */ +.chat-container { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + max-width: 100%; + margin: 0; + overflow: hidden; +} + +@media (max-width: 639px) { + .chat-container { + border-radius: 0; + border: none; + height: 100%; + display: flex; + flex-direction: column; + } +} + +@media (min-width: 768px) { + .chat-container { + max-width: 900px; + margin: 0 auto; + } +} + +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + background-color: rgba(0, 0, 0, 0.2); + flex-shrink: 0; + position: relative; + z-index: 5; +} + +.chat-header h2 { + margin: 0; + font-size: 1.1rem; +} + +@media (min-width: 640px) { + .chat-header { + padding: 0.75rem 1rem; + } + + .chat-header h2 { + font-size: 1.25rem; + } +} + +.chat-messages { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 0.5rem; + background: rgba(0, 0, 0, 0.2); + min-height: 0; /* Important for flexbox */ +} + +@media (max-width: 639px) { + .chat-messages { + border-radius: 0; + background: transparent; + padding: 0.5rem; + } +} + +@media (min-width: 640px) { + .chat-messages { + padding: 0.75rem; + border-radius: 0; + } +} + +.message { + margin-bottom: 0.05rem; + padding: 0.15rem 0.75rem; + border-radius: 0.5rem; + word-wrap: break-word; + overflow-wrap: break-word; + max-width: 100%; +} + +@media (min-width: 640px) { + .message { + margin-bottom: 0.06rem; + padding: 0.18rem 0.9rem; + border-radius: 0.75rem; + } +} + +.message-user { + background: rgba(102, 126, 234, 0.2); + margin-left: auto; + max-width: 85%; +} + +@media (min-width: 640px) { + .message-user { + max-width: 70%; + } +} + +.message-assistant { + background: rgba(255, 255, 255, 0.05); + max-width: 90%; +} + +@media (min-width: 640px) { + .message-assistant { + max-width: 70%; + } +} + +.message-thinking { + opacity: 0.7; + margin-bottom: 0.05rem; +} + +.message-role { + font-size: 0.75rem; + color: #888; + margin-bottom: 0.4rem; + text-transform: uppercase; +} + +@media (min-width: 640px) { + .message-role { + font-size: 0.8rem; + margin-bottom: 0.5rem; + } +} + +.message-content { + color: #e0e0e0; + line-height: 1.25; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; +} + +.message-content pre { + overflow-x: auto; + max-width: 100%; +} + +.message-content code { + word-break: break-all; +} + +.empty-chat { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: rgba(255, 255, 255, 0.3); + font-size: 1rem; + text-align: center; + padding: 1rem; +} + +@media (min-width: 640px) { + .empty-chat { + font-size: 1.1rem; + } +} + +.chat-input-form { + display: flex; + gap: 0.5rem; + padding: 0.75rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + background-color: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(10px); + flex-shrink: 0; + width: 100%; + position: relative; + z-index: 5; +} + +@media (max-width: 639px) { + .chat-input-form { + border-radius: 0; + } +} + +@media (min-width: 640px) { + .chat-input-form { + position: relative; + } +} + +@media (min-width: 640px) { + .chat-input-form { + gap: 1rem; + padding: 1rem; + } +} + +.chat-input { + flex: 1; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #e0e0e0; + padding: 0.75rem; + border-radius: 0.5rem; + font-size: 0.95rem; + min-width: 0; +} + +.chat-input-thinking { + opacity: 0.6; +} + +@media (min-width: 640px) { + .chat-input { + padding: 1rem; + font-size: 1rem; + } +} + +.chat-input:focus { + outline: none; + border-color: #667eea; + background: rgba(255, 255, 255, 0.08); +} + +.thinking-indicator { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0; +} + +.spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: var(--primary-color, #667eea); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.tool-spinner { + margin-left: 0.5rem; +} + +/* Tool message styles */ +.message-tool { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.15rem 0.6rem; + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + align-self: flex-start; + max-width: fit-content; + margin-bottom: 0.05rem; +} + +.tool-emoji { + font-size: 1.2rem; +} + +.tool-name { + color: var(--text-primary, #e0e0e0); +} + +.tool-link { + background: none; + border: none; + color: #667eea; + cursor: pointer; + padding: 0; + font-size: 1rem; + text-decoration: underline; + transition: opacity 0.2s; +} + +.tool-link:hover { + opacity: 0.8; +} + +.tool-calls { + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.tool-call { + font-size: 0.8rem; + color: #43e97b; + font-family: monospace; + word-break: break-all; +} + +@media (min-width: 640px) { + .tool-call { + font-size: 0.85rem; + } +} + +/* Model selection styles */ +.model-select-group { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.model-select-group .form-group { + flex: 1; + min-width: 150px; +} + +/* Mobile-specific optimizations */ +@media (max-width: 639px) { + body { + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; + } + + * { + -webkit-tap-highlight-color: transparent; + } + + input, select, textarea { + font-size: 16px; /* Prevents zoom on iOS */ + } +} + +/* WebSocket status indicator */ +.ws-status { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; +} + +.ws-connected { + background-color: rgba(81, 207, 102, 0.1); + color: #51cf66; +} + +.ws-disconnected { + background-color: rgba(255, 107, 107, 0.1); + color: #ff6b6b; +} + +/* Modal styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal-content { + background-color: rgba(255, 255, 255, 0.05); + border-radius: 8px; + max-width: 600px; + max-height: 80vh; + width: 90%; + display: flex; + flex-direction: column; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.modal-header h3 { + margin: 0; +} + +.modal-close { + background: none; + border: none; + color: rgba(255, 255, 255, 0.6); + font-size: 1.5rem; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-close:hover { + color: rgba(255, 255, 255, 0.9); +} + +.modal-body { + padding: 1rem; + overflow-y: auto; + flex: 1; +} + +.modal-section { + margin-bottom: 1.5rem; +} + +.modal-section:last-child { + margin-bottom: 0; +} + +.modal-section h4 { + margin: 0 0 0.5rem 0; + color: rgba(255, 255, 255, 0.6); +} + +.json-display { + background-color: rgba(0, 0, 0, 0.2); + padding: 1rem; + border-radius: 4px; + overflow-x: auto; + font-family: 'Courier New', monospace; + font-size: 0.9rem; + line-height: 1.4; + color: #e0e0e0; +} + +.copy-btn { + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.2); + color: rgba(255, 255, 255, 0.6); + cursor: pointer; + padding: 0.25rem; + border-radius: 4px; + transition: all 0.2s; +} + +.copy-btn:hover { + color: rgba(255, 255, 255, 0.9); + border-color: #667eea; + background: rgba(255, 255, 255, 0.05); +} + +.new-conversation-btn { + background-color: transparent; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.new-conversation-btn:hover { + background-color: rgba(255, 255, 255, 0.05); +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/App.tsx b/hyperdrive/packages/spider/ui/src/App.tsx new file mode 100644 index 000000000..f3bfe1177 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/App.tsx @@ -0,0 +1,107 @@ +import { useState, useEffect } from 'react'; +import './App.css'; +import { useSpiderStore } from './store/spider'; +import ApiKeys from './components/ApiKeys'; +import SpiderKeys from './components/SpiderKeys'; +import McpServers from './components/McpServers'; +import Conversations from './components/Conversations'; +import Chat from './components/Chat'; +import Settings from './components/Settings'; + +export type TabType = 'chat' | 'api-keys' | 'spider-keys' | 'mcp-servers' | 'conversations' | 'settings'; + +function App() { + const { initialize, clearError, apiKeys } = useSpiderStore(); + const [activeTab, setActiveTab] = useState('api-keys'); + + useEffect(() => { + initialize().then(() => { + // After initialization, check if we should switch to Chat tab if API keys exist + const store = useSpiderStore.getState(); + if (store.apiKeys.length > 0) { + setActiveTab('chat'); + } + // Otherwise stay on API Keys tab + }); + }, [initialize]); + + // Clear error when switching tabs + const handleTabChange = (tab: TabType) => { + clearError(); + setActiveTab(tab); + }; + + // Export for Conversations component to use + useEffect(() => { + (window as any).switchToChat = () => setActiveTab('chat'); + }, []); + + return ( +
+
+ 🕷️ + +
+ +
+ {activeTab === 'chat' && } + {activeTab === 'api-keys' && } + {activeTab === 'spider-keys' && } + {activeTab === 'mcp-servers' && } + {activeTab === 'conversations' && } + {activeTab === 'settings' && } +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/auth/anthropic.ts b/hyperdrive/packages/spider/ui/src/auth/anthropic.ts new file mode 100644 index 000000000..5b71b0af4 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/auth/anthropic.ts @@ -0,0 +1,85 @@ +import { ApiError, exchange_oauth_token as exchangeOauthToken, refresh_oauth_token as refreshOauthToken } from '@caller-utils'; + +export namespace AuthAnthropic { + const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; + + // Generate PKCE challenge and verifier + async function generatePKCE() { + const generateRandomString = (length: number) => { + const array = new Uint8Array(length); + crypto.getRandomValues(array); + return btoa(String.fromCharCode(...array)) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); + }; + + const verifier = generateRandomString(32); + + const encoder = new TextEncoder(); + const data = encoder.encode(verifier); + const hash = await crypto.subtle.digest('SHA-256', data); + + const challenge = btoa(String.fromCharCode(...new Uint8Array(hash))) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); + + return { verifier, challenge }; + } + + export async function authorize() { + const pkce = await generatePKCE(); + + const url = new URL("https://claude.ai/oauth/authorize"); + url.searchParams.set("code", "true"); + url.searchParams.set("client_id", CLIENT_ID); + url.searchParams.set("response_type", "code"); + url.searchParams.set("redirect_uri", "https://console.anthropic.com/oauth/code/callback"); + url.searchParams.set("scope", "org:create_api_key user:profile user:inference"); + url.searchParams.set("code_challenge", pkce.challenge); + url.searchParams.set("code_challenge_method", "S256"); + url.searchParams.set("state", pkce.verifier); + + return { + url: url.toString(), + verifier: pkce.verifier, + }; + } + + export async function exchange(code: string, verifier: string) { + try { + // Use the backend proxy to avoid CORS issues + const result = await exchangeOauthToken({ + code: code, + verifier: verifier, + }); + + return result; + } catch (error) { + if (error instanceof ApiError) { + throw error; + } + throw new ExchangeFailed(); + } + } + + export async function refresh(refreshToken: string) { + try { + // Use the backend proxy to avoid CORS issues + const result = await refreshOauthToken({ + refreshToken: refreshToken, + }); + + return result; + } catch (error) { + throw new Error("Failed to refresh token"); + } + } + + export class ExchangeFailed extends Error { + constructor() { + super("Exchange failed"); + } + } +} diff --git a/hyperdrive/packages/spider/ui/src/components/ApiKeys.tsx b/hyperdrive/packages/spider/ui/src/components/ApiKeys.tsx new file mode 100644 index 000000000..b50b1699b --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/ApiKeys.tsx @@ -0,0 +1,128 @@ +import { useState } from 'react'; +import { useSpiderStore } from '../store/spider'; +import { ClaudeLogin } from './ClaudeLogin'; + +export default function ApiKeys() { + const { apiKeys, isLoading, error, setApiKey, removeApiKey } = useSpiderStore(); + const [showAddForm, setShowAddForm] = useState(false); + const [showClaudeLogin, setShowClaudeLogin] = useState(false); + const [provider, setProvider] = useState('anthropic'); + const [apiKeyValue, setApiKeyValue] = useState(''); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!apiKeyValue.trim()) return; + + await setApiKey(provider, apiKeyValue); + setApiKeyValue(''); + setShowAddForm(false); + }; + + return ( +
+
+

API Keys

+
+ + +
+
+ + {error && ( +
+ {error} +
+ )} + + {showClaudeLogin && ( +
+ { + setShowClaudeLogin(false); + // Refresh API keys list + useSpiderStore.getState().loadApiKeys(); + }} + onCancel={() => setShowClaudeLogin(false)} + /> +
+ )} + + {showAddForm && ( +
+
+ + +
+ +
+ + setApiKeyValue(e.target.value)} + placeholder="sk-..." + required + /> +
+ + +
+ )} + +
+
+ {apiKeys.length === 0 ? ( +

No API keys configured

+ ) : ( + apiKeys.map((key) => ( +
+
+

{key.provider}

+

Key: {key.keyPreview}

+

Created: {new Date(key.createdAt * 1000).toLocaleDateString()}

+ {key.lastUsed && ( +

Last used: {new Date(key.lastUsed * 1000).toLocaleDateString()}

+ )} +
+ +
+ )) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/components/Chat.tsx b/hyperdrive/packages/spider/ui/src/components/Chat.tsx new file mode 100644 index 000000000..6c1ad09d4 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/Chat.tsx @@ -0,0 +1,313 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useSpiderStore } from '../store/spider'; +import ReactMarkdown from 'react-markdown'; +import { webSocketService } from '../services/websocket'; + +interface ToolCall { + id: string; + tool_name: string; + parameters: string; +} + +interface ToolResult { + tool_call_id: string; + result: string; +} + +function ToolCallModal({ toolCall, toolResult, onClose }: { + toolCall: ToolCall; + toolResult?: ToolResult; + onClose: () => void; +}) { + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text).then(() => { + // Could add a toast notification here + }).catch(err => { + console.error('Failed to copy:', err); + }); + }; + + return ( +
+
e.stopPropagation()}> +
+

Tool Call Details: {toolCall.tool_name}

+ +
+
+
+
+

Tool Call

+ +
+
+              {JSON.stringify(toolCall, null, 2)}
+            
+
+ {toolResult && ( +
+
+

Tool Result

+ +
+
+                {JSON.stringify(toolResult, null, 2)}
+              
+
+ )} +
+
+
+ ); +} + +export default function Chat() { + const { + activeConversation, + isLoading, + error, + sendMessage, + clearActiveConversation, + cancelRequest, + wsConnected, + useWebSocket + } = useSpiderStore(); + const [message, setMessage] = useState(''); + const [selectedToolCall, setSelectedToolCall] = useState<{call: ToolCall, result?: ToolResult} | null>(null); + const abortControllerRef = useRef(null); + const messagesEndRef = useRef(null); + const chatMessagesRef = useRef(null); + const inputRef = useRef(null); + + // Auto-scroll to bottom when new messages arrive + const scrollToBottom = (smooth: boolean = true) => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'end' }); + } + }; + + // Scroll on new messages or loading state changes + useEffect(() => { + // Use a small delay to ensure DOM has updated + const timer = setTimeout(() => { + scrollToBottom(); + }, 100); + return () => clearTimeout(timer); + }, [activeConversation?.messages?.length, isLoading]); + + // Scroll immediately when conversation changes + useEffect(() => { + scrollToBottom(false); + }, [activeConversation?.id]); + + // Auto-focus input when loading completes (assistant finishes responding) + useEffect(() => { + if (!isLoading && inputRef.current) { + inputRef.current.focus(); + } + }, [isLoading]); + + // Helper to get tool emoji - always use the same emoji + const getToolEmoji = (toolName: string) => { + return '🔧'; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!message.trim() || isLoading) return; + + const controller = new AbortController(); + abortControllerRef.current = controller; + + try { + await sendMessage(message, controller.signal); + setMessage(''); + // Scroll after sending message + setTimeout(() => scrollToBottom(), 50); + } catch (err: any) { + if (err.name !== 'AbortError') { + console.error('Failed to send message:', err); + } + } finally { + abortControllerRef.current = null; + } + }; + + const handleCancel = () => { + // Cancel HTTP request if using HTTP + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + + // Cancel WebSocket request if using WebSocket + if (useWebSocket && wsConnected) { + webSocketService.sendCancel(); + } + + // Update store state + if (cancelRequest) { + cancelRequest(); + } + }; + + const handleNewConversation = () => { + clearActiveConversation(); + }; + + return ( +
+
+

Chat

+
+ {useWebSocket && ( + + {wsConnected ? '🟢' : '🔴'} {wsConnected ? 'Live' : 'HTTP'} + + )} + +
+
+ + {error && ( +
+ {error} +
+ )} + +
+ {activeConversation?.messages.map((msg, index) => { + const toolCalls = msg.toolCallsJson ? JSON.parse(msg.toolCallsJson) as ToolCall[] : null; + const toolResults = msg.toolResultsJson ? JSON.parse(msg.toolResultsJson) as ToolResult[] : null; + const nextMsg = activeConversation.messages[index + 1]; + const hasToolResult = nextMsg?.role === 'tool' && nextMsg.toolResultsJson; + + return ( + + {msg.role !== 'tool' && (msg.content && msg.content.trim() && msg.content !== '[Tool calls pending]') && ( +
+
+ {msg.content} +
+
+ )} + + {/* Display tool calls as separate message-like items */} + {toolCalls && toolCalls.map((toolCall, toolIndex) => { + // Find the corresponding tool result in the next message if it's a tool message + const toolResultFromNext = nextMsg?.role === 'tool' && nextMsg.toolResultsJson + ? (JSON.parse(nextMsg.toolResultsJson) as ToolResult[])?.find(r => r.tool_call_id === toolCall.id) + : null; + + const isLastMessage = index === activeConversation.messages.length - 1; + const isWaitingForResult = isLastMessage && isLoading && !toolResultFromNext; + + return ( +
+ {getToolEmoji(toolCall.tool_name)} + {isWaitingForResult ? ( + <> + {toolCall.tool_name} + + + ) : ( + + )} +
+ ); + })} +
+ ); + }) || ( +
+

Start a conversation by typing a message below

+
+ )} + {isLoading && activeConversation && ( +
+
+
+ + Thinking... +
+
+
+ )} +
+
+ + {selectedToolCall && ( + setSelectedToolCall(null)} + /> + )} + +
+ setMessage(e.target.value)} + placeholder={isLoading ? "Thinking..." : "Type your message..."} + disabled={isLoading} + className={`chat-input ${isLoading ? 'chat-input-thinking' : ''}`} + /> + {isLoading ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/hyperdrive/packages/spider/ui/src/components/ClaudeLogin.tsx b/hyperdrive/packages/spider/ui/src/components/ClaudeLogin.tsx new file mode 100644 index 000000000..420fced48 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/ClaudeLogin.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { AuthAnthropic } from '../auth/anthropic'; +import { useSpiderStore } from '../store/spider'; + +interface ClaudeLoginProps { + onSuccess?: () => void; + onCancel?: () => void; +} + +export const ClaudeLogin: React.FC = ({ onSuccess, onCancel }) => { + const [isLoading, setIsLoading] = useState(false); + const [authUrl, setAuthUrl] = useState(null); + const [verifier, setVerifier] = useState(null); + const [authCode, setAuthCode] = useState(''); + const [error, setError] = useState(null); + + const handleStartAuth = async () => { + setIsLoading(true); + setError(null); + + try { + const { url, verifier: v } = await AuthAnthropic.authorize(); + setAuthUrl(url); + setVerifier(v); + + // Open auth URL in new window + window.open(url, '_blank', 'width=600,height=700'); + } catch (err) { + setError('Failed to start authentication'); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + const handleExchangeCode = async () => { + if (!authCode || !verifier) { + setError('Please enter the authorization code'); + return; + } + + setIsLoading(true); + setError(null); + + try { + const tokens = await AuthAnthropic.exchange(authCode, verifier); + + // Store tokens in localStorage + localStorage.setItem('claude_oauth', JSON.stringify({ + refresh: tokens.refresh, + access: tokens.access, + expires: tokens.expires, + })); + + // Store as API key in Spider + const store = useSpiderStore.getState(); + await store.setApiKey('anthropic-oauth', tokens.access); + + onSuccess?.(); + } catch (err) { + setError('Invalid authorization code. Please try again.'); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + + return ( +
+
+
+

Login with Claude

+

+ Authenticate with your Claude.ai account to use your subscription for chat. +

+
+ + + {!authUrl ? ( +
+ +
+ ) : ( +
+
+

+ A new window has opened for authentication. After you authorize the app, + you'll receive a code. Please paste it below: +

+ + Click here if the window didn't open + +
+ +
+ + setAuthCode(e.target.value)} + placeholder="Paste the code here" + className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ + +
+ )} + + {error && ( +
+ {error} +
+ )} + + {onCancel && ( + + )} +
+
+ ); +}; \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/components/Conversations.tsx b/hyperdrive/packages/spider/ui/src/components/Conversations.tsx new file mode 100644 index 000000000..d58baf443 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/Conversations.tsx @@ -0,0 +1,57 @@ +import { useEffect } from 'react'; +import { useSpiderStore } from '../store/spider'; + +export default function Conversations() { + const { conversations, loadConversations, loadConversation, isLoading } = useSpiderStore(); + + useEffect(() => { + loadConversations(); + }, [loadConversations]); + + const handleSelectConversation = async (id: string) => { + await loadConversation(id); + // Switch to Chat tab after loading conversation + if ((window as any).switchToChat) { + (window as any).switchToChat(); + } + }; + + return ( +
+
+

Conversation History

+ +
+ +
+
+ {conversations.length === 0 ? ( +

No conversations yet

+ ) : ( + conversations.map((conv) => ( +
handleSelectConversation(conv.id)} + > +
+

Conversation {conv.id.substring(0, 8)}...

+

Client: {conv.metadata.client}

+

Started: {conv.metadata.start_time}

+

Messages: {conv.messages.length}

+

Provider: {conv.llm_provider}

+
+
+ )) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/components/McpServers.tsx b/hyperdrive/packages/spider/ui/src/components/McpServers.tsx new file mode 100644 index 000000000..b3990be06 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/McpServers.tsx @@ -0,0 +1,321 @@ +import { useState, useEffect } from 'react'; +import { useSpiderStore } from '../store/spider'; + +export default function McpServers() { + const { + mcpServers, + isLoading, + error, + addMcpServer, + connectMcpServer, + disconnectMcpServer, + removeMcpServer, + loadMcpServers + } = useSpiderStore(); + const [showAddForm, setShowAddForm] = useState(false); + const [serverName, setServerName] = useState(''); + const [transportType, setTransportType] = useState('websocket'); + const [url, setUrl] = useState('ws://localhost:10125'); + const [hypergridUrl, setHypergridUrl] = useState('http://localhost:8080/operator:hypergrid:ware.hypr/shim/mcp'); + const [hypergridToken, setHypergridToken] = useState(''); + const [hypergridClientId, setHypergridClientId] = useState(''); + const [hypergridNode, setHypergridNode] = useState(''); + const [connectingServers, setConnectingServers] = useState>(new Set()); + + // Periodically refresh server status + useEffect(() => { + const interval = setInterval(() => { + loadMcpServers(); + }, 5000); // Refresh every 5 seconds + + return () => clearInterval(interval); + }, [loadMcpServers]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + let transport: any = { + transportType: transportType, + command: null, + args: null, + url: null, + hypergridToken: null, + hypergridClientId: null, + hypergridNode: null + }; + + if (transportType === 'websocket') { + transport.url = url; + } else if (transportType === 'hypergrid') { + transport.url = hypergridUrl; + transport.hypergridToken = hypergridToken; + transport.hypergridClientId = hypergridClientId; + transport.hypergridNode = hypergridNode; + } + + await addMcpServer(serverName, transport); + setServerName(''); + setTransportType('websocket'); + setUrl('ws://localhost:10125'); + setHypergridUrl('http://localhost:8080/operator:hypergrid:ware.hypr/shim/mcp'); + setHypergridToken(''); + setHypergridClientId(''); + setHypergridNode(''); + setShowAddForm(false); + + // Refresh servers list after adding + setTimeout(() => loadMcpServers(), 500); + }; + + const handleConnect = async (serverId: string) => { + setConnectingServers(prev => new Set(prev).add(serverId)); + try { + await connectMcpServer(serverId); + // Poll for connection status update + let attempts = 0; + const pollInterval = setInterval(async () => { + await loadMcpServers(); + attempts++; + const server = mcpServers.find(s => s.id === serverId); + if (server?.connected || attempts > 10) { + clearInterval(pollInterval); + setConnectingServers(prev => { + const next = new Set(prev); + next.delete(serverId); + return next; + }); + } + }, 500); + } catch (error) { + setConnectingServers(prev => { + const next = new Set(prev); + next.delete(serverId); + return next; + }); + } + }; + + const handleDisconnect = async (serverId: string) => { + await disconnectMcpServer(serverId); + // Refresh servers list after disconnecting + setTimeout(() => loadMcpServers(), 500); + }; + + const handleRemove = async (serverId: string) => { + if (confirm('Are you sure you want to remove this MCP server?')) { + await removeMcpServer(serverId); + // Refresh servers list after removing + setTimeout(() => loadMcpServers(), 500); + } + }; + + return ( +
+
+

MCP Servers

+ +
+ + {error && ( +
+ {error} +
+ )} + + {showAddForm && ( +
+
+ + setServerName(e.target.value)} + placeholder="My MCP Server" + required + /> +
+ +
+ + +
+ + {transportType === 'websocket' && ( +
+ + setUrl(e.target.value)} + placeholder="ws://localhost:10125" + required + /> + + URL of the WebSocket MCP server or ws-mcp wrapper + +
+ )} + + {transportType === 'hypergrid' && ( + <> +
+ + setHypergridUrl(e.target.value)} + placeholder="http://localhost:8080/operator:hypergrid:ware.hypr/shim/mcp" + required + /> + + Base URL for the Hypergrid API endpoint + +
+ +
+ + setHypergridToken(e.target.value)} + placeholder="Enter your hypergrid token (optional for initial connection)" + /> + + Token for authenticating with the Hypergrid network + +
+ +
+ + setHypergridClientId(e.target.value)} + placeholder="Enter your client ID" + required + /> + + Unique identifier for this client + +
+ +
+ + setHypergridNode(e.target.value)} + placeholder="Enter your Hyperware node name" + required + /> + + Name of your Hyperware node + +
+ + )} + + +
+ )} + +
+
+ {mcpServers.length === 0 ? ( +

No MCP servers configured

+ ) : ( + mcpServers.map((server) => { + const isConnecting = connectingServers.has(server.id); + return ( +
+
+

{server.name}

+

+ Status: { + isConnecting ? '🟡 Connecting...' : + server.connected ? '🟢 Connected' : + '🔴 Disconnected' + } +

+

+ Transport: {server.transport.transportType === 'hypergrid' ? + `Hypergrid - ${server.transport.hypergridNode || 'Not configured'}` : + `WebSocket - ${server.transport.url || 'No URL specified'}` + } +

+

Tools: {server.tools.length}

+ {server.tools.length > 0 && ( +
+ Available Tools +
    + {server.tools.map((tool, index) => ( +
  • + {tool.name}: {tool.description} + {tool.inputSchemaJson && (✓ Schema)} +
  • + ))} +
+
+ )} +
+
+ {!server.connected && !isConnecting && ( + + )} + {server.connected && ( + + )} + +
+
+ ); + }) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/components/Settings.tsx b/hyperdrive/packages/spider/ui/src/components/Settings.tsx new file mode 100644 index 000000000..a08a4d019 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/Settings.tsx @@ -0,0 +1,125 @@ +import { useState, useEffect } from 'react'; +import { useSpiderStore } from '../store/spider'; + +export default function Settings() { + const { config, isLoading, error, updateConfig } = useSpiderStore(); + const [provider, setProvider] = useState(config.defaultLlmProvider); + const [model, setModel] = useState(''); + const [maxTokens, setMaxTokens] = useState(config.maxTokens); + const [temperature, setTemperature] = useState(config.temperature); + + // Model options based on provider + const modelOptions = { + anthropic: [ + { value: 'claude-opus-4-1-20250805', label: 'Claude 4.1 Opus' }, + { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet' } + ], + openai: [ + { value: 'gpt-4o', label: 'GPT-4o' }, + { value: 'gpt-4o-mini', label: 'GPT-4o Mini' }, + { value: 'gpt-4-turbo', label: 'GPT-4 Turbo' }, + { value: 'gpt-4', label: 'GPT-4' }, + { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' } + ], + google: [ + { value: 'gemini-2.0-flash-exp', label: 'Gemini 2.0 Flash (Experimental)' }, + { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro' }, + { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash' }, + { value: 'gemini-pro', label: 'Gemini Pro' } + ] + }; + + useEffect(() => { + setProvider(config.defaultLlmProvider); + setMaxTokens(config.maxTokens); + setTemperature(config.temperature); + }, [config]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await updateConfig({ + defaultLlmProvider: provider, + maxTokens: maxTokens, + temperature: temperature, + }); + }; + + return ( +
+
+

Settings

+
+ + {error && ( +
+ {error} +
+ )} + +
+
+
+ + +
+ +
+ + +
+
+ +
+ + setMaxTokens(Number(e.target.value))} + min="1" + max="100000" + /> +
+ +
+ + setTemperature(Number(e.target.value))} + min="0" + max="2" + step="0.1" + /> +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/components/SpiderKeys.tsx b/hyperdrive/packages/spider/ui/src/components/SpiderKeys.tsx new file mode 100644 index 000000000..bd414e732 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/components/SpiderKeys.tsx @@ -0,0 +1,109 @@ +import { useState } from 'react'; +import { useSpiderStore } from '../store/spider'; + +export default function SpiderKeys() { + const { spiderKeys, isLoading, error, createSpiderKey, revokeSpiderKey } = useSpiderStore(); + const [showAddForm, setShowAddForm] = useState(false); + const [keyName, setKeyName] = useState(''); + const [permissions, setPermissions] = useState(['read']); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!keyName.trim()) return; + + await createSpiderKey(keyName, permissions); + setKeyName(''); + setPermissions(['read']); + setShowAddForm(false); + }; + + const togglePermission = (perm: string) => { + setPermissions(prev => + prev.includes(perm) + ? prev.filter(p => p !== perm) + : [...prev, perm] + ); + }; + + return ( +
+
+

Spider API Keys

+ +
+ + {error && ( +
+ {error} +
+ )} + + {showAddForm && ( +
+
+ + setKeyName(e.target.value)} + placeholder="My API Key" + required + /> +
+ +
+ +
+ {['read', 'write', 'chat', 'admin'].map(perm => ( + + ))} +
+
+ + +
+ )} + +
+
+ {spiderKeys.length === 0 ? ( +

No Spider API keys generated

+ ) : ( + spiderKeys.map((key) => ( +
+
+

{key.name}

+

Key: {key.key}

+

Permissions: {key.permissions.join(', ')}

+

Created: {new Date(key.createdAt * 1000).toLocaleDateString()}

+
+ +
+ )) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/index.css b/hyperdrive/packages/spider/ui/src/index.css new file mode 100644 index 000000000..baa2da425 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/index.css @@ -0,0 +1,665 @@ +/* Root CSS for Skeleton App */ + +:root { + /* Color scheme - customize these for your app */ + --primary-color: #646cff; + --primary-hover: #535bf2; + --background: #242424; + --surface: #1a1a1a; + --text-primary: rgba(255, 255, 255, 0.87); + --text-secondary: rgba(255, 255, 255, 0.6); + --border-color: rgba(255, 255, 255, 0.1); + --error-color: #ff6b6b; + --success-color: #51cf66; + + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: dark; + color: var(--text-primary); + background-color: var(--background); + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + min-width: 320px; + min-height: 100vh; + overflow: hidden; +} + +#root { + width: 100%; + height: 100vh; + margin: 0; + padding: 0; + overflow: hidden; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: var(--surface); + cursor: pointer; + transition: border-color 0.25s; +} + +button:hover { + border-color: var(--primary-color); +} + +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +input, textarea { + padding: 0.6em; + font-size: 1em; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--surface); + color: var(--text-primary); + font-family: inherit; +} + +input:focus, textarea:focus { + outline: none; + border-color: var(--primary-color); +} + +/* Utility classes */ +.error { + color: var(--error-color); + padding: 1em; + border-radius: 4px; + background-color: rgba(255, 107, 107, 0.1); + border: 1px solid var(--error-color); +} + +.success { + color: var(--success-color); + padding: 1em; + border-radius: 4px; + background-color: rgba(81, 207, 102, 0.1); + border: 1px solid var(--success-color); +} + +.loading { + opacity: 0.6; + pointer-events: none; +} + +/* Chat Styles */ +.chat-container { + display: flex; + flex-direction: column; + height: 600px; + border: 1px solid var(--border-color); + border-radius: 8px; + overflow: hidden; +} + +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid var(--border-color); + background-color: var(--surface); +} + +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message { + padding: 0.15rem 0.75rem; + border-radius: 8px; + max-width: 80%; + margin-bottom: 0.05rem; +} + +.message-user { + align-self: flex-end; + background-color: var(--primary-color); + color: white; +} + +.message-assistant { + align-self: flex-start; + background-color: var(--surface); + border: 1px solid var(--border-color); +} + +.message-thinking { + opacity: 0.7; +} + +.message-role { + font-size: 0.8rem; + font-weight: bold; + margin-bottom: 0.5rem; + text-transform: capitalize; +} + +.message-content { + line-height: 1.25; +} + +.message-content h1, +.message-content h2, +.message-content h3, +.message-content h4, +.message-content h5, +.message-content h6 { + margin-top: 0.5rem; + margin-bottom: 0.25rem; +} + +.message-content p { + margin: 0.25rem 0; +} + +.message-content ul, +.message-content ol { + margin: 0.25rem 0; + padding-left: 1.5rem; +} + +.message-content code { + background-color: rgba(255, 255, 255, 0.1); + padding: 0.2rem 0.4rem; + border-radius: 3px; + font-family: 'Courier New', monospace; +} + +.message-content pre { + background-color: rgba(0, 0, 0, 0.2); + padding: 1rem; + border-radius: 4px; + overflow-x: auto; +} + +.message-content pre code { + background-color: transparent; + padding: 0; +} + +.thinking-indicator { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.chat-input-form { + display: flex; + gap: 0.5rem; + padding: 1rem; + border-top: 1px solid var(--border-color); + background-color: var(--surface); +} + +.chat-input { + flex: 1; +} + +.chat-input-thinking { + opacity: 0.6; +} + +.empty-chat { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: var(--text-secondary); +} + +/* Button Styles */ +.btn { + padding: 0.5rem 1rem; + border-radius: 4px; + border: none; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover:not(:disabled) { + background-color: var(--primary-hover); +} + +.btn-danger { + background-color: var(--error-color); + color: white; +} + +.btn-danger:hover:not(:disabled) { + background-color: #ff5252; +} + +.btn-success { + background-color: var(--success-color); + color: white; +} + +.btn-success:hover:not(:disabled) { + background-color: #45b55a; +} + +.btn-warning { + background-color: #ffa94d; + color: #1a1a1a; +} + +.btn-warning:hover:not(:disabled) { + background-color: #ff922b; +} + +.btn-icon { + padding: 0.5rem; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + height: 36px; +} + +.new-conversation-btn { + background-color: transparent; + border: 1px solid var(--border-color); +} + +.new-conversation-btn:hover { + background-color: var(--surface); +} + +/* MCP Server Styles */ +.component-container { + padding: 1rem; +} + +.component-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.mcp-server-form { + background-color: var(--surface); + padding: 1.5rem; + border-radius: 8px; + margin-bottom: 1rem; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.form-group input { + width: 100%; +} + +.form-help { + display: block; + margin-top: 0.25rem; + font-size: 0.875rem; + color: var(--text-secondary); +} + +.transport-info { + padding: 0.5rem; + background-color: var(--background); + border-radius: 4px; + color: var(--text-secondary); +} + +.mcp-servers-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.mcp-server-item { + display: flex; + justify-content: space-between; + align-items: start; + padding: 1rem; + background-color: var(--surface); + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.mcp-server-info { + flex: 1; +} + +.mcp-server-info h3 { + margin: 0 0 0.5rem 0; +} + +.mcp-server-info p { + margin: 0.25rem 0; + color: var(--text-secondary); +} + +.mcp-server-actions { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.mcp-server-tools { + margin-top: 0.5rem; +} + +.mcp-server-tools summary { + cursor: pointer; + font-weight: 500; + color: var(--primary-color); +} + +.mcp-server-tools ul { + margin: 0.5rem 0 0 1rem; + padding: 0; + list-style: none; +} + +.mcp-server-tools li { + margin: 0.25rem 0; + font-size: 0.9rem; +} + +.error-message { + color: var(--error-color); + padding: 0.75rem; + border-radius: 4px; + background-color: rgba(255, 107, 107, 0.1); + border: 1px solid var(--error-color); + margin-bottom: 1rem; +} + +.empty-state { + text-align: center; + color: var(--text-secondary); + padding: 2rem; +} + +/* Tool calls and results */ +.tool-calls, +.tool-results { + margin-top: 0.5rem; + padding: 0.5rem; + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.tool-calls summary, +.tool-results summary { + cursor: pointer; + font-weight: 500; + color: var(--text-secondary); +} + +.tool-calls pre, +.tool-results pre { + margin: 0.5rem 0 0 0; + padding: 0.5rem; + background-color: rgba(0, 0, 0, 0.3); + border-radius: 3px; + overflow-x: auto; + font-size: 0.85rem; +} + +/* Tool message styles */ +.message-tool { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.15rem 0.75rem; + background-color: var(--surface); + border: 1px solid var(--border-color); + border-radius: 8px; + align-self: flex-start; + max-width: fit-content; +} + +.tool-emoji { + font-size: 1.2rem; +} + +.tool-name { + color: var(--text-primary); +} + +.tool-spinner { + margin-left: 0.5rem; +} + +.tool-link { + background: none; + border: none; + color: var(--primary-color); + cursor: pointer; + padding: 0; + font-size: 1rem; + text-decoration: underline; + transition: opacity 0.2s; +} + +.tool-link:hover { + opacity: 0.8; +} + +/* Modal styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal-content { + background-color: var(--surface); + border-radius: 8px; + max-width: 600px; + max-height: 80vh; + width: 90%; + display: flex; + flex-direction: column; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.modal-header h3 { + margin: 0; +} + +.modal-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 1.5rem; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-close:hover { + color: var(--text-primary); +} + +.modal-body { + padding: 1rem; + overflow-y: auto; + flex: 1; +} + +.modal-section { + margin-bottom: 1.5rem; +} + +.modal-section:last-child { + margin-bottom: 0; +} + +.modal-section h4 { + margin: 0 0 0.5rem 0; + color: var(--text-secondary); +} + +.json-display { + background-color: rgba(0, 0, 0, 0.2); + padding: 1rem; + border-radius: 4px; + overflow-x: auto; + font-family: 'Courier New', monospace; + font-size: 0.9rem; + line-height: 1.4; + color: var(--text-primary); +} + +.copy-btn { + background: transparent; + border: 1px solid var(--border-color); + color: var(--text-secondary); + cursor: pointer; + padding: 0.25rem; + border-radius: 4px; + transition: all 0.2s; +} + +.copy-btn:hover { + color: var(--text-primary); + border-color: var(--primary-color); + background: var(--surface); +} + +/* WebSocket status indicator */ +.ws-status { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; +} + +.ws-connected { + background-color: rgba(81, 207, 102, 0.1); + color: var(--success-color); +} + +.ws-disconnected { + background-color: rgba(255, 107, 107, 0.1); + color: var(--error-color); +} + +/* Light mode support */ +@media (prefers-color-scheme: light) { + :root { + --background: #ffffff; + --surface: #f5f5f5; + --text-primary: #213547; + --text-secondary: #646464; + --border-color: rgba(0, 0, 0, 0.1); + color: var(--text-primary); + background-color: var(--background); + } + + button { + background-color: #f9f9f9; + } + + .message-content code { + background-color: rgba(0, 0, 0, 0.05); + } + + .message-content pre { + background-color: rgba(0, 0, 0, 0.03); + } + + .tool-calls, + .tool-results { + background-color: rgba(0, 0, 0, 0.03); + } + + .tool-calls pre, + .tool-results pre { + background-color: rgba(0, 0, 0, 0.05); + } +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/main.tsx b/hyperdrive/packages/spider/ui/src/main.tsx new file mode 100644 index 000000000..a3ad16d4d --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/main.tsx @@ -0,0 +1,12 @@ +// Entry point for the React application +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +// Create root and render the app +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/services/websocket.ts b/hyperdrive/packages/spider/ui/src/services/websocket.ts new file mode 100644 index 000000000..33c275358 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/services/websocket.ts @@ -0,0 +1,174 @@ +import { Message, ConversationMetadata } from '@caller-utils'; +import { + WsClientMessage, + WsServerMessage, + AuthMessage, + ChatMessage, + CancelMessage, + PingMessage +} from '../types/websocket'; + +export type MessageHandler = (message: WsServerMessage) => void; + +class WebSocketService { + private ws: WebSocket | null = null; + private messageHandlers: Set = new Set(); + private reconnectTimeout: NodeJS.Timeout | null = null; + private url: string = ''; + private isAuthenticated: boolean = false; + + connect(url: string): Promise { + return new Promise((resolve, reject) => { + if (this.ws?.readyState === WebSocket.OPEN) { + resolve(); + return; + } + + this.url = url; + this.ws = new WebSocket(url); + + this.ws.onopen = () => { + console.log('WebSocket connected'); + this.clearReconnectTimeout(); + resolve(); + }; + + this.ws.onerror = (error) => { + console.error('WebSocket error:', error); + reject(error); + }; + + this.ws.onclose = () => { + console.log('WebSocket disconnected'); + this.isAuthenticated = false; + this.scheduleReconnect(); + }; + + this.ws.onmessage = (event) => { + try { + const message = JSON.parse(event.data) as WsServerMessage; + this.handleMessage(message); + } catch (error) { + console.error('Failed to parse WebSocket message:', error); + } + }; + }); + } + + private handleMessage(message: WsServerMessage) { + // Notify all handlers + this.messageHandlers.forEach(handler => handler(message)); + } + + authenticate(apiKey: string): Promise { + return new Promise((resolve, reject) => { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + reject(new Error('WebSocket not connected')); + return; + } + + // Set up one-time handler for auth response + const authHandler = (message: WsServerMessage) => { + if (message.type === 'auth_success') { + this.isAuthenticated = true; + this.removeMessageHandler(authHandler); + resolve(); + } else if (message.type === 'auth_error') { + this.removeMessageHandler(authHandler); + reject(new Error(message.error || 'Authentication failed')); + } + }; + + this.addMessageHandler(authHandler); + + // Send auth message + const authMsg: AuthMessage = { + type: 'auth', + apiKey + }; + this.send(authMsg); + }); + } + + sendChatMessage(messages: Message[], llmProvider?: string, model?: string, mcpServers?: string[], metadata?: ConversationMetadata): void { + if (!this.isAuthenticated) { + throw new Error('Not authenticated'); + } + + const chatMsg: ChatMessage = { + type: 'chat', + payload: { + messages, + llmProvider, + model, + mcpServers, + metadata + } + }; + this.send(chatMsg); + } + + sendCancel(): void { + if (!this.isAuthenticated) { + throw new Error('Not authenticated'); + } + + const cancelMsg: CancelMessage = { + type: 'cancel' + }; + this.send(cancelMsg); + } + + send(data: WsClientMessage): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket not connected'); + } + + this.ws.send(JSON.stringify(data)); + } + + addMessageHandler(handler: MessageHandler): void { + this.messageHandlers.add(handler); + } + + removeMessageHandler(handler: MessageHandler): void { + this.messageHandlers.delete(handler); + } + + disconnect(): void { + this.clearReconnectTimeout(); + if (this.ws) { + this.ws.close(); + this.ws = null; + } + this.isAuthenticated = false; + } + + private scheduleReconnect(): void { + if (this.reconnectTimeout) return; + + this.reconnectTimeout = setTimeout(() => { + console.log('Attempting to reconnect WebSocket...'); + this.connect(this.url).catch(error => { + console.error('Reconnection failed:', error); + }); + }, 3000); + } + + private clearReconnectTimeout(): void { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + } + + get isConnected(): boolean { + return this.ws?.readyState === WebSocket.OPEN; + } + + get isReady(): boolean { + return this.isConnected && this.isAuthenticated; + } +} + +export const webSocketService = new WebSocketService(); \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/store/spider.ts b/hyperdrive/packages/spider/ui/src/store/spider.ts new file mode 100644 index 000000000..7c15b96a5 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/store/spider.ts @@ -0,0 +1,605 @@ +import { create } from 'zustand'; +import * as api from '../utils/api'; +import { webSocketService } from '../services/websocket'; +import { WsServerMessage } from '../types/websocket'; +import { AuthAnthropic } from '../auth/anthropic'; + +interface ApiKeyInfo { + provider: string; + createdAt: number; + lastUsed?: number; + keyPreview: string; +} + +interface SpiderApiKey { + key: string; + name: string; + permissions: string[]; + createdAt: number; +} + +interface McpServer { + id: string; + name: string; + transport: { + transportType: string; + command?: string; + args?: string[]; + url?: string; + hypergridToken?: string; + hypergridClientId?: string; + hypergridNode?: string; + }; + tools: Array<{ + name: string; + description: string; + parameters: string; + inputSchemaJson?: string; + }>; + connected: boolean; +} + +interface ConversationMetadata { + startTime: string; + client: string; + fromStt: boolean; +} + +interface Conversation { + id: string; + messages: Message[]; + metadata: ConversationMetadata; + llmProvider: string; + model?: string; + mcpServers: string[]; +} + +interface Message { + role: string; + content: string; + toolCallsJson?: string; + toolResultsJson?: string; + timestamp: number; +} + +interface SpiderConfig { + defaultLlmProvider: string; + maxTokens: number; + temperature: number; +} + +interface SpiderStore { + // State + apiKeys: ApiKeyInfo[]; + spiderKeys: SpiderApiKey[]; + mcpServers: McpServer[]; + conversations: Conversation[]; + activeConversation: Conversation | null; + config: SpiderConfig; + isLoading: boolean; + error: string | null; + isConnected: boolean; + nodeId: string; + currentRequestId: string | null; + useWebSocket: boolean; + wsConnected: boolean; + + // Actions + initialize: () => Promise; + setApiKey: (provider: string, key: string) => Promise; + removeApiKey: (provider: string) => Promise; + loadApiKeys: () => Promise; + createSpiderKey: (name: string, permissions: string[]) => Promise; + revokeSpiderKey: (key: string) => Promise; + loadSpiderKeys: () => Promise; + addMcpServer: (name: string, transport: any) => Promise; + connectMcpServer: (serverId: string) => Promise; + disconnectMcpServer: (serverId: string) => Promise; + removeMcpServer: (serverId: string) => Promise; + loadMcpServers: () => Promise; + sendMessage: (message: string, signal?: AbortSignal) => Promise; + cancelRequest: () => Promise; + clearActiveConversation: () => void; + loadConversations: (client?: string, limit?: number) => Promise; + loadConversation: (id: string) => Promise; + loadConfig: () => Promise; + updateConfig: (config: Partial) => Promise; + clearError: () => void; + toggleWebSocket: () => Promise; + connectWebSocket: () => Promise; + disconnectWebSocket: () => void; +} + +// Helper function to fetch admin key using generated binding +async function fetchAdminKey(): Promise { + return api.getAdminKey(); +} + +// Helper function to get API key for chat (checks OAuth tokens) +async function getApiKeyForChat(provider: string): Promise { + // Check if we have an OAuth token for Anthropic + if (provider === 'anthropic' || provider === 'anthropic-oauth') { + const oauthData = localStorage.getItem('claude_oauth'); + if (oauthData) { + try { + const tokens = JSON.parse(oauthData); + + // Check if token is expired + if (tokens.expires && tokens.expires > Date.now()) { + return tokens.access; + } + + // Try to refresh the token + const refreshed = await AuthAnthropic.refresh(tokens.refresh); + + // Update stored tokens + localStorage.setItem('claude_oauth', JSON.stringify({ + refresh: refreshed.refresh, + access: refreshed.access, + expires: refreshed.expires, + })); + + return refreshed.access; + } catch (error) { + console.error('Failed to refresh OAuth token:', error); + // Remove invalid tokens + localStorage.removeItem('claude_oauth'); + } + } + } + + // Fall back to admin key + return (window as any).__spiderAdminKey || null; +} + +export const useSpiderStore = create((set, get) => ({ + // Initial state + apiKeys: [], + spiderKeys: [], + mcpServers: [], + conversations: [], + activeConversation: null, + config: { + defaultLlmProvider: 'anthropic', + maxTokens: 4096, + temperature: 0.7, + }, + isLoading: false, + error: null, + isConnected: false, + nodeId: '', + currentRequestId: null, + useWebSocket: true, // Default to WebSocket for progressive updates + wsConnected: false, + + // Actions + initialize: async () => { + try { + // Fetch admin key if no spider keys are loaded + const state = get(); + if (!state.spiderKeys || state.spiderKeys.length === 0) { + try { + const adminKey = await fetchAdminKey(); + // Store the admin key in a variable accessible to API calls + (window as any).__spiderAdminKey = adminKey; + } catch (error) { + console.error('Failed to fetch admin key:', error); + } + } + set({ isLoading: true }); + + // Check if our.js is loaded + if (typeof window.our === 'undefined') { + set({ + isConnected: false, + error: 'Not connected to Hyperware node. Make sure you are running on a Hyperware node.', + isLoading: false + }); + return; + } + + const nodeId = window.our.node; + set({ isConnected: true, nodeId }); + + // Load initial data + await Promise.all([ + get().loadApiKeys(), + get().loadSpiderKeys(), + get().loadMcpServers(), + get().loadConfig(), + ]); + + // Try to connect WebSocket for progressive updates + if (get().useWebSocket) { + await get().connectWebSocket(); + } + + set({ isLoading: false }); + } catch (error: any) { + set({ + error: error.message || 'Failed to initialize', + isLoading: false, + isConnected: false + }); + } + }, + + setApiKey: async (provider: string, key: string) => { + try { + set({ isLoading: true, error: null }); + await api.setApiKey(provider, key); + await get().loadApiKeys(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to set API key', isLoading: false }); + } + }, + + removeApiKey: async (provider: string) => { + try { + set({ isLoading: true, error: null }); + await api.removeApiKey(provider); + await get().loadApiKeys(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to remove API key', isLoading: false }); + } + }, + + loadApiKeys: async () => { + try { + const keys = await api.listApiKeys(); + set({ apiKeys: keys }); + } catch (error: any) { + set({ error: error.message || 'Failed to load API keys' }); + } + }, + + createSpiderKey: async (name: string, permissions: string[]) => { + try { + set({ isLoading: true, error: null }); + await api.createSpiderKey(name, permissions); + await get().loadSpiderKeys(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to create Spider key', isLoading: false }); + } + }, + + revokeSpiderKey: async (key: string) => { + try { + set({ isLoading: true, error: null }); + await api.revokeSpiderKey(key); + await get().loadSpiderKeys(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to revoke Spider key', isLoading: false }); + } + }, + + loadSpiderKeys: async () => { + try { + // Ensure admin key is fetched first if not already present + if (!(window as any).__spiderAdminKey) { + try { + const adminKey = await fetchAdminKey(); + (window as any).__spiderAdminKey = adminKey; + } catch (error) { + console.error('Failed to fetch admin key:', error); + } + } + + const keys = await api.listSpiderKeys(); + set({ spiderKeys: keys }); + } catch (error: any) { + set({ error: error.message || 'Failed to load Spider keys' }); + } + }, + + addMcpServer: async (name: string, transport: any) => { + try { + set({ isLoading: true, error: null }); + await api.addMcpServer(name, transport); + await get().loadMcpServers(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to add MCP server', isLoading: false }); + } + }, + + connectMcpServer: async (serverId: string) => { + try { + set({ isLoading: true, error: null }); + await api.connectMcpServer(serverId); + await get().loadMcpServers(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to connect MCP server', isLoading: false }); + } + }, + + disconnectMcpServer: async (serverId: string) => { + try { + set({ isLoading: true, error: null }); + await api.disconnectMcpServer(serverId); + await get().loadMcpServers(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to disconnect MCP server', isLoading: false }); + } + }, + + removeMcpServer: async (serverId: string) => { + try { + set({ isLoading: true, error: null }); + await api.removeMcpServer(serverId); + await get().loadMcpServers(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to remove MCP server', isLoading: false }); + } + }, + + loadMcpServers: async () => { + try { + const servers = await api.listMcpServers(); + set({ mcpServers: servers }); + } catch (error: any) { + set({ error: error.message || 'Failed to load MCP servers' }); + } + }, + + sendMessage: async (message: string, signal?: AbortSignal) => { + try { + const requestId = Math.random().toString(36).substring(7); + set({ isLoading: true, error: null, currentRequestId: requestId }); + + // Get current conversation or create new one + let conversation = get().activeConversation; + if (!conversation) { + conversation = { + id: '', + messages: [], + metadata: { + startTime: new Date().toISOString(), + client: 'web-ui', + fromStt: false, + }, + llmProvider: get().config.defaultLlmProvider, + model: get().config.defaultLlmProvider === 'anthropic' ? 'claude-sonnet-4-20250514' : undefined, + mcpServers: get().mcpServers.filter(s => s.connected).map(s => s.id), + }; + } + + // Add user message + const userMessage: Message = { + role: 'user', + content: message, + toolCallsJson: null, + toolResultsJson: null, + timestamp: Date.now(), + }; + + // Update local state immediately for better UX + conversation.messages.push(userMessage); + set({ activeConversation: { ...conversation } }); + + // Check if we should use WebSocket + if (get().useWebSocket && webSocketService.isReady) { + // Send via WebSocket for progressive updates + webSocketService.sendChatMessage( + conversation.messages, + conversation.llmProvider, + conversation.model, + conversation.mcpServers, + conversation.metadata + ); + // WebSocket responses will be handled by the message handler + return; + } + + // Fallback to HTTP + // Get appropriate API key (OAuth or admin) + const apiKey = await getApiKeyForChat(conversation.llmProvider); + if (!apiKey) { + throw new Error('No valid API key available. Please add an API key or login with Claude.'); + } + + // Send to backend with abort signal support + const response = await api.chat( + apiKey, + conversation.messages, + conversation.llmProvider, + conversation.model, + conversation.mcpServers, + conversation.metadata, + signal + ); + + // Only update if this request hasn't been cancelled + if (get().currentRequestId === requestId) { + // Update conversation with response + conversation.id = response.conversationId; + + // Add all messages from the response (includes tool calls and results) + if (response.allMessages && response.allMessages.length > 0) { + conversation.messages.push(...response.allMessages); + } else { + // Fallback to just the final response if allMessages is not available + conversation.messages.push(response.response); + } + + // Update conversations list + const conversations = get().conversations; + const existingIndex = conversations.findIndex(c => c.id === conversation.id); + if (existingIndex >= 0) { + conversations[existingIndex] = conversation; + } else { + conversations.unshift(conversation); + } + + set({ + activeConversation: { ...conversation }, + conversations: [...conversations], + isLoading: false, + currentRequestId: null + }); + } + } catch (error: any) { + if (error.name === 'AbortError') { + set({ isLoading: false, currentRequestId: null }); + } else { + set({ + error: error.message || 'Failed to send message', + isLoading: false, + currentRequestId: null + }); + } + } + }, + + cancelRequest: async () => { + set({ currentRequestId: null, isLoading: false }); + // TODO: Send cancel request to backend if needed + }, + + clearActiveConversation: () => { + set({ activeConversation: null }); + }, + + loadConversations: async (client?: string, limit?: number) => { + try { + const conversations = await api.listConversations(client, limit); + set({ conversations }); + } catch (error: any) { + set({ error: error.message || 'Failed to load conversations' }); + } + }, + + loadConversation: async (id: string) => { + try { + const conversation = await api.getConversation(id); + set({ activeConversation: conversation }); + } catch (error: any) { + set({ error: error.message || 'Failed to load conversation' }); + } + }, + + loadConfig: async () => { + try { + const config = await api.getConfig(); + set({ config }); + } catch (error: any) { + set({ error: error.message || 'Failed to load config' }); + } + }, + + updateConfig: async (config: Partial) => { + try { + set({ isLoading: true, error: null }); + await api.updateConfig(config); + await get().loadConfig(); + set({ isLoading: false }); + } catch (error: any) { + set({ error: error.message || 'Failed to update config', isLoading: false }); + } + }, + + clearError: () => set({ error: null }), + + toggleWebSocket: async () => { + const newState = !get().useWebSocket; + set({ useWebSocket: newState }); + + if (newState) { + await get().connectWebSocket(); + } else { + get().disconnectWebSocket(); + } + }, + + connectWebSocket: async () => { + try { + // Determine WebSocket URL based on current location and BASE_URL + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const host = window.location.host; + const baseUrl = import.meta.env.BASE_URL || '/'; + const wsPath = baseUrl.endsWith('/') ? `${baseUrl}ws` : `${baseUrl}/ws`; + const wsUrl = `${protocol}//${host}${wsPath}`; + + console.log('Connecting to WebSocket at:', wsUrl); + await webSocketService.connect(wsUrl); + + // Set up message handler for progressive updates + webSocketService.addMessageHandler((message: WsServerMessage) => { + const state = get(); + + switch (message.type) { + case 'message': + // Progressive message update from tool loop + if (state.activeConversation && message.message) { + const updatedConversation = { ...state.activeConversation }; + updatedConversation.messages.push(message.message); + set({ activeConversation: updatedConversation }); + } + break; + + case 'chat_complete': + // Final response received + if (state.activeConversation && message.payload) { + const updatedConversation = { ...state.activeConversation }; + updatedConversation.id = message.payload.conversationId; + + // Update conversations list + const conversations = [...state.conversations]; + const existingIndex = conversations.findIndex(c => c.id === updatedConversation.id); + if (existingIndex >= 0) { + conversations[existingIndex] = updatedConversation; + } else { + conversations.unshift(updatedConversation); + } + + set({ + activeConversation: updatedConversation, + conversations, + isLoading: false, + currentRequestId: null + }); + } + break; + + case 'error': + set({ + error: message.error || 'WebSocket error occurred', + isLoading: false, + currentRequestId: null + }); + break; + } + }); + + // Authenticate with appropriate API key (OAuth or admin) + const provider = get().config.defaultLlmProvider; + const authKey = await getApiKeyForChat(provider); + if (!authKey) { + console.error('No API key available for WebSocket auth'); + throw new Error('No valid API key available. Please add an API key or login with Claude.'); + } + await webSocketService.authenticate(authKey); + + set({ wsConnected: true }); + } catch (error: any) { + console.error('Failed to connect WebSocket:', error); + set({ + wsConnected: false, + useWebSocket: false, + error: 'Failed to connect WebSocket. Falling back to HTTP.' + }); + } + }, + + disconnectWebSocket: () => { + webSocketService.disconnect(); + set({ wsConnected: false }); + }, +})); \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/types/caller-utils.d.ts b/hyperdrive/packages/spider/ui/src/types/caller-utils.d.ts new file mode 100644 index 000000000..9d01ecbe5 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/types/caller-utils.d.ts @@ -0,0 +1,16 @@ +declare module '../../target/ui/caller-utils' { + export function setApiKey(requestBody: string): Promise; + export function listApiKeys(requestBody: string): Promise; + export function removeApiKey(provider: string): Promise; + export function createSpiderKey(requestBody: string): Promise; + export function listSpiderKeys(requestBody: string): Promise; + export function revokeSpiderKey(keyId: string): Promise; + export function addMcpServer(requestBody: string): Promise; + export function listMcpServers(requestBody: string): Promise; + export function connectMcpServer(serverId: string): Promise; + export function listConversations(requestBody: string): Promise; + export function getConversation(conversationId: string): Promise; + export function getConfig(requestBody: string): Promise; + export function updateConfig(requestBody: string): Promise; + export function chat(requestBody: string): Promise; +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/types/global.ts b/hyperdrive/packages/spider/ui/src/types/global.ts new file mode 100644 index 000000000..f4b35ac55 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/types/global.ts @@ -0,0 +1,32 @@ +// Global type definitions for Hyperware environment + +// The window.our object is provided by the /our.js script +// It contains the node and process identity +declare global { + interface Window { + our?: { + node: string; // e.g., "alice.os" + process: string; // e.g., "skeleton-app:skeleton-app:skeleton.os" + }; + } +} + +// Base URL for API calls +// In production, this is empty (same origin) +// In development, you might proxy to your local node +export const BASE_URL = ''; + +// Helper to check if we're in a Hyperware environment +export const isHyperwareEnvironment = (): boolean => { + return typeof window !== 'undefined' && window.our !== undefined; +}; + +// Get the current node identity +export const getNodeId = (): string | null => { + return window.our?.node || null; +}; + +// Get the current process identity +export const getProcessId = (): string | null => { + return window.our?.process || null; +}; \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/types/websocket.ts b/hyperdrive/packages/spider/ui/src/types/websocket.ts new file mode 100644 index 000000000..f7891ebe5 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/types/websocket.ts @@ -0,0 +1,86 @@ +// WebSocket message types for Spider chat + +import { Message, ConversationMetadata, ChatResponse } from '@caller-utils'; + +// Client -> Server messages +export type WsClientMessage = + | AuthMessage + | ChatMessage + | CancelMessage + | PingMessage; + +export interface AuthMessage { + type: 'auth'; + apiKey: string; +} + +export interface ChatMessage { + type: 'chat'; + payload: { + messages: Message[]; + llmProvider?: string; + mcpServers?: string[]; + metadata?: ConversationMetadata; + }; +} + +export interface CancelMessage { + type: 'cancel'; +} + +export interface PingMessage { + type: 'ping'; +} + +// Server -> Client messages +export type WsServerMessage = + | AuthSuccessMessage + | AuthErrorMessage + | StatusMessage + | StreamMessage + | MessageUpdate + | ChatCompleteMessage + | ErrorMessage + | PongMessage; + +export interface AuthSuccessMessage { + type: 'auth_success'; + message: string; +} + +export interface AuthErrorMessage { + type: 'auth_error'; + error: string; +} + +export interface StatusMessage { + type: 'status'; + status: string; + message?: string; +} + +export interface StreamMessage { + type: 'stream'; + iteration: number; + message: string; + tool_calls?: string; +} + +export interface MessageUpdate { + type: 'message'; + message: Message; +} + +export interface ChatCompleteMessage { + type: 'chat_complete'; + payload: ChatResponse; +} + +export interface ErrorMessage { + type: 'error'; + error: string; +} + +export interface PongMessage { + type: 'pong'; +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/src/utils/api.ts b/hyperdrive/packages/spider/ui/src/utils/api.ts new file mode 100644 index 000000000..834aa2fdc --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/utils/api.ts @@ -0,0 +1,175 @@ +// Import the generated functions and types directly +import { + set_api_key as _setApiKey, + list_api_keys as _listApiKeys, + remove_api_key as _removeApiKey, + create_spider_key as _createSpiderKey, + list_spider_keys as _listSpiderKeys, + revoke_spider_key as _revokeSpiderKey, + add_mcp_server as _addMcpServer, + list_mcp_servers as _listMcpServers, + connect_mcp_server as _connectMcpServer, + disconnect_mcp_server as _disconnectMcpServer, + remove_mcp_server as _removeMcpServer, + list_conversations as _listConversations, + get_conversation as _getConversation, + get_config as _getConfig, + update_config as _updateConfig, + chat as _chat, + get_admin_key as _getAdminKey, + type ApiKeyInfo, + type SpiderApiKey, + type McpServer, + type Conversation, + type ConfigResponse, + type ChatResponse, + type Message, + type ConversationMetadata, + type TransportConfig, +} from '@caller-utils'; + +export async function getAdminKey(): Promise { + return _getAdminKey(); +} + +export async function setApiKey(provider: string, key: string) { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _setApiKey({ provider, key, authKey }); +} + +export async function listApiKeys(): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _listApiKeys({ authKey }); +} + +export async function removeApiKey(provider: string) { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _removeApiKey({ provider, authKey }); +} + +export async function createSpiderKey(name: string, permissions: string[]): Promise { + const adminKey = (window as any).__spiderAdminKey; + if (!adminKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _createSpiderKey({ name, permissions, adminKey }); +} + +export async function listSpiderKeys(): Promise { + const adminKey = (window as any).__spiderAdminKey; + if (!adminKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _listSpiderKeys({ adminKey }); +} + +export async function revokeSpiderKey(key: string) { + const adminKey = (window as any).__spiderAdminKey; + if (!adminKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _revokeSpiderKey({ keyId: key, adminKey }); +} + +export async function addMcpServer(name: string, transport: TransportConfig): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _addMcpServer({ name, transport, authKey }); +} + +export async function listMcpServers(): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _listMcpServers({ authKey }); +} + +export async function connectMcpServer(serverId: string) { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _connectMcpServer({ serverId, authKey }); +} + +export async function disconnectMcpServer(serverId: string) { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _disconnectMcpServer({ serverId, authKey }); +} + +export async function removeMcpServer(serverId: string) { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _removeMcpServer({ serverId, authKey }); +} + +export async function listConversations(client?: string, limit?: number, offset?: number): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _listConversations({ + client: client || null, + limit: limit || null, + offset: offset || null, + authKey + }); +} + +export async function getConversation(conversationId: string): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _getConversation({ conversationId, authKey }); +} + +export async function getConfig(): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _getConfig({ authKey }); +} + +export async function updateConfig(config: Partial): Promise { + const authKey = (window as any).__spiderAdminKey; + if (!authKey) { + throw new Error('Admin key not available. Please refresh the page.'); + } + return _updateConfig({ + defaultLlmProvider: config.defaultLlmProvider || null, + maxTokens: config.maxTokens || null, + temperature: config.temperature || null, + authKey + }); +} + +export async function chat(apiKey: string, messages: Message[], llmProvider?: string, model?: string, mcpServers?: string[], metadata?: ConversationMetadata, signal?: AbortSignal): Promise { + // TODO: Pass signal to the underlying API call when supported + return _chat({ + apiKey, + messages, + llmProvider: llmProvider || null, + model: model || null, + mcpServers: mcpServers || null, + metadata: metadata || null + }); +} diff --git a/hyperdrive/packages/spider/ui/src/vite-env.d.ts b/hyperdrive/packages/spider/ui/src/vite-env.d.ts new file mode 100644 index 000000000..151aa6856 --- /dev/null +++ b/hyperdrive/packages/spider/ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/tsconfig.json b/hyperdrive/packages/spider/ui/tsconfig.json new file mode 100644 index 000000000..d07dc20e1 --- /dev/null +++ b/hyperdrive/packages/spider/ui/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + + /* Path mappings */ + "baseUrl": ".", + "paths": { + "@caller-utils": ["../target/ui/caller-utils"] + } + }, + "include": ["src"], + "exclude": ["../target/ui/caller-utils.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/tsconfig.node.json b/hyperdrive/packages/spider/ui/tsconfig.node.json new file mode 100644 index 000000000..4eb43d054 --- /dev/null +++ b/hyperdrive/packages/spider/ui/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/hyperdrive/packages/spider/ui/vite.config.ts b/hyperdrive/packages/spider/ui/vite.config.ts new file mode 100644 index 000000000..1c9b21c6b --- /dev/null +++ b/hyperdrive/packages/spider/ui/vite.config.ts @@ -0,0 +1,74 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'path' + +/* +If you are developing a UI outside of a Hyperware project, +comment out the following 2 lines: +*/ +import manifest from '../pkg/manifest.json' +import metadata from '../metadata.json' + +/* +IMPORTANT: +This must match the process name from pkg/manifest.json + pkg/metadata.json +The format is "/" + "process_name:package_name:publisher_node" +*/ +const BASE_URL = `/${manifest[0].process_name}:${metadata.properties.package_name}:${metadata.properties.publisher}`; + +// This is the proxy URL, it must match the node you are developing against +const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080').replace('localhost', '127.0.0.1'); + +console.log('process.env.VITE_NODE_URL', process.env.VITE_NODE_URL, PROXY_URL); + +export default defineConfig({ + plugins: [react()], + base: BASE_URL, + resolve: { + alias: { + '@caller-utils': path.resolve(__dirname, '../target/ui/caller-utils.ts') + } + }, + build: { + rollupOptions: { + external: ['/our.js'] + } + }, + server: { + open: true, + proxy: { + '/our': { + target: PROXY_URL, + changeOrigin: true, + }, + [`${BASE_URL}/our.js`]: { + target: PROXY_URL, + changeOrigin: true, + rewrite: (path) => path.replace(BASE_URL, ''), + }, + // This route will match all other HTTP requests to the backend + [`^${BASE_URL}/(?!(@vite/client|src/.*|node_modules/.*|@react-refresh|$))`]: { + target: PROXY_URL, + changeOrigin: true, + ws: true, // Enable WebSocket proxy + }, + // '/example': { + // target: PROXY_URL, + // changeOrigin: true, + // rewrite: (path) => path.replace(BASE_URL, ''), + // // This is only for debugging purposes + // configure: (proxy, _options) => { + // proxy.on('error', (err, _req, _res) => { + // console.log('proxy error', err); + // }); + // proxy.on('proxyReq', (proxyReq, req, _res) => { + // console.log('Sending Request to the Target:', req.method, req.url); + // }); + // proxy.on('proxyRes', (proxyRes, req, _res) => { + // console.log('Received Response from the Target:', proxyRes.statusCode, req.url); + // }); + // }, + // }, + } + } +}); diff --git a/hyperdrive/packages/terminal/Cargo.lock b/hyperdrive/packages/terminal/Cargo.lock index 2860ebbc2..19afe4693 100644 --- a/hyperdrive/packages/terminal/Cargo.lock +++ b/hyperdrive/packages/terminal/Cargo.lock @@ -1033,6 +1033,17 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clear-state" +version = "0.1.0" +dependencies = [ + "anyhow", + "hyperware_process_lib", + "serde", + "serde_json", + "wit-bindgen", +] + [[package]] name = "colorchoice" version = "1.0.3" diff --git a/hyperdrive/packages/terminal/Cargo.toml b/hyperdrive/packages/terminal/Cargo.toml index 7a6d7f5c5..3a57261ec 100644 --- a/hyperdrive/packages/terminal/Cargo.toml +++ b/hyperdrive/packages/terminal/Cargo.toml @@ -5,6 +5,7 @@ members = [ "add-rpcurl-provider", "alias", "cat", + "clear-state", "echo", "get-providers", "help", diff --git a/hyperdrive/packages/terminal/clear-state/Cargo.toml b/hyperdrive/packages/terminal/clear-state/Cargo.toml new file mode 100644 index 000000000..580f64180 --- /dev/null +++ b/hyperdrive/packages/terminal/clear-state/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "clear-state" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +anyhow = "1.0" +hyperware_process_lib = "2.1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +wit-bindgen = "0.42.1" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "hyperware:process" diff --git a/hyperdrive/packages/terminal/clear-state/src/lib.rs b/hyperdrive/packages/terminal/clear-state/src/lib.rs new file mode 100644 index 000000000..5f0a63ccc --- /dev/null +++ b/hyperdrive/packages/terminal/clear-state/src/lib.rs @@ -0,0 +1,33 @@ +use hyperware_process_lib::{ + kernel_types::StateAction, println, script, Address, ProcessId, Request, +}; + +wit_bindgen::generate!({ + path: "../target/wit", + world: "process-v1", +}); + +const USAGE: &str = "\x1b[1mUsage:\x1b[0m clear-state "; +const STATE_PROCESS_ID: (&str, &str, &str) = ("state", "distro", "sys"); + +script!(init); +fn init(_our: Address, args: String) -> String { + if args.is_empty() { + return format!("Clear the state of the given process.\n{USAGE}"); + } + + let Ok(ref process_id) = args.parse::() else { + return format!( + "'{args}' is not a process-id (e.g. `process-name:package-name:publisher.os`)\n{USAGE}" + ); + }; + + let Ok(Ok(_)) = Request::to(("our", STATE_PROCESS_ID)) + .body(serde_json::to_vec(&StateAction::DeleteState(process_id.clone())).unwrap()) + .send_and_await_response(5) + else { + return format!("Failed to delete state for process {process_id}"); + }; + + format!("Deleted state of process {process_id}") +} diff --git a/hyperdrive/packages/terminal/help/src/lib.rs b/hyperdrive/packages/terminal/help/src/lib.rs index 999e32d93..3b26ff75e 100644 --- a/hyperdrive/packages/terminal/help/src/lib.rs +++ b/hyperdrive/packages/terminal/help/src/lib.rs @@ -5,11 +5,12 @@ wit_bindgen::generate!({ world: "process-v1", }); -const HELP_MESSAGES: [[&str; 2]; 15] = [ +const HELP_MESSAGES: [[&str; 2]; 16] = [ ["add-node-provider", "\n\x1b[1madd-node-provider\x1b[0m [--trusted ]: add a node provider to the providers configuration.\n - Examples:\n \x1b[1madd-node-provider 8453 other-node.hypr abc123pubkey 192.168.1.1 9000\x1b[0m (defaults to trusted=false)\n \x1b[1madd-node-provider 1 other-node.hypr abc123pubkey 192.168.1.1 9000 --trusted true\x1b[0m"], ["add-rpcurl-provider", "\n\x1b[1madd-rpcurl-provider\x1b[0m [--chain-id ] [--trusted ] [--auth-type --auth-value ]: add an RPC URL provider to the providers configuration.\n - Examples:\n \x1b[1madd-rpcurl-provider wss://base-mainnet.infura.io/v3/your-key\x1b[0m (defaults to chain-id=8453, trusted=true)\n \x1b[1madd-rpcurl-provider wss://mainnet.infura.io/v3/your-key --chain-id 1\x1b[0m\n \x1b[1madd-rpcurl-provider wss://base-mainnet.infura.io/ws/v3/your-key --trusted false\x1b[0m\n \x1b[1madd-rpcurl-provider wss://rpc.example.com --auth-type bearer --auth-value your-token\x1b[0m"], ["alias", "\n\x1b[1malias\x1b[0m : create an alias for a script.\n - Example: \x1b[1malias get-block get-block:hns-indexer:sys\x1b[0m\n - note: all of these listed commands are just default aliases for terminal scripts."], ["cat", "\n\x1b[1mcat\x1b[0m : print the contents of a file in the terminal.\n - Example: \x1b[1mcat /terminal:sys/pkg/scripts.json\x1b[0m"], + ["clear-state", "\n\x1b[1mclear-state\x1b[0m : clear the state of the given process."], ["echo", "\n\x1b[1mecho\x1b[0m : print text to the terminal.\n - Example: \x1b[1mecho foo\x1b[0m"], ["get-providers", "\n\x1b[1mget-providers\x1b[0m: display the providers configuration."], ["hi", "\n\x1b[1mhi\x1b[0m : send a text message to another node's command line.\n - Example: \x1b[1mhi mothu.hypr hello world\x1b[0m"], diff --git a/hyperdrive/packages/terminal/pkg/manifest.json b/hyperdrive/packages/terminal/pkg/manifest.json index 8238109e0..4b98add19 100644 --- a/hyperdrive/packages/terminal/pkg/manifest.json +++ b/hyperdrive/packages/terminal/pkg/manifest.json @@ -30,6 +30,7 @@ "net:distro:sys", "sign:sign:sys", "sqlite:distro:sys", + "state:distro:sys", "timer:distro:sys", "vfs:distro:sys", { diff --git a/hyperdrive/packages/terminal/pkg/scripts.json b/hyperdrive/packages/terminal/pkg/scripts.json index 1b2c17825..6d12decba 100644 --- a/hyperdrive/packages/terminal/pkg/scripts.json +++ b/hyperdrive/packages/terminal/pkg/scripts.json @@ -61,6 +61,18 @@ "grant_capabilities": [], "wit_version": 1 }, + "clear-state.wasm": { + "root": false, + "public": false, + "request_networking": false, + "request_capabilities": [ + "state:distro:sys" + ], + "grant_capabilities": [ + "state:distro:sys" + ], + "wit_version": 1 + }, "echo.wasm": { "root": false, "public": false, diff --git a/hyperdrive/packages/terminal/terminal/src/lib.rs b/hyperdrive/packages/terminal/terminal/src/lib.rs index babd24ceb..c1831f6ae 100644 --- a/hyperdrive/packages/terminal/terminal/src/lib.rs +++ b/hyperdrive/packages/terminal/terminal/src/lib.rs @@ -76,6 +76,10 @@ impl VersionedState { "cat".to_string(), ProcessId::new(Some("cat"), "terminal", "sys"), ), + ( + "clear-state".to_string(), + ProcessId::new(Some("clear-state"), "terminal", "sys"), + ), ( "echo".to_string(), ProcessId::new(Some("echo"), "terminal", "sys"), diff --git a/hyperdrive/src/eth/mod.rs b/hyperdrive/src/eth/mod.rs index af96d9326..e8d67acb1 100644 --- a/hyperdrive/src/eth/mod.rs +++ b/hyperdrive/src/eth/mod.rs @@ -41,6 +41,16 @@ struct ActiveProviders { pub nodes: Vec, } +#[derive(Debug, Clone, Default)] +struct MethodFailures { + /// Category 1: Simple methods that failed (retry until success) + pub failed_methods: HashSet, + /// Category 2: eth_sendRawTransaction failures (clear after 60m) + pub send_raw_tx_failed: Option, + /// Category 3: eth_getLogs max failed block range + pub failed_logs_range: Option, +} + #[derive(Debug, Clone)] struct UrlProvider { pub trusted: bool, @@ -48,6 +58,12 @@ struct UrlProvider { /// a list, in case we build multiple providers for the same url pub pubsub: Vec>, pub auth: Option, + /// whether this provider was online as of last check + pub online: bool, + /// last time we checked if offline provider is back online + pub last_health_check: Option, + /// method-specific failures + pub method_failures: MethodFailures, } #[derive(Debug, Clone)] @@ -60,6 +76,112 @@ struct NodeProvider { /// the HNS update that describes this node provider /// kept so we can re-serialize to SavedConfigs pub hns_update: HnsUpdate, + /// whether this provider was online as of last check + pub online: bool, + /// last time we checked if offline provider is back online + pub last_health_check: Option, + /// method-specific failures + pub method_failures: MethodFailures, +} + +impl MethodFailures { + /// Check if a method should be skipped for this provider + fn should_skip_method(&self, method: &str, params: &serde_json::Value) -> bool { + // Check Category 2: eth_sendRawTransaction with 60m timeout + if method == "eth_sendRawTransaction" { + if let Some(failed_at) = self.send_raw_tx_failed { + if failed_at.elapsed() < Duration::from_secs(3600) { + return true; + } + } + } + + // Check Category 3: eth_getLogs with block range + if method == "eth_getLogs" { + if let Some(block_range) = extract_block_range(params) { + if let Some(failed_range) = self.failed_logs_range { + // Skip if current range is >= failed range + if block_range >= failed_range { + return true; + } + } + } + } + + // Check Category 1: Simple methods + self.failed_methods.contains(method) + } + + /// Mark a method as failed + fn mark_method_failed(&mut self, method: &str, params: &serde_json::Value) { + match method { + "eth_sendRawTransaction" => { + // Category 2: Mark with timestamp + self.send_raw_tx_failed = Some(Instant::now()); + } + "eth_getLogs" => { + // Category 3: Update minimum failed block range + if let Some(block_range) = extract_block_range(params) { + // Update to the minimum of current and new failed range + self.failed_logs_range = Some(match self.failed_logs_range { + Some(existing) => existing.min(block_range), + None => block_range, + }); + } + } + _ => { + // Category 1: Simple methods + self.failed_methods.insert(method.to_string()); + } + } + } + + /// Clear a method failure (when it succeeds) + fn clear_method_failure(&mut self, method: &str) { + match method { + "eth_sendRawTransaction" => { + self.send_raw_tx_failed = None; + } + "eth_getLogs" => { + // Clear the failed range when getLogs succeeds + self.failed_logs_range = None; + } + _ => { + self.failed_methods.remove(method); + } + } + } +} + +/// Extract block range from eth_getLogs params +fn extract_block_range(params: &serde_json::Value) -> Option { + if let Some(arr) = params.as_array() { + if let Some(obj) = arr.first().and_then(|v| v.as_object()) { + let from_block = parse_block_number(obj.get("fromBlock")?)?; + let to_block = parse_block_number(obj.get("toBlock")?)?; + return Some(to_block.saturating_sub(from_block)); + } + } + None +} + +/// Parse block number from various formats (hex string, number, "latest", etc.) +fn parse_block_number(value: &serde_json::Value) -> Option { + match value { + serde_json::Value::String(s) => { + if s.starts_with("0x") { + u64::from_str_radix(&s[2..], 16).ok() + } else { + match s.as_str() { + "latest" | "pending" => Some(u64::MAX), + "earliest" => Some(0), + _ => s.parse().ok(), + } + } + } + serde_json::Value::Number(n) => n.as_u64(), + _ => None, + } } impl ActiveProviders { @@ -76,6 +198,9 @@ impl ActiveProviders { url: url.clone(), pubsub: vec![], auth: auth.clone(), + online: true, // Default to online + last_health_check: None, + method_failures: MethodFailures::default(), }; self.urls.insert(0, url_provider); } @@ -90,6 +215,9 @@ impl ActiveProviders { trusted: new.trusted, usable: true, // Default to usable hns_update: hns_update.clone(), + online: true, // Default to online + last_health_check: None, + method_failures: MethodFailures::default(), }; self.nodes.insert(0, node_provider); } @@ -646,6 +774,7 @@ async fn handle_eth_action( ð_action, &providers, &mut receiver, + &response_channels, &print_tx, &mut request_cache, ), @@ -665,6 +794,7 @@ async fn handle_eth_action( ð_action, &providers, &mut receiver, + &response_channels, &print_tx, &mut request_cache, ), @@ -730,6 +860,7 @@ async fn fulfill_request( eth_action: &EthAction, providers: &Providers, remote_request_receiver: &mut ProcessMessageReceiver, + response_channels: &ResponseChannels, print_tx: &PrintSender, request_cache: &mut RequestCache, ) -> EthResponse { @@ -767,8 +898,38 @@ async fn fulfill_request( // then if we have none or they all fail, go to node providers. // finally, if no provider works, return an error. - // bump the successful provider to the front of the list for future requests + // Track all errors for comprehensive error reporting if all fail + let mut all_errors = Vec::new(); + // Keep track of the last valid RPC error response (e.g., rate limits) + let mut last_rpc_error: Option = None; + + // Try URL providers, respecting their order but skipping offline ones for mut url_provider in urls.into_iter() { + // Skip offline providers + if !url_provider.online { + verbose_print( + print_tx, + &format!("eth: skipping offline url provider {}", url_provider.url), + ) + .await; + continue; + } + + // Check method-specific failures + if url_provider + .method_failures + .should_skip_method(method, params) + { + verbose_print( + print_tx, + &format!( + "eth: skipping url provider {} due to previous {} failure", + url_provider.url, method + ), + ) + .await; + continue; + } let (pubsub, newly_activated) = match url_provider.pubsub.first() { Some(pubsub) => (pubsub, false), None => { @@ -791,28 +952,19 @@ async fn fulfill_request( }; match pubsub.raw_request(method.into(), params).await { Ok(value) => { - let mut is_replacement_successful = true; + // Provider succeeded - clear any method failures and update pubsub if needed providers.entry(chain_id.clone()).and_modify(|aps| { - let Some(index) = find_index( - &aps.urls.iter().map(|u| u.url.as_str()).collect(), - &url_provider.url, - ) else { - is_replacement_successful = false; - return (); - }; - let mut old_provider = aps.urls.remove(index); - if newly_activated { - old_provider.pubsub.push(url_provider.pubsub.pop().unwrap()); + if let Some(provider) = aps.urls.iter_mut().find(|p| p.url == url_provider.url) + { + // Clear method failure since it succeeded + provider.method_failures.clear_method_failure(method); + + if newly_activated { + provider.pubsub.push(url_provider.pubsub.pop().unwrap()); + } } - aps.urls.insert(0, old_provider); }); - if !is_replacement_successful { - verbose_print( - print_tx, - &format!("eth: unexpectedly couldn't find provider to be modified"), - ) - .await; - } + let response = EthResponse::Response(value); let mut request_cache = request_cache.lock().await; if request_cache.len() >= MAX_REQUEST_CACHE_LEN { @@ -831,34 +983,101 @@ async fn fulfill_request( ), ) .await; - // if rpc_error is of type ErrResponse, return to user! - if let RpcError::ErrorResp(err) = rpc_error { - let err_value = - serde_json::to_value(err).unwrap_or_else(|_| serde_json::Value::Null); - return EthResponse::Err(EthError::RpcError(err_value)); - } - if !newly_activated { - // this provider failed and needs to be reset - let mut is_reset_successful = true; + + // Track the error + all_errors.push((url_provider.url.clone(), format!("{:?}", rpc_error))); + + // Store RPC error responses for later if all providers fail + let is_rpc_error_resp = if let RpcError::ErrorResp(err) = &rpc_error { + last_rpc_error = + Some(serde_json::to_value(err).unwrap_or_else(|_| serde_json::Value::Null)); + true + } else { + false + }; + + // Determine what to mark as failed + if is_rpc_error_resp { + // Valid RPC error response - mark the specific method as failed + let mut should_spawn_retry = false; providers.entry(chain_id.clone()).and_modify(|aps| { - let Some(index) = find_index( - &aps.urls.iter().map(|u| u.url.as_str()).collect(), - &url_provider.url, - ) else { - is_reset_successful = false; - return (); + let Some(provider) = + aps.urls.iter_mut().find(|p| p.url == url_provider.url) + else { + return; }; - let mut url = aps.urls.remove(index); - url.pubsub = vec![]; - aps.urls.insert(index, url); + // Check if this method wasn't already marked as failed + if !provider.method_failures.should_skip_method(method, params) { + provider.method_failures.mark_method_failed(method, params); + should_spawn_retry = true; + } }); - if !is_reset_successful { + + // Spawn method retry task if this is a new failure + if should_spawn_retry && method != "eth_sendRawTransaction" { + crate::eth::utils::spawn_method_retry_for_url_provider( + providers.clone(), + chain_id.clone(), + url_provider.url.clone(), + method.to_string(), + params.clone(), + print_tx.clone(), + ); verbose_print( print_tx, - &format!("eth: unexpectedly couldn't find provider to be modified"), + &format!( + "eth: spawned method retry for {} on {}", + method, url_provider.url + ), ) .await; } + // Continue to next provider without marking offline + continue; + } + + // Transport/connection error - mark the provider as offline and spawn health check + let mut spawn_health_check = false; + providers.entry(chain_id.clone()).and_modify(|aps| { + let Some(index) = find_index( + &aps.urls.iter().map(|u| u.url.as_str()).collect(), + &url_provider.url, + ) else { + return (); + }; + let mut url = aps.urls.remove(index); + url.pubsub = vec![]; + url.online = false; + url.last_health_check = Some(Instant::now()); + + // Only spawn health check if not already running + if url.last_health_check.is_none() + || url.last_health_check.unwrap().elapsed() > Duration::from_secs(30) + { + spawn_health_check = true; + } + + aps.urls.insert(index, url); + }); + + // Spawn health check task if needed + if spawn_health_check { + use crate::eth::utils::spawn_health_check_for_url_provider; + spawn_health_check_for_url_provider( + providers.clone(), + chain_id.clone(), + url_provider.url.clone(), + print_tx.clone(), + ); + + verbose_print( + print_tx, + &format!( + "eth: spawned health check for offline provider {}", + url_provider.url + ), + ) + .await; } } } @@ -872,6 +1091,35 @@ async fn fulfill_request( aps.nodes.clone() }; for node_provider in &nodes { + // Skip offline node providers + if !node_provider.online || !node_provider.usable { + verbose_print( + print_tx, + &format!( + "eth: skipping offline/unusable node provider {}", + node_provider.hns_update.name + ), + ) + .await; + continue; + } + + // Check method-specific failures + if node_provider + .method_failures + .should_skip_method(method, params) + { + verbose_print( + print_tx, + &format!( + "eth: skipping node provider {} due to previous {} failure", + node_provider.hns_update.name, method + ), + ) + .await; + continue; + } + verbose_print( print_tx, &format!( @@ -890,21 +1138,143 @@ async fn fulfill_request( remote_request_receiver, ) .await; - if let EthResponse::Err(e) = response { - if let EthError::RpcMalformedResponse = e { - set_node_unusable( - &providers, - &chain_id, - &node_provider.hns_update.name, - print_tx, - ) - .await; + + if let EthResponse::Err(e) = &response { + // Track the error + all_errors.push((node_provider.hns_update.name.clone(), format!("{:?}", e))); + + // Check if it's an RPC error (method failure) vs transport error + let is_rpc_error = matches!(e, EthError::RpcError(_)); + + if is_rpc_error { + // Mark the specific method as failed + let mut should_spawn_retry = false; + providers.entry(chain_id.clone()).and_modify(|aps| { + let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_provider.hns_update.name) + else { + return; + }; + // Check if this method wasn't already marked as failed + if !provider.method_failures.should_skip_method(method, params) { + provider.method_failures.mark_method_failed(method, params); + should_spawn_retry = true; + } + }); + // Store the RPC error + if let EthError::RpcError(err_value) = e { + last_rpc_error = Some(err_value.clone()); + } + + // Spawn method retry task if this is a new failure + if should_spawn_retry && method != "eth_sendRawTransaction" { + use crate::eth::utils::spawn_method_retry_for_node_provider; + spawn_method_retry_for_node_provider( + our.to_string(), + providers.clone(), + chain_id.clone(), + node_provider.hns_update.name.clone(), + method.to_string(), + params.clone(), + send_to_loop.clone(), + response_channels.clone(), + print_tx.clone(), + ); + verbose_print( + print_tx, + &format!( + "eth: spawned method retry for {} on node {}", + method, node_provider.hns_update.name + ), + ) + .await; + } + } else { + // Transport/timeout error - mark node as offline and spawn health check + let mut spawn_health_check = false; + providers.entry(chain_id.clone()).and_modify(|aps| { + let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_provider.hns_update.name) + else { + return; + }; + provider.online = false; + provider.usable = false; + + // Only spawn health check if not recently checked + if provider.last_health_check.is_none() + || provider.last_health_check.unwrap().elapsed() > Duration::from_secs(30) + { + spawn_health_check = true; + provider.last_health_check = Some(Instant::now()); + } + }); + + // Spawn health check task if needed + if spawn_health_check { + use crate::eth::utils::spawn_health_check_for_node_provider; + spawn_health_check_for_node_provider( + our.to_string(), + providers.clone(), + chain_id.clone(), + node_provider.hns_update.name.clone(), + send_to_loop.clone(), + response_channels.clone(), + print_tx.clone(), + ); + + verbose_print( + print_tx, + &format!( + "eth: spawned health check for offline node provider {}", + node_provider.hns_update.name + ), + ) + .await; + } } + // Continue trying other providers instead of returning the error + continue; } else { + // Success! Clear method failure and return the response + providers.entry(chain_id.clone()).and_modify(|aps| { + if let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_provider.hns_update.name) + { + provider.method_failures.clear_method_failure(method); + } + }); return response; } } - EthResponse::Err(EthError::NoRpcForChain) + + // All providers failed, return comprehensive error + if all_errors.is_empty() { + EthResponse::Err(EthError::NoRpcForChain) + } else { + verbose_print( + print_tx, + &format!( + "eth: all providers failed for chain {}: {:?}", + chain_id, all_errors + ), + ) + .await; + + // If we have a valid RPC error response from any provider, return that + // This gives the user more specific information about why the request failed + if let Some(rpc_error) = last_rpc_error { + EthResponse::Err(EthError::RpcError(rpc_error)) + } else { + EthResponse::Err(EthError::NoRpcForChain) + } + } } /// take an EthAction and send it to a node provider, then await a response. diff --git a/hyperdrive/src/eth/utils.rs b/hyperdrive/src/eth/utils.rs index bfb230e6a..6210211b8 100644 --- a/hyperdrive/src/eth/utils.rs +++ b/hyperdrive/src/eth/utils.rs @@ -1,10 +1,11 @@ -use crate::eth::{Providers, UrlProvider}; -use alloy::providers::ProviderBuilder; +use crate::eth::{Providers, ResponseChannels, UrlProvider}; +use alloy::providers::{Provider, ProviderBuilder}; use alloy::rpc::client::WsConnect; use anyhow::Result; use lib::types::core::*; use lib::types::eth::*; use serde::Serialize; +use std::time::{Duration, Instant}; use url::Url; pub async fn activate_url_provider(provider: &mut UrlProvider) -> Result<()> { @@ -205,3 +206,364 @@ pub async fn set_node_unusable( } is_replacement_successful } + +/// Check if an offline provider is back online by sending eth_blockNumber +pub async fn check_url_provider_health(provider: &mut UrlProvider) -> bool { + // First try to activate the provider if not already activated + if provider.pubsub.is_empty() { + if let Err(_) = activate_url_provider(provider).await { + return false; + } + } + + // Try to get the latest block number as a health check + if let Some(pubsub) = provider.pubsub.first() { + match tokio::time::timeout(Duration::from_secs(10), pubsub.get_block_number()).await { + Ok(Ok(_)) => true, + _ => { + // Provider failed, clear the connection + provider.pubsub.clear(); + false + } + } + } else { + false + } +} + +/// Spawn a health check task for an offline URL provider +pub fn spawn_health_check_for_url_provider( + providers: Providers, + chain_id: u64, + url: String, + print_tx: PrintSender, +) { + tokio::spawn(async move { + let mut backoff_mins = 1u64; + + // Double the backoff, max 60 minutes + backoff_mins = (backoff_mins * 2).min(60); + + loop { + // Wait for the backoff period + tokio::time::sleep(Duration::from_secs(backoff_mins * 60)).await; + + // Try to check health + let mut provider_online = false; + + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps.urls.iter_mut().find(|p| p.url == url) { + provider.last_health_check = Some(Instant::now()); + if check_url_provider_health(provider).await { + provider.online = true; + provider_online = true; + provider.last_health_check = Some(Instant::now()); + + verbose_print(&print_tx, &format!("eth: provider {} is back online", url)) + .await; + } + } + } + + if provider_online { + // Provider is back online, exit the health check loop + break; + } + } + }); +} + +/// Spawn a method-specific retry for URL provider +pub fn spawn_method_retry_for_url_provider( + providers: Providers, + chain_id: u64, + url: String, + method: String, + params: serde_json::Value, + print_tx: PrintSender, +) { + tokio::spawn(async move { + let mut backoff_mins = 1u64; + + // For eth_sendRawTransaction, just wait 60 minutes then clear + if method == "eth_sendRawTransaction" { + tokio::time::sleep(Duration::from_secs(3600)).await; + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps.urls.iter_mut().find(|p| p.url == url) { + provider.method_failures.send_raw_tx_failed = None; + verbose_print( + &print_tx, + &format!("eth: cleared eth_sendRawTransaction failure for {}", url), + ) + .await; + } + } + return; + } + + // For other methods, retry with exponential backoff + loop { + tokio::time::sleep(Duration::from_secs(backoff_mins * 60)).await; + + // Double the backoff, max 60 minutes + backoff_mins = (backoff_mins * 2).min(60); + + // Try to activate and test the method + let Some(mut aps) = providers.get_mut(&chain_id) else { + continue; + }; + + let Some(provider) = aps.urls.iter_mut().find(|p| p.url == url) else { + continue; + }; + + if provider.pubsub.is_empty() { + let Ok(_) = activate_url_provider(provider).await else { + continue; + }; + } + + let Some(pubsub) = provider.pubsub.first() else { + continue; + }; + + // Try the previously-failing method + let success = matches!( + tokio::time::timeout( + Duration::from_secs(10), + pubsub.raw_request::<_, serde_json::Value>( + std::borrow::Cow::Owned(method.clone()), + ¶ms + ) + ) + .await, + Ok(Ok(_)) + ); + + if success { + // Clear the method failure + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps.urls.iter_mut().find(|p| p.url == url) { + provider.method_failures.clear_method_failure(&method); + verbose_print( + &print_tx, + &format!("eth: {} now working again for {}", method, url), + ) + .await; + } + } + break; + } + } + }); +} + +/// Spawn a method-specific retry for node provider +pub fn spawn_method_retry_for_node_provider( + our: String, + providers: Providers, + chain_id: u64, + node_name: String, + method: String, + params: serde_json::Value, + send_to_loop: MessageSender, + response_channels: ResponseChannels, + print_tx: PrintSender, +) { + tokio::spawn(async move { + let mut backoff_mins = 1u64; + + // For eth_sendRawTransaction, just wait 60 minutes then clear + if method == "eth_sendRawTransaction" { + tokio::time::sleep(Duration::from_secs(3600)).await; + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_name) + { + provider.method_failures.send_raw_tx_failed = None; + verbose_print( + &print_tx, + &format!( + "eth: cleared eth_sendRawTransaction failure for node {}", + node_name + ), + ) + .await; + } + } + return; + } + + // For other methods, retry with exponential backoff + loop { + tokio::time::sleep(Duration::from_secs(backoff_mins * 60)).await; + + // Double the backoff, max 60 minutes + backoff_mins = (backoff_mins * 2).min(60); + + // Try the method via the node + let km_id = rand::random(); + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + + // Register our response channel + response_channels.insert(km_id, sender); + + // Send the actual request + kernel_message( + &our, + km_id, + Address { + node: node_name.clone(), + process: ETH_PROCESS_ID.clone(), + }, + None, + true, + Some(10), + EthAction::Request { + chain_id: chain_id, + method: method.clone(), + params: params.clone(), + }, + &send_to_loop, + ) + .await; + + // Wait for response + let success = match tokio::time::timeout(Duration::from_secs(10), receiver.recv()).await + { + Ok(Some(Ok(km))) => matches!(km.message, Message::Response(_)), + _ => false, + }; + + // Clean up response channel + response_channels.remove(&km_id); + + if success { + // Clear the method failure + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_name) + { + provider.method_failures.clear_method_failure(&method); + verbose_print( + &print_tx, + &format!("eth: {} now working again for node {}", method, node_name), + ) + .await; + } + } + break; + } + } + }); +} + +/// Spawn a health check task for an offline node provider +pub fn spawn_health_check_for_node_provider( + our: String, + providers: Providers, + chain_id: u64, + node_name: String, + send_to_loop: MessageSender, + response_channels: ResponseChannels, + print_tx: PrintSender, +) { + tokio::spawn(async move { + let mut backoff_mins = 1u64; + + loop { + // Wait for the backoff period + tokio::time::sleep(Duration::from_secs(backoff_mins * 60)).await; + + // Double the backoff, max 60 minutes + backoff_mins = (backoff_mins * 2).min(60); + + // Try to send eth_blockNumber to check health + let km_id = rand::random(); + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + + // Register our response channel + response_channels.insert(km_id, sender); + + // Send eth_blockNumber request + kernel_message( + &our, + km_id, + Address { + node: node_name.clone(), + process: ETH_PROCESS_ID.clone(), + }, + None, + true, + Some(10), + EthAction::Request { + chain_id: chain_id, + method: "eth_blockNumber".to_string(), + params: serde_json::json!([]), + }, + &send_to_loop, + ) + .await; + + // Wait for response with timeout + let provider_online = + match tokio::time::timeout(Duration::from_secs(10), receiver.recv()).await { + Ok(Some(Ok(km))) => { + // Check if we got a successful response + matches!(km.message, Message::Response(_)) + } + _ => false, + }; + + // Clean up response channel + response_channels.remove(&km_id); + + if provider_online { + // Mark the provider as online + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_name) + { + provider.online = true; + provider.usable = true; + provider.last_health_check = Some(Instant::now()); + + verbose_print( + &print_tx, + &format!("eth: node provider {} is back online", node_name), + ) + .await; + } + } + // Provider is back online, exit the health check loop + break; + } else { + // Provider is still offline, update last health check time + if let Some(mut aps) = providers.get_mut(&chain_id) { + if let Some(provider) = aps + .nodes + .iter_mut() + .find(|p| p.hns_update.name == node_name) + { + provider.last_health_check = Some(Instant::now()); + } + } + + verbose_print( + &print_tx, + &format!( + "eth: health check failed for node provider {} (backoff: {} min)", + node_name, backoff_mins, + ), + ) + .await; + } + } + }); +} diff --git a/hyperdrive/src/http/login.html b/hyperdrive/src/http/login.html index 0a3ede650..d05d29aff 100644 --- a/hyperdrive/src/http/login.html +++ b/hyperdrive/src/http/login.html @@ -143,7 +143,11 @@
-
+
+
+
+ Need help? +
+ diff --git a/hyperdrive/src/http/utils.rs b/hyperdrive/src/http/utils.rs index 7b8930cfc..016eb5c30 100644 --- a/hyperdrive/src/http/utils.rs +++ b/hyperdrive/src/http/utils.rs @@ -172,24 +172,27 @@ pub async fn handle_close_websocket( ws_senders: WebSocketSenders, channel_id: u32, ) -> bool { - let Some(got) = ws_senders.get(&channel_id) else { - return false; - }; + // in block to drop `got` reference before `.remove`: avoid deadlock + { + let Some(got) = ws_senders.get(&channel_id) else { + return false; + }; - if got.value().0 != source.process { - send_action_response( - id, - source.clone(), - send_to_loop, - Err(HttpServerError::WsChannelNotFound), - ) - .await; - return true; + if got.value().0 != source.process { + send_action_response( + id, + source.clone(), + send_to_loop, + Err(HttpServerError::WsChannelNotFound), + ) + .await; + return true; + } + + let _ = got.value().1.send(warp::ws::Message::close()).await; } - let _ = got.value().1.send(warp::ws::Message::close()).await; ws_senders.remove(&channel_id); - return false; } diff --git a/hyperdrive/src/main.rs b/hyperdrive/src/main.rs index e9bb037f4..bd087a5ba 100644 --- a/hyperdrive/src/main.rs +++ b/hyperdrive/src/main.rs @@ -23,6 +23,7 @@ mod kernel; mod keygen; mod kv; mod net; +mod notifications; #[cfg(not(feature = "simulation-mode"))] mod register; mod sol; @@ -246,6 +247,9 @@ async fn main() { // fd_manager makes sure we don't overrun the `ulimit -n`: max number of file descriptors let (fd_manager_sender, fd_manager_receiver): (MessageSender, MessageReceiver) = mpsc::channel(FD_MANAGER_CHANNEL_CAPACITY); + // notifications handles web push notifications + let (notifications_sender, notifications_receiver): (MessageSender, MessageReceiver) = + mpsc::channel(VFS_CHANNEL_CAPACITY); // terminal receives prints via this channel, all other modules send prints let (print_sender, print_receiver): (PrintSender, PrintReceiver) = mpsc::channel(TERMINAL_CHANNEL_CAPACITY); @@ -342,7 +346,7 @@ async fn main() { ), ( ProcessId::new(Some("state"), "distro", "sys"), - state_sender, + state_sender.clone(), None, false, ), @@ -364,6 +368,12 @@ async fn main() { None, false, ), + ( + ProcessId::new(Some("notifications"), "distro", "sys"), + notifications_sender, + None, + false, + ), ]; /* @@ -499,13 +509,20 @@ async fn main() { print_sender.clone(), )); tasks.spawn(vfs::vfs( - our_name_arc, + our_name_arc.clone(), kernel_message_sender.clone(), print_sender.clone(), vfs_message_receiver, caps_oracle_sender.clone(), home_directory_path.clone(), )); + tasks.spawn(notifications::notifications( + our_name_arc, + kernel_message_sender.clone(), + print_sender.clone(), + notifications_receiver, + state_sender.clone(), + )); // if a runtime task exits, try to recover it, // unless it was terminal signaling a quit diff --git a/hyperdrive/src/notifications.rs b/hyperdrive/src/notifications.rs new file mode 100644 index 000000000..34ded85ff --- /dev/null +++ b/hyperdrive/src/notifications.rs @@ -0,0 +1,1038 @@ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use lib::types::core::{ + KernelMessage, LazyLoadBlob, Message, MessageReceiver, MessageSender, PrintSender, Printout, + ProcessId, Request, Response, NOTIFICATIONS_PROCESS_ID, +}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::RwLock; +use web_push::WebPushClient; +use web_push::{ + ContentEncoding, IsahcWebPushClient, SubscriptionInfo, VapidSignatureBuilder, + WebPushMessageBuilder, +}; + +// Import our types from lib +use lib::core::StateAction; +use lib::notifications::{ + NotificationsAction, NotificationsError, NotificationsResponse, PushSubscription, +}; + +/// VAPID keys for web push notifications +#[derive(Serialize, Deserialize, Clone)] +pub struct VapidKeys { + pub public_key: String, + pub private_key: String, +} + +impl VapidKeys { + /// Generate a new pair of VAPID keys + pub fn generate() -> Result { + // Use a simple method to generate compatible keys + // Generate random bytes for private key (32 bytes for P-256) + let mut private_key_bytes = [0u8; 32]; + use rand::RngCore; + rand::thread_rng().fill_bytes(&mut private_key_bytes); + + // Use p256 crate to generate proper keys + use p256::ecdsa::SigningKey; + + let signing_key = SigningKey::from_bytes(&private_key_bytes.into()).map_err(|e| { + NotificationsError::KeyGenerationError { + error: format!("Failed to create signing key: {:?}", e), + } + })?; + + let verifying_key = signing_key.verifying_key(); + let public_key_point = verifying_key.to_encoded_point(false); // false = uncompressed + let public_key_bytes = public_key_point.as_bytes(); + + if public_key_bytes.len() != 65 || public_key_bytes[0] != 0x04 { + return Err(NotificationsError::KeyGenerationError { + error: format!( + "Invalid public key format: len={}, first_byte=0x{:02x}", + public_key_bytes.len(), + if public_key_bytes.len() > 0 { + public_key_bytes[0] + } else { + 0 + } + ), + }); + } + + // Encode keys for storage + let public_key = URL_SAFE_NO_PAD.encode(public_key_bytes); + let private_key = URL_SAFE_NO_PAD.encode(&private_key_bytes); + + // Key generation logging moved to caller with send_to_terminal access + + Ok(VapidKeys { + public_key, + private_key, + }) + } +} + +use std::collections::VecDeque; + +#[derive(Clone)] +pub struct QueuedNotification { + title: String, + body: String, + icon: Option, + data: Option, +} + +pub struct NotificationsState { + vapid_keys: Option, + subscriptions: Vec, + last_push_timestamp: Option, + notification_queue: VecDeque, + queue_processor_handle: Option>, +} + +pub async fn notifications( + our_node: Arc, + send_to_loop: MessageSender, + send_to_terminal: PrintSender, + mut recv_notifications: MessageReceiver, + send_to_state: MessageSender, +) -> Result<(), anyhow::Error> { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: starting notifications module".to_string(), + ) + .send(&send_to_terminal) + .await; + + let state = Arc::new(RwLock::new(NotificationsState { + vapid_keys: None, + subscriptions: Vec::new(), + last_push_timestamp: None, + notification_queue: VecDeque::new(), + queue_processor_handle: None, + })); + + // Try to load existing keys from state + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: loading keys from state".to_string(), + ) + .send(&send_to_terminal) + .await; + load_keys_from_state( + &our_node, + &mut recv_notifications, + &send_to_state, + &send_to_loop, + &send_to_terminal, + &state, + ) + .await; + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: finished loading keys from state".to_string(), + ) + .send(&send_to_terminal) + .await; + + while let Some(km) = recv_notifications.recv().await { + if *our_node != km.source.node { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: got request from {}, but requests must come from our node {our_node}", + km.source.node + ), + ) + .send(&send_to_terminal) + .await; + continue; + } + + let state = state.clone(); + let our_node = our_node.clone(); + let send_to_loop = send_to_loop.clone(); + let send_to_terminal = send_to_terminal.clone(); + let send_to_state = send_to_state.clone(); + + tokio::spawn(async move { + if let Err(e) = handle_request( + &our_node, + km, + &send_to_loop, + &send_to_terminal, + &send_to_state, + &state, + ) + .await + { + Printout::new( + 0, + NOTIFICATIONS_PROCESS_ID.clone(), + format!("notifications: error handling request: {:?}", e), + ) + .send(&send_to_terminal) + .await; + } + }); + } + + Ok(()) +} + +async fn load_keys_from_state( + our_node: &str, + recv_notifications: &mut MessageReceiver, + send_to_state: &MessageSender, + send_to_loop: &MessageSender, + send_to_terminal: &PrintSender, + state: &Arc>, +) { + // Load VAPID keys + let request_id = rand::random::(); + + let km = KernelMessage::builder() + .id(request_id) + .source((our_node, NOTIFICATIONS_PROCESS_ID.clone())) + .target((our_node, ProcessId::new(Some("state"), "distro", "sys"))) + .message(Message::Request(Request { + inherit: false, + expects_response: Some(5), + body: serde_json::to_vec(&StateAction::GetState(NOTIFICATIONS_PROCESS_ID.clone())) + .unwrap(), + metadata: None, + capabilities: vec![], + })) + .build() + .unwrap(); + + km.send(send_to_state).await; + + // Wait for response with timeout + let timeout = tokio::time::sleep(tokio::time::Duration::from_secs(5)); + tokio::pin!(timeout); + + loop { + tokio::select! { + _ = &mut timeout => { + // Timeout reached, keys not found in state + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: no saved keys found in state, will generate on first use".to_string(), + ) + .send(send_to_terminal) + .await; + break; + } + Some(km) = recv_notifications.recv() => { + // Check if this is our response + if km.id == request_id { + if let Message::Response((response, _context)) = km.message { + // Check if we got the state successfully + if let Ok(state_response) = serde_json::from_slice::(&response.body) { + match state_response { + lib::core::StateResponse::GetState => { + // We got the state, deserialize the keys from context + if let Some(blob) = km.lazy_load_blob { + if let Ok(keys) = serde_json::from_slice::(&blob.bytes) { + let mut state_guard = state.write().await; + state_guard.vapid_keys = Some(keys); + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: loaded existing VAPID keys from state".to_string(), + ) + .send(send_to_terminal) + .await; + } + } + } + _ => {} + } + } + } + break; + } else { + // Not our response, put it back for main loop to handle + km.send(send_to_loop).await; + } + } + } + } + + // Load subscriptions + let request_id = rand::random::(); + + let km = KernelMessage::builder() + .id(request_id) + .source((our_node, NOTIFICATIONS_PROCESS_ID.clone())) + .target((our_node, ProcessId::new(Some("state"), "distro", "sys"))) + .message(Message::Request(Request { + inherit: false, + expects_response: Some(5), + body: serde_json::to_vec(&StateAction::GetState(ProcessId::new( + Some("notifications-subscriptions"), + "distro", + "sys", + ))) + .unwrap(), + metadata: None, + capabilities: vec![], + })) + .build() + .unwrap(); + + km.send(send_to_state).await; + + // Wait for response with timeout + let timeout = tokio::time::sleep(tokio::time::Duration::from_secs(5)); + tokio::pin!(timeout); + + loop { + tokio::select! { + _ = &mut timeout => { + // Timeout reached, no saved subscriptions + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: no saved subscriptions found in state".to_string(), + ) + .send(send_to_terminal) + .await; + break; + } + Some(km) = recv_notifications.recv() => { + // Check if this is our response + if km.id == request_id { + if let Message::Response((response, _context)) = km.message { + // Check if we got the state successfully + if let Ok(state_response) = serde_json::from_slice::(&response.body) { + match state_response { + lib::core::StateResponse::GetState => { + // We got the state, deserialize the subscriptions from context + if let Some(blob) = km.lazy_load_blob { + if let Ok(subscriptions) = serde_json::from_slice::>(&blob.bytes) { + let mut state_guard = state.write().await; + state_guard.subscriptions = subscriptions; + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!("notifications: loaded {} existing subscriptions from state", state_guard.subscriptions.len()), + ) + .send(send_to_terminal) + .await; + } + } + } + _ => {} + } + } + } + break; + } else { + // Not our response, put it back for main loop to handle + km.send(send_to_loop).await; + } + } + } + } +} + +async fn handle_request( + our_node: &str, + km: KernelMessage, + send_to_loop: &MessageSender, + send_to_terminal: &PrintSender, + send_to_state: &MessageSender, + state: &Arc>, +) -> Result<(), NotificationsError> { + let KernelMessage { + id, + source, + rsvp, + message, + .. + } = km; + + let Message::Request(Request { + expects_response, + body, + .. + }) = message + else { + return Err(NotificationsError::BadRequest { + error: "not a request".into(), + }); + }; + + let action: NotificationsAction = + serde_json::from_slice(&body).map_err(|e| NotificationsError::BadJson { + error: format!("parse into NotificationsAction failed: {:?}", e), + })?; + + let response = match action { + NotificationsAction::InitializeKeys => { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: InitializeKeys action received".to_string(), + ) + .send(send_to_terminal) + .await; + let keys = VapidKeys::generate()?; + + // Save keys to state + save_keys_to_state(our_node, send_to_state, &keys).await?; + + // Update our state + let mut state_guard = state.write().await; + state_guard.vapid_keys = Some(keys); + + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Keys initialized successfully".to_string(), + ) + .send(send_to_terminal) + .await; + NotificationsResponse::KeysInitialized + } + NotificationsAction::GetPublicKey => { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: GetPublicKey action received from {:?}", + source + ), + ) + .send(send_to_terminal) + .await; + let state_guard = state.read().await; + match &state_guard.vapid_keys { + Some(keys) => NotificationsResponse::PublicKey(keys.public_key.clone()), + None => { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: no keys found, generating new ones".to_string(), + ) + .send(send_to_terminal) + .await; + // Try to initialize keys + drop(state_guard); + let keys = VapidKeys::generate()?; + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: generated new keys, public key: {}", + keys.public_key + ), + ) + .send(send_to_terminal) + .await; + save_keys_to_state(our_node, send_to_state, &keys).await?; + + let mut state_guard = state.write().await; + let public_key = keys.public_key.clone(); + state_guard.vapid_keys = Some(keys); + + NotificationsResponse::PublicKey(public_key) + } + } + } + NotificationsAction::SendNotification { + title, + body, + icon, + data, + } => { + // Add notification to queue + let mut state_guard = state.write().await; + + // Check if we have keys and subscriptions + if state_guard.vapid_keys.is_none() { + return Err(NotificationsError::KeysNotInitialized); + } + + if state_guard.subscriptions.is_empty() { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: No subscriptions available to send notification".to_string(), + ) + .send(send_to_terminal) + .await; + return Ok(()); + } + + // Create queued notification + let queued_notification = QueuedNotification { + title, + body, + icon, + data, + }; + + // Add to queue + state_guard + .notification_queue + .push_back(queued_notification); + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Added notification to queue, queue size: {}", + state_guard.notification_queue.len() + ), + ) + .send(send_to_terminal) + .await; + + // Check if we need to start the queue processor + if state_guard.queue_processor_handle.is_none() + || state_guard + .queue_processor_handle + .as_ref() + .unwrap() + .is_finished() + { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Starting queue processor".to_string(), + ) + .send(send_to_terminal) + .await; + + // Clone what we need for the async task + let state_clone = state.clone(); + let send_to_terminal_clone = send_to_terminal.clone(); + + // Start the queue processor + let handle = tokio::spawn(async move { + process_notification_queue(&send_to_terminal_clone, &state_clone).await; + }); + + state_guard.queue_processor_handle = Some(handle); + } + + NotificationsResponse::NotificationSent + } + NotificationsAction::AddSubscription { mut subscription } => { + let mut state_guard = state.write().await; + + // Set created_at timestamp if not provided (for backward compatibility) + if subscription.created_at == 0 { + subscription.created_at = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + } + + // Check if subscription already exists (by endpoint) + if !state_guard + .subscriptions + .iter() + .any(|s| s.endpoint == subscription.endpoint) + { + state_guard.subscriptions.push(subscription.clone()); + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Added subscription, total: {}", + state_guard.subscriptions.len() + ), + ) + .send(send_to_terminal) + .await; + + // Save subscriptions to state + save_subscriptions_to_state(our_node, send_to_state, &state_guard.subscriptions) + .await?; + } else { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Subscription already exists, updating it".to_string(), + ) + .send(send_to_terminal) + .await; + // Update existing subscription + if let Some(existing) = state_guard + .subscriptions + .iter_mut() + .find(|s| s.endpoint == subscription.endpoint) + { + *existing = subscription; + save_subscriptions_to_state( + our_node, + send_to_state, + &state_guard.subscriptions, + ) + .await?; + } + } + + // Clean up old subscriptions (older than 1 month) + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let one_month_ms = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds + + let initial_count = state_guard.subscriptions.len(); + let mut removed_subscriptions = Vec::new(); + state_guard.subscriptions.retain(|s| { + let age = now.saturating_sub(s.created_at); + if age > one_month_ms { + removed_subscriptions.push((age, s.endpoint.clone())); + false + } else { + true + } + }); + + // Log removed subscriptions + for (age, endpoint) in removed_subscriptions { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Removing old subscription ({}ms old): {}", + age, endpoint + ), + ) + .send(send_to_terminal) + .await; + } + + if state_guard.subscriptions.len() < initial_count { + save_subscriptions_to_state(our_node, send_to_state, &state_guard.subscriptions) + .await?; + } + + NotificationsResponse::SubscriptionAdded + } + NotificationsAction::RemoveSubscription { endpoint } => { + let mut state_guard = state.write().await; + let initial_len = state_guard.subscriptions.len(); + state_guard.subscriptions.retain(|s| s.endpoint != endpoint); + + if state_guard.subscriptions.len() < initial_len { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Removed subscription, remaining: {}", + state_guard.subscriptions.len() + ), + ) + .send(send_to_terminal) + .await; + // Save updated subscriptions to state + save_subscriptions_to_state(our_node, send_to_state, &state_guard.subscriptions) + .await?; + NotificationsResponse::SubscriptionRemoved + } else { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Subscription not found to remove".to_string(), + ) + .send(send_to_terminal) + .await; + NotificationsResponse::SubscriptionRemoved + } + } + NotificationsAction::ClearSubscriptions => { + let mut state_guard = state.write().await; + state_guard.subscriptions.clear(); + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Cleared all subscriptions".to_string(), + ) + .send(send_to_terminal) + .await; + + // Save empty subscriptions to state + save_subscriptions_to_state(our_node, send_to_state, &state_guard.subscriptions) + .await?; + + NotificationsResponse::SubscriptionsCleared + } + NotificationsAction::GetSubscription { endpoint } => { + let state_guard = state.read().await; + let subscription = state_guard + .subscriptions + .iter() + .find(|s| s.endpoint == endpoint) + .cloned(); + + NotificationsResponse::SubscriptionInfo(subscription) + } + }; + + // Send response if expected + if let Some(target) = rsvp.or_else(|| expects_response.map(|_| source)) { + let response_bytes = serde_json::to_vec(&response).unwrap(); + + KernelMessage::builder() + .id(id) + .source((our_node, NOTIFICATIONS_PROCESS_ID.clone())) + .target(target) + .message(Message::Response(( + Response { + inherit: false, + body: response_bytes, + metadata: None, + capabilities: vec![], + }, + None, + ))) + .build() + .unwrap() + .send(send_to_loop) + .await; + + // Response sent + } + + Ok(()) +} + +async fn save_keys_to_state( + our_node: &str, + send_to_state: &MessageSender, + keys: &VapidKeys, +) -> Result<(), NotificationsError> { + let keys_bytes = serde_json::to_vec(keys).map_err(|e| NotificationsError::StateError { + error: format!("Failed to serialize keys: {:?}", e), + })?; + + KernelMessage::builder() + .id(rand::random()) + .source((our_node, NOTIFICATIONS_PROCESS_ID.clone())) + .target((our_node, ProcessId::new(Some("state"), "distro", "sys"))) + .message(Message::Request(Request { + inherit: false, + expects_response: None, // Don't expect a response to avoid polluting the main loop + body: serde_json::to_vec(&StateAction::SetState(NOTIFICATIONS_PROCESS_ID.clone())) + .unwrap(), + metadata: None, + capabilities: vec![], + })) + .lazy_load_blob(Some(LazyLoadBlob { + mime: Some("application/octet-stream".into()), + bytes: keys_bytes, + })) + .build() + .unwrap() + .send(send_to_state) + .await; + + Ok(()) +} + +async fn save_subscriptions_to_state( + our_node: &str, + send_to_state: &MessageSender, + subscriptions: &[PushSubscription], +) -> Result<(), NotificationsError> { + let subscriptions_bytes = + serde_json::to_vec(subscriptions).map_err(|e| NotificationsError::StateError { + error: format!("Failed to serialize subscriptions: {:?}", e), + })?; + + KernelMessage::builder() + .id(rand::random()) + .source((our_node, NOTIFICATIONS_PROCESS_ID.clone())) + .target((our_node, ProcessId::new(Some("state"), "distro", "sys"))) + .message(Message::Request(Request { + inherit: false, + expects_response: None, // Don't expect a response to avoid polluting the main loop + body: serde_json::to_vec(&StateAction::SetState(ProcessId::new( + Some("notifications-subscriptions"), + "distro", + "sys", + ))) + .unwrap(), + metadata: None, + capabilities: vec![], + })) + .lazy_load_blob(Some(LazyLoadBlob { + mime: Some("application/octet-stream".into()), + bytes: subscriptions_bytes, + })) + .build() + .unwrap() + .send(send_to_state) + .await; + + Ok(()) +} + +async fn process_notification_queue( + send_to_terminal: &PrintSender, + state: &Arc>, +) { + loop { + // Check if we should process the next notification + let should_process = { + let state_guard = state.read().await; + + // Check if enough time has passed since last push + match state_guard.last_push_timestamp { + None => true, // No previous push, can send immediately + Some(last_timestamp) => { + let elapsed = tokio::time::Instant::now().duration_since(last_timestamp); + elapsed >= tokio::time::Duration::from_secs(5) + } + } + }; + + if should_process { + // Process one notification from the queue + let notification = { + let mut state_guard = state.write().await; + state_guard.notification_queue.pop_front() + }; + + if let Some(notification) = notification { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Processing notification from queue".to_string(), + ) + .send(send_to_terminal) + .await; + + // Send the notification + if let Err(e) = + send_notification_to_all(send_to_terminal, state, notification).await + { + Printout::new( + 0, + NOTIFICATIONS_PROCESS_ID.clone(), + format!("notifications: Error sending notification: {:?}", e), + ) + .send(send_to_terminal) + .await; + } + + // Update timestamp and check if we should exit + let mut state_guard = state.write().await; + state_guard.last_push_timestamp = Some(tokio::time::Instant::now()); + + // Check if queue is now empty and clean up if so + if state_guard.notification_queue.is_empty() { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: Queue now empty, exiting processor".to_string(), + ) + .send(send_to_terminal) + .await; + state_guard.queue_processor_handle = None; + return; + } else { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: {} more notifications in queue, waiting 5 seconds", + state_guard.notification_queue.len() + ), + ) + .send(send_to_terminal) + .await; + } + } + } else { + // Wait until 5 seconds have passed since last push + let wait_duration = { + let state_guard = state.read().await; + match state_guard.last_push_timestamp { + Some(last_timestamp) => { + let elapsed = tokio::time::Instant::now().duration_since(last_timestamp); + if elapsed < tokio::time::Duration::from_secs(5) { + tokio::time::Duration::from_secs(5) - elapsed + } else { + tokio::time::Duration::from_secs(0) + } + } + None => tokio::time::Duration::from_secs(0), + } + }; + + if wait_duration > tokio::time::Duration::from_secs(0) { + tokio::time::sleep(wait_duration).await; + } + } + } +} + +async fn send_notification_to_all( + send_to_terminal: &PrintSender, + state: &Arc>, + notification: QueuedNotification, +) -> Result<(), NotificationsError> { + let state_guard = state.read().await; + + let keys = state_guard + .vapid_keys + .as_ref() + .ok_or(NotificationsError::KeysNotInitialized)?; + + if state_guard.subscriptions.is_empty() { + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + "notifications: No subscriptions available to send notification".to_string(), + ) + .send(send_to_terminal) + .await; + return Ok(()); + } + + // Build the notification payload + let payload = serde_json::json!({ + "title": notification.title, + "body": notification.body, + "icon": notification.icon, + "data": notification.data, + }); + + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Sending notification to {} devices", + state_guard.subscriptions.len() + ), + ) + .send(send_to_terminal) + .await; + + // Send to all subscriptions + let mut send_errors = Vec::new(); + let mut send_count = 0; + + for subscription in &state_guard.subscriptions { + // Create subscription info for web-push + let subscription_info = SubscriptionInfo::new( + &subscription.endpoint, + &subscription.keys.p256dh, + &subscription.keys.auth, + ); + + // Convert raw private key bytes to PEM format for web-push + let private_key_bytes = URL_SAFE_NO_PAD.decode(&keys.private_key).map_err(|e| { + NotificationsError::WebPushError { + error: format!("Failed to decode private key: {:?}", e), + } + })?; + + // Convert Vec to fixed-size array + let private_key_array: [u8; 32] = + private_key_bytes + .try_into() + .map_err(|_| NotificationsError::WebPushError { + error: "Invalid private key length".to_string(), + })?; + + // Create PEM from raw bytes using p256 + use p256::ecdsa::SigningKey; + use p256::pkcs8::EncodePrivateKey; + + let signing_key = SigningKey::from_bytes(&private_key_array.into()).map_err(|e| { + NotificationsError::WebPushError { + error: format!("Failed to create signing key: {:?}", e), + } + })?; + + let pem_content = signing_key + .to_pkcs8_pem(p256::pkcs8::LineEnding::LF) + .map_err(|e| NotificationsError::WebPushError { + error: format!("Failed to convert to PEM: {:?}", e), + })? + .to_string(); + + // Create VAPID signature from PEM + let mut sig_builder = + VapidSignatureBuilder::from_pem(pem_content.as_bytes(), &subscription_info).map_err( + |e| NotificationsError::WebPushError { + error: format!("Failed to create VAPID signature: {:?}", e), + }, + )?; + + // Add required subject claim for VAPID + sig_builder.add_claim("sub", "mailto:admin@hyperware.ai"); + + let sig_builder = sig_builder + .build() + .map_err(|e| NotificationsError::WebPushError { + error: format!("Failed to build VAPID signature: {:?}", e), + })?; + + // Build the web push message + let mut message_builder = WebPushMessageBuilder::new(&subscription_info); + let payload_str = payload.to_string(); + message_builder.set_payload(ContentEncoding::Aes128Gcm, payload_str.as_bytes()); + message_builder.set_vapid_signature(sig_builder); + + let message = message_builder + .build() + .map_err(|e| NotificationsError::WebPushError { + error: format!("Failed to build message: {:?}", e), + })?; + + // Send the notification using IsahcWebPushClient + let client = IsahcWebPushClient::new().map_err(|e| NotificationsError::WebPushError { + error: format!("Failed to create web push client: {:?}", e), + })?; + + match client.send(message).await { + Ok(_) => { + send_count += 1; + } + Err(e) => { + Printout::new( + 0, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Failed to send to {}: {:?}", + subscription.endpoint, e + ), + ) + .send(send_to_terminal) + .await; + send_errors.push(format!("Failed to send to endpoint: {:?}", e)); + } + } + } + + Printout::new( + 2, + NOTIFICATIONS_PROCESS_ID.clone(), + format!( + "notifications: Sent to {}/{} devices", + send_count, + state_guard.subscriptions.len() + ), + ) + .send(send_to_terminal) + .await; + + Ok(()) +} diff --git a/hyperdrive/src/register-ui/src/pages/Login.tsx b/hyperdrive/src/register-ui/src/pages/Login.tsx index 64faac28d..741f754c0 100644 --- a/hyperdrive/src/register-ui/src/pages/Login.tsx +++ b/hyperdrive/src/register-ui/src/pages/Login.tsx @@ -17,6 +17,11 @@ function Login({ }: LoginProps) { const navigate = useNavigate(); + useEffect(() => { + if (!hnsName) navigate('/'); + }, [hnsName]); + + const [keyErrs, setKeyErrs] = useState([]); const [loading, setLoading] = useState(""); diff --git a/hyperdrive/src/state.rs b/hyperdrive/src/state.rs index 611800df8..710886bf9 100644 --- a/hyperdrive/src/state.rs +++ b/hyperdrive/src/state.rs @@ -118,6 +118,22 @@ pub async fn state_sender( continue; } + if (km.source.process.package() != "distro" && km.source.process.package() != "terminal") + || km.source.process.publisher() != "sys" + { + Printout::new( + 1, + STATE_PROCESS_ID.clone(), + format!( + "state: got request from {}, but requests must come from kernel or terminal", + km.source.process + ), + ) + .send(&send_to_terminal) + .await; + continue; + } + let queue = process_queues .get(&km.source.process) .cloned() diff --git a/lib/Cargo.toml b/lib/Cargo.toml index f0a8d10e1..a3e17ac41 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lib" authors = ["Sybil Technologies AG"] -version = "1.6.0" +version = "1.7.0" edition = "2021" description = "A general-purpose sovereign cloud computing platform" homepage = "https://hyperware.ai" diff --git a/lib/src/core.rs b/lib/src/core.rs index 308bc3b1b..8d72cb246 100644 --- a/lib/src/core.rs +++ b/lib/src/core.rs @@ -13,6 +13,7 @@ lazy_static::lazy_static! { pub static ref KERNEL_PROCESS_ID: ProcessId = ProcessId::new(Some("kernel"), "distro", "sys"); pub static ref KV_PROCESS_ID: ProcessId = ProcessId::new(Some("kv"), "distro", "sys"); pub static ref NET_PROCESS_ID: ProcessId = ProcessId::new(Some("net"), "distro", "sys"); + pub static ref NOTIFICATIONS_PROCESS_ID: ProcessId = ProcessId::new(Some("notifications"), "distro", "sys"); pub static ref STATE_PROCESS_ID: ProcessId = ProcessId::new(Some("state"), "distro", "sys"); pub static ref SQLITE_PROCESS_ID: ProcessId = ProcessId::new(Some("sqlite"), "distro", "sys"); pub static ref TERMINAL_PROCESS_ID: ProcessId = ProcessId::new(Some("terminal"), "terminal", "sys"); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f779da572..0373ac41c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -5,6 +5,7 @@ mod http; mod kernel; mod kv; mod net; +pub mod notifications; mod sqlite; mod state; mod timer; diff --git a/lib/src/notifications.rs b/lib/src/notifications.rs new file mode 100644 index 000000000..76745ae96 --- /dev/null +++ b/lib/src/notifications.rs @@ -0,0 +1,90 @@ +use crate::types::core::ProcessId; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// IPC Requests for the notifications:distro:sys runtime module. +#[derive(Serialize, Deserialize, Debug)] +pub enum NotificationsAction { + /// Send a push notification to all registered devices + SendNotification { + title: String, + body: String, + icon: Option, + data: Option, + }, + /// Get the public key for VAPID authentication + GetPublicKey, + /// Initialize or regenerate VAPID keys + InitializeKeys, + /// Add a push subscription for a device + AddSubscription { subscription: PushSubscription }, + /// Remove a push subscription + RemoveSubscription { endpoint: String }, + /// Clear all subscriptions + ClearSubscriptions, + /// Get subscription info by endpoint + GetSubscription { endpoint: String }, +} + +/// Push subscription information from the client +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PushSubscription { + pub endpoint: String, + pub keys: SubscriptionKeys, + /// Timestamp when the subscription was created (milliseconds since epoch) + pub created_at: u64, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SubscriptionKeys { + pub p256dh: String, + pub auth: String, +} + +/// Responses for the notifications:distro:sys runtime module. +#[derive(Serialize, Deserialize, Debug)] +pub enum NotificationsResponse { + NotificationSent, + PublicKey(String), + KeysInitialized, + SubscriptionAdded, + SubscriptionRemoved, + SubscriptionsCleared, + SubscriptionInfo(Option), + Err(NotificationsError), +} + +#[derive(Error, Debug, Serialize, Deserialize)] +pub enum NotificationsError { + #[error("failed to send notification: {error}")] + SendError { error: String }, + #[error("failed to generate VAPID keys: {error}")] + KeyGenerationError { error: String }, + #[error("failed to load keys from state: {error}")] + StateError { error: String }, + #[error("bad request error: {error}")] + BadRequest { error: String }, + #[error("Bad JSON blob: {error}")] + BadJson { error: String }, + #[error("VAPID keys not initialized")] + KeysNotInitialized, + #[error("web push error: {error}")] + WebPushError { error: String }, + #[error("unauthorized request from {process}")] + Unauthorized { process: ProcessId }, +} + +impl NotificationsError { + pub fn kind(&self) -> &str { + match *self { + NotificationsError::SendError { .. } => "SendError", + NotificationsError::KeyGenerationError { .. } => "KeyGenerationError", + NotificationsError::StateError { .. } => "StateError", + NotificationsError::BadRequest { .. } => "BadRequest", + NotificationsError::BadJson { .. } => "BadJson", + NotificationsError::KeysNotInitialized => "KeysNotInitialized", + NotificationsError::WebPushError { .. } => "WebPushError", + NotificationsError::Unauthorized { .. } => "Unauthorized", + } + } +} diff --git a/scripts/build-packages/Cargo.toml b/scripts/build-packages/Cargo.toml index cc7bdcb1c..6ea81bb24 100644 --- a/scripts/build-packages/Cargo.toml +++ b/scripts/build-packages/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" anyhow = "1.0.71" clap = "4" fs-err = "2.11" -kit = { git = "https://github.com/hyperware-ai/kit", rev = "79fe678" } +kit = { git = "https://github.com/hyperware-ai/kit", rev = "aac33b6" } serde = "1" serde_json = "1" tokio = "1.28" diff --git a/scripts/build-packages/src/main.rs b/scripts/build-packages/src/main.rs index 980f6166a..b7a439139 100644 --- a/scripts/build-packages/src/main.rs +++ b/scripts/build-packages/src/main.rs @@ -12,11 +12,13 @@ use zip::write::FileOptions; struct PackageBuildParameters { local_dependencies: Option>, is_hyperapp: Option, + features: Option>, } struct PackageBuildParametersPath { local_dependencies: Option>, is_hyperapp: Option, + features: Option>, } fn zip_directory(dir_path: &Path) -> anyhow::Result> { @@ -148,6 +150,7 @@ fn main() -> anyhow::Result<()> { .local_dependencies .map(|bp| bp.iter().map(|f| packages_dir.join(f)).collect()), is_hyperapp: val.is_hyperapp, + features: val.features, }, ) }) @@ -164,7 +167,7 @@ fn main() -> anyhow::Result<()> { // don't run on, e.g., `.DS_Store` return None; } - let (local_dependency_array, is_hyperapp) = + let (local_dependency_array, is_hyperapp, package_specific_features) = if let Some(filename) = entry_path.file_name() { if let Some(maybe_params) = build_parameters.remove(&filename.to_string_lossy().to_string()) @@ -172,18 +175,36 @@ fn main() -> anyhow::Result<()> { ( maybe_params.local_dependencies.unwrap_or_default(), maybe_params.is_hyperapp.unwrap_or_default(), + maybe_params.features.unwrap_or_default(), ) } else { - (vec![], false) + (vec![], false, vec![]) } } else { - (vec![], false) + (vec![], false, vec![]) }; + let package_specific_features = if package_specific_features.is_empty() { + features.clone() + } else if package_specific_features.contains(&"caller-utils".to_string()) { + // build without caller-utils flag, which will fail but will + // also create caller-utils crate (required for succeeding build) + let _ = build_and_zip_package( + entry_path.clone(), + child_pkg_path.to_str().unwrap(), + skip_frontend, + &features, + local_dependency_array.clone(), + is_hyperapp, + ); + format!("{features},{}", package_specific_features.join(",")) + } else { + format!("{features},{}", package_specific_features.join(",")) + }; Some(build_and_zip_package( entry_path.clone(), child_pkg_path.to_str().unwrap(), skip_frontend, - &features, + &package_specific_features, local_dependency_array, is_hyperapp, ))