diff --git a/examples/openui-rust-tui/.gitignore b/examples/openui-rust-tui/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/examples/openui-rust-tui/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/openui-rust-tui/Cargo.lock b/examples/openui-rust-tui/Cargo.lock new file mode 100644 index 000000000..793676299 --- /dev/null +++ b/examples/openui-rust-tui/Cargo.lock @@ -0,0 +1,662 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compact_str" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "openui-rust-tui" +version = "0.1.0" +dependencies = [ + "crossterm", + "ratatui", + "serde", + "serde_json", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/examples/openui-rust-tui/Cargo.toml b/examples/openui-rust-tui/Cargo.toml new file mode 100644 index 000000000..6dd05b6ac --- /dev/null +++ b/examples/openui-rust-tui/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "openui-rust-tui" +version = "0.1.0" +edition = "2021" +description = "A Rust/ratatui terminal client that renders streamed OpenUI Lang via a Node bridge." + +[[bin]] +name = "openui-rust-tui" +path = "src/main.rs" + +[dependencies] +ratatui = "0.29" +crossterm = "0.28" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.release] +opt-level = 2 diff --git a/examples/openui-rust-tui/README.md b/examples/openui-rust-tui/README.md new file mode 100644 index 000000000..3b6b89da0 --- /dev/null +++ b/examples/openui-rust-tui/README.md @@ -0,0 +1,73 @@ +# OpenUI Rust TUI (ratatui) — via a Node bridge + +A **Rust/[ratatui](https://ratatui.rs)** terminal client that renders streamed +**OpenUI Lang** as a native, mouse-driven, scrollable TUI. OpenUI stays canonical +in JavaScript: a small **Node "bridge"** runs `@openuidev/lang-core` to parse the +streamed OpenUI Lang and emits a serialized render tree; the Rust process is a +pure renderer + input handler. + +This is a sibling to the Ink example (`examples/openui-tui-chat`), exploring the +same idea on a Rust stack. It exists because ratatui is *immediate-mode* — you own +every widget's `Rect` — which makes the things that are hard in Ink easy: + +- **Pixel-perfect mouse.** Clicks hit-test against exact widget rects, even when the content is scrolled. +- **Real scrolling / clipping.** A form taller than the screen lives in a scrollable viewport (with a scroll indicator and auto-scroll to the focused field) instead of corrupting the layout. +- **No flicker.** ratatui double-buffers and diffs cells. + +``` +you ─▶ prompt + ─▶ (Rust) write {"type":"send","content":...} to bridge stdin + ─▶ (Node bridge) @openuidev/lang-core streaming parse + evaluate + ─▶ emit {"type":"render","root":} JSON on stdout + ─▶ (Rust/ratatui) render tree → widgets, handle keyboard + mouse + scroll + ─▶ actions (button/select/submit) ─▶ back to the bridge +``` + +## Architecture + +| Piece | Role | +| --- | --- | +| `bridge/` (Node/TS) | The OpenUI brain. `@openuidev/lang-core` defines the component library + system prompt, streams from an OpenAI-compatible model, parses/evaluates OpenUI Lang, and emits a JSON render tree over stdio. | +| `src/main.rs` (Rust) | Spawns the bridge, reads the render tree, draws it with ratatui, and handles keyboard/mouse/scroll + the action loop. Form field state lives here and is sent back on submit. | + +Protocol (newline-delimited JSON): Rust→bridge `{"type":"send","content":"…"}`; +bridge→Rust `{"type":"ready"}`, `{"type":"render","root":,"streaming":bool}`, `{"type":"error","message":"…"}`. + +## Run + +Requires **Rust** (a recent stable — ratatui 0.29's deps need rustc ≥ 1.88; a +`rust-toolchain.toml` selects `stable`), **Node 20+**, and an OpenAI-compatible key. + +```sh +# from the repo root: build the workspace packages the bridge depends on +pnpm install + +export OPENAI_API_KEY=sk-... # optional: OPENAI_BASE_URL / OPENAI_MODEL +cd examples/openui-rust-tui +cargo run # spawns the Node bridge automatically +``` + +Then type a prompt (e.g. _"a success callout, some tags, and a bar chart"_ or +_"a long registration form"_). + +### Controls + +- Type + **Enter** — send a message. +- **Tab / Shift+Tab** — move focus (composer ↔ interactive elements). Focus auto-scrolls into view. +- **Enter / click** — activate the focused follow-up/button, or choose a Select option. +- **↑ / ↓** or **number keys** — choose a Select option; **↑ / ↓** also scroll when the composer is focused. +- **Mouse click** — focus/activate any visible element; **scroll wheel** scrolls the viewport. +- **Ctrl+C** — quit (restores your terminal via the alternate screen). + +## Supported components + +`Card`, `CardHeader`, `TextContent`, `Callout`, `TagBlock`, `Table`/`Col`, +`BarChart`/`Series`, `FollowUpBlock`/`FollowUpItem`, +`Form`/`FormControl`/`Input`/`Select`/`Buttons`/`Button`. Follow-ups, buttons and +form submits drive the assistant loop via the OpenUI `@ToAssistant` action. + +## Notes / limitations (POC) + +- Charts/tables render as text/ASCII; not pixel-faithful. +- The bridge is spawned with an absolute path baked at compile time (`CARGO_MANIFEST_DIR`), so run the built binary from this checkout. +- Only the current exchange is shown (no multi-turn transcript log); the viewport scrolls a single long response. diff --git a/examples/openui-rust-tui/bridge/package.json b/examples/openui-rust-tui/bridge/package.json new file mode 100644 index 000000000..692574fbf --- /dev/null +++ b/examples/openui-rust-tui/bridge/package.json @@ -0,0 +1,21 @@ +{ + "name": "openui-rust-tui-bridge", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Node 'OpenUI brain' for the Rust/ratatui TUI: parses streamed OpenUI Lang via @openuidev/lang-core and emits a serialized render tree over stdio.", + "scripts": { + "start": "tsx src/bridge.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@openuidev/lang-core": "workspace:*", + "openai": "^6.22.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/node": "^20", + "tsx": "^4.19.2", + "typescript": "^5.9.3" + } +} diff --git a/examples/openui-rust-tui/bridge/src/bridge.ts b/examples/openui-rust-tui/bridge/src/bridge.ts new file mode 100644 index 000000000..ab8b4770c --- /dev/null +++ b/examples/openui-rust-tui/bridge/src/bridge.ts @@ -0,0 +1,97 @@ +import { + createStore, + createStreamingParser, + evaluateElementProps, + type ElementNode, +} from "@openuidev/lang-core"; +import { createInterface } from "node:readline"; +import OpenAI from "openai"; +import type { ChatCompletionMessageParam } from "openai/resources/chat/completions"; +import { library } from "./library.js"; + +// ── protocol ────────────────────────────────────────────────────────────── +// stdin (Rust → bridge): {"type":"send","content":"..."} +// stdout (bridge → Rust): one JSON object per line: +// {"type":"ready"} +// {"type":"render","root":,"streaming":bool} +// {"type":"error","message":"..."} + +function emit(obj: unknown) { + process.stdout.write(JSON.stringify(obj) + "\n"); +} + +const systemPrompt = library.prompt(); +const schema = library.toJSONSchema(); + +const client = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + baseURL: process.env.OPENAI_BASE_URL || undefined, +}); +const model = process.env.OPENAI_MODEL || "gpt-5.5"; + +const history: ChatCompletionMessageParam[] = [{ role: "system", content: systemPrompt }]; + +/** Parse + evaluate an OpenUI Lang string into a concrete, JSON-serializable tree. */ +function renderTree(response: string): ElementNode | null { + const sp = createStreamingParser(schema, library.root); + const parsed = sp.set(response); + if (!parsed.root) return null; + const store = createStore(); + store.initialize(parsed.stateDeclarations ?? {}, {}); + try { + return evaluateElementProps(parsed.root, { + ctx: { getState: (n) => store.get(n), resolveRef: () => undefined }, + library, + store, + errors: [], + }); + } catch { + return parsed.root; + } +} + +let running = false; + +async function handleSend(content: string) { + if (running) return; + running = true; + history.push({ role: "user", content }); + try { + const stream = await client.chat.completions.create({ model, messages: history, stream: true }); + let acc = ""; + let lastEmit = 0; + for await (const chunk of stream) { + const delta = chunk.choices?.[0]?.delta?.content; + if (!delta) continue; + acc += delta; + const now = Date.now(); + if (now - lastEmit > 70) { + lastEmit = now; + emit({ type: "render", root: renderTree(acc), streaming: true }); + } + } + history.push({ role: "assistant", content: acc }); + emit({ type: "render", root: renderTree(acc), streaming: false }); + } catch (e) { + emit({ type: "error", message: e instanceof Error ? e.message : String(e) }); + } finally { + running = false; + } +} + +const rl = createInterface({ input: process.stdin }); +rl.on("line", (line) => { + const trimmed = line.trim(); + if (!trimmed) return; + let msg: { type?: string; content?: string }; + try { + msg = JSON.parse(trimmed); + } catch { + return; + } + if (msg.type === "send" && typeof msg.content === "string") { + void handleSend(msg.content); + } +}); + +emit({ type: "ready" }); diff --git a/examples/openui-rust-tui/bridge/src/library.ts b/examples/openui-rust-tui/bridge/src/library.ts new file mode 100644 index 000000000..ae500dba7 --- /dev/null +++ b/examples/openui-rust-tui/bridge/src/library.ts @@ -0,0 +1,198 @@ +import { createLibrary, defineComponent } from "@openuidev/lang-core"; +import { z } from "zod"; + +// The bridge only parses/evaluates OpenUI Lang — it never renders — so each +// component's `component` is a no-op. The Rust side is the renderer. +const noop = null as unknown as never; + +const Series = defineComponent({ + name: "Series", + description: "One data series for a chart.", + props: z.object({ category: z.string(), values: z.array(z.number()) }), + component: noop, +}); + +const Col = defineComponent({ + name: "Col", + description: "A table column: a label plus its column data (one entry per row).", + props: z.object({ + label: z.string(), + data: z.array(z.union([z.string(), z.number()])), + type: z.enum(["string", "number"]).optional(), + }), + component: noop, +}); + +const SelectItem = defineComponent({ + name: "SelectItem", + description: "An option inside a Select.", + props: z.object({ value: z.string(), label: z.string() }), + component: noop, +}); + +const FollowUpItem = defineComponent({ + name: "FollowUpItem", + description: "A clickable follow-up suggestion; its text is sent to the assistant when chosen.", + props: z.object({ text: z.string() }), + component: noop, +}); + +const Button = defineComponent({ + name: "Button", + description: + 'A clickable button. Provide an action Action([@ToAssistant("message")]) to control what is sent to the assistant.', + props: z.object({ + label: z.string(), + action: z.any().optional(), + variant: z.enum(["primary", "secondary", "tertiary"]).optional(), + }), + component: noop, +}); + +const Input = defineComponent({ + name: "Input", + description: "A single-line text field inside a Form.", + props: z.object({ + name: z.string(), + placeholder: z.string().optional(), + type: z.enum(["text", "email", "password", "number", "url"]).optional(), + }), + component: noop, +}); + +const Select = defineComponent({ + name: "Select", + description: "A single-choice dropdown inside a Form.", + props: z.object({ + name: z.string(), + items: z.array(SelectItem.ref), + placeholder: z.string().optional(), + }), + component: noop, +}); + +const FormControl = defineComponent({ + name: "FormControl", + description: "A labelled form field wrapping one Input or Select.", + props: z.object({ + label: z.string(), + input: z.union([Input.ref, Select.ref]), + hint: z.string().optional(), + }), + component: noop, +}); + +const Buttons = defineComponent({ + name: "Buttons", + description: "A group of Button components.", + props: z.object({ + buttons: z.array(Button.ref), + direction: z.enum(["row", "column"]).optional(), + }), + component: noop, +}); + +const Form = defineComponent({ + name: "Form", + description: + "A form with fields and explicit action buttons. Provide Buttons(...) as the second argument.", + props: z.object({ + name: z.string(), + buttons: Buttons.ref, + fields: z.array(FormControl.ref).default([]), + }), + component: noop, +}); + +const BarChart = defineComponent({ + name: "BarChart", + description: "A bar chart. Use for comparing values across categories.", + props: z.object({ + labels: z.array(z.string()), + series: z.array(Series.ref), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + }), + component: noop, +}); + +const Table = defineComponent({ + name: "Table", + description: "A column-oriented data table. Each Col holds its own data array.", + props: z.object({ columns: z.array(Col.ref) }), + component: noop, +}); + +const TextContent = defineComponent({ + name: "TextContent", + description: "A block of text. Optional size controls emphasis.", + props: z.object({ + text: z.string(), + size: z.enum(["small", "default", "large", "small-heavy", "large-heavy"]).optional(), + }), + component: noop, +}); + +const CardHeader = defineComponent({ + name: "CardHeader", + description: "A header with an optional title and subtitle.", + props: z.object({ title: z.string().optional(), subtitle: z.string().optional() }), + component: noop, +}); + +const Callout = defineComponent({ + name: "Callout", + description: + "A colored callout banner for highlights, tips, or status. Choose a variant matching the tone.", + props: z.object({ + variant: z.enum(["info", "success", "warning", "error", "neutral"]), + title: z.string(), + description: z.string().optional(), + }), + component: noop, +}); + +const TagBlock = defineComponent({ + name: "TagBlock", + description: "A row of short colored tags/pills (keywords, categories, or labels).", + props: z.object({ tags: z.array(z.string()) }), + component: noop, +}); + +const FollowUpBlock = defineComponent({ + name: "FollowUpBlock", + description: "A list of follow-up suggestions shown at the end of a response.", + props: z.object({ items: z.array(FollowUpItem.ref) }), + component: noop, +}); + +const Card = defineComponent({ + name: "Card", + description: "The root container. Children stack vertically. Every response is a single Card.", + props: z.object({ children: z.array(z.any()) }), + component: noop, +}); + +export const library = createLibrary({ + components: [ + Card, + CardHeader, + TextContent, + Callout, + TagBlock, + Table, + Col, + BarChart, + Series, + FollowUpBlock, + FollowUpItem, + Form, + FormControl, + Input, + Select, + SelectItem, + Buttons, + Button, + ], + root: "Card", +}); diff --git a/examples/openui-rust-tui/bridge/tsconfig.json b/examples/openui-rust-tui/bridge/tsconfig.json new file mode 100644 index 000000000..dd6d97623 --- /dev/null +++ b/examples/openui-rust-tui/bridge/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/openui-rust-tui/rust-toolchain.toml b/examples/openui-rust-tui/rust-toolchain.toml new file mode 100644 index 000000000..044520f21 --- /dev/null +++ b/examples/openui-rust-tui/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +# ratatui 0.29's dependency tree needs a recent rustc (>= 1.88). +channel = "stable" diff --git a/examples/openui-rust-tui/src/main.rs b/examples/openui-rust-tui/src/main.rs new file mode 100644 index 000000000..fdd3b7571 --- /dev/null +++ b/examples/openui-rust-tui/src/main.rs @@ -0,0 +1,934 @@ +// OpenUI Rust TUI — a ratatui renderer for streamed OpenUI Lang. +// +// A Node "bridge" (bridge/src/bridge.ts) is the OpenUI brain: it uses +// @openuidev/lang-core to parse the streamed OpenUI Lang and emits a serialized +// render tree (JSON, one object per line) on stdout, and accepts actions on +// stdin. This Rust process is a pure renderer + input handler: it draws the tree +// with ratatui and, because ratatui is immediate-mode (we own every widget's +// Rect), mouse hit-testing is pixel-exact and long content scrolls cleanly. + +use std::collections::HashMap; +use std::io::{BufRead, BufReader, Write}; +use std::process::{Child, ChildStdin, Command, Stdio}; +use std::sync::mpsc::{self, Receiver}; +use std::thread; +use std::time::Duration; + +use crossterm::event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, + MouseButton, MouseEvent, MouseEventKind, +}; +use crossterm::execute; +use crossterm::terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, +}; +use ratatui::prelude::*; +use ratatui::widgets::{Block, Borders, Clear, Paragraph}; +use serde_json::Value; + +// ─────────────────────────── bridge protocol ─────────────────────────── + +enum BridgeMsg { + Ready, + Render { root: Option, streaming: bool }, + Error(String), +} + +fn spawn_bridge() -> std::io::Result<(Child, Receiver, ChildStdin)> { + let bridge_entry = concat!(env!("CARGO_MANIFEST_DIR"), "/bridge/src/bridge.ts"); + let mut child = Command::new("node") + .args(["--import", "tsx", bridge_entry]) + .current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/bridge")) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn()?; + + let stdout = child.stdout.take().expect("bridge stdout"); + let stdin = child.stdin.take().expect("bridge stdin"); + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let reader = BufReader::new(stdout); + for line in reader.lines() { + let Ok(line) = line else { break }; + let Ok(v) = serde_json::from_str::(&line) else { continue }; + let msg = match v.get("type").and_then(Value::as_str) { + Some("ready") => BridgeMsg::Ready, + Some("render") => BridgeMsg::Render { + root: v.get("root").filter(|r| !r.is_null()).cloned(), + streaming: v.get("streaming").and_then(Value::as_bool).unwrap_or(false), + }, + Some("error") => { + BridgeMsg::Error(v.get("message").and_then(Value::as_str).unwrap_or("error").to_string()) + } + _ => continue, + }; + if tx.send(msg).is_err() { + break; + } + } + }); + + Ok((child, rx, stdin)) +} + +// ─────────────────────────── interactive targets ─────────────────────────── + +#[derive(Clone)] +enum TargetKind { + Button { message: String, form: Option }, + FollowUp { text: String }, + Input { form: String, name: String }, + Select { form: String, name: String, options: Vec<(String, String)> }, +} + +#[derive(Clone)] +struct Target { + kind: TargetKind, + top: usize, // first content line (pre-scroll) this target occupies + height: usize, // number of content lines +} + +#[derive(Clone, Copy, PartialEq)] +enum Focus { + Composer, + Target(usize), +} + +// ─────────────────────────── app state ─────────────────────────── + +struct App { + root: Option, + streaming: bool, + composer: String, + focus: Focus, + scroll: usize, + fields: HashMap<(String, String), String>, + selects: HashMap<(String, String), String>, + select_cursor: HashMap<(String, String), usize>, + error: Option, + status: String, + // recomputed each frame: + targets: Vec, + content_area: Rect, + content_height: usize, + total_lines: usize, + should_quit: bool, +} + +impl App { + fn new() -> Self { + App { + root: None, + streaming: false, + composer: String::new(), + focus: Focus::Composer, + scroll: 0, + fields: HashMap::new(), + selects: HashMap::new(), + select_cursor: HashMap::new(), + error: None, + status: "Ask for UI — a chart, a table, a callout, or a (long) form.".to_string(), + targets: Vec::new(), + content_area: Rect::default(), + content_height: 0, + total_lines: 0, + should_quit: false, + } + } +} + +const GRAD_START: (u8, u8, u8) = (0, 224, 255); +const GRAD_END: (u8, u8, u8) = (180, 90, 255); + +fn gradient_line(text: &str, bold: bool) -> Line<'static> { + let chars: Vec = text.chars().collect(); + let n = chars.len().max(1) as f32 - 1.0; + let spans: Vec = chars + .iter() + .enumerate() + .map(|(i, c)| { + let t = if n <= 0.0 { 0.0 } else { i as f32 / n }; + let r = (GRAD_START.0 as f32 + (GRAD_END.0 as f32 - GRAD_START.0 as f32) * t) as u8; + let g = (GRAD_START.1 as f32 + (GRAD_END.1 as f32 - GRAD_START.1 as f32) * t) as u8; + let b = (GRAD_START.2 as f32 + (GRAD_END.2 as f32 - GRAD_START.2 as f32) * t) as u8; + let mut style = Style::default().fg(Color::Rgb(r, g, b)); + if bold { + style = style.add_modifier(Modifier::BOLD); + } + Span::styled(c.to_string(), style) + }) + .collect(); + Line::from(spans) +} + +fn main() -> std::io::Result<()> { + if std::env::var("OPENAI_API_KEY").is_err() { + eprintln!("OPENAI_API_KEY is not set. Export it (and optionally OPENAI_BASE_URL / OPENAI_MODEL) and retry."); + std::process::exit(1); + } + + let (mut child, rx, mut bridge_stdin) = spawn_bridge()?; + + enable_raw_mode()?; + let mut stdout = std::io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let mut app = App::new(); + let res = run(&mut terminal, &mut app, &rx, &mut bridge_stdin); + + disable_raw_mode()?; + execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; + terminal.show_cursor()?; + let _ = child.kill(); + + res +} + +fn run( + terminal: &mut Terminal, + app: &mut App, + rx: &Receiver, + bridge_stdin: &mut ChildStdin, +) -> std::io::Result<()> { + loop { + // Drain bridge messages. + while let Ok(msg) = rx.try_recv() { + match msg { + BridgeMsg::Ready => {} + BridgeMsg::Render { root, streaming } => { + app.root = root; + app.streaming = streaming; + app.error = None; + if !streaming { + app.status = "Tab/click to focus · Enter/click to act · type to fill · ↑↓/click to choose".to_string(); + } + } + BridgeMsg::Error(e) => { + app.error = Some(e); + app.streaming = false; + } + } + } + + terminal.draw(|f| draw(f, app))?; + + if app.should_quit { + return Ok(()); + } + + if event::poll(Duration::from_millis(60))? { + match event::read()? { + Event::Key(key) => handle_key(app, key, bridge_stdin), + Event::Mouse(m) => handle_mouse(app, m, bridge_stdin), + Event::Resize(_, _) => {} + _ => {} + } + } + } +} + +// ─────────────────────────── drawing ─────────────────────────── + +fn draw(f: &mut Frame, app: &mut App) { + let area = f.area(); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1), Constraint::Min(1), Constraint::Length(4)]) + .split(area); + + // Header + let mut header = gradient_line(" ◆ OpenUI (Rust/ratatui) ", true).spans; + header.push(Span::styled( + " streamed OpenUI Lang, rendered natively", + Style::default().add_modifier(Modifier::DIM), + )); + f.render_widget(Paragraph::new(Line::from(header)), chunks[0]); + + // Content + let (lines, targets) = build_view(app); + app.targets = targets; + app.total_lines = lines.len(); + let content_area = chunks[1]; + let inner_h = content_area.height.saturating_sub(2) as usize; // minus border + app.content_height = inner_h; + app.content_area = content_area; + + // Clamp scroll and auto-scroll to the focused target. + if let Focus::Target(i) = app.focus { + if let Some(t) = app.targets.get(i) { + if t.top < app.scroll { + app.scroll = t.top; + } else if t.top + t.height > app.scroll + inner_h.max(1) { + app.scroll = (t.top + t.height).saturating_sub(inner_h.max(1)); + } + } + } + let max_scroll = app.total_lines.saturating_sub(inner_h.max(1)); + if app.scroll > max_scroll { + app.scroll = max_scroll; + } + + let scrolled = app.streaming; // subtle hint could be added + let _ = scrolled; + let title = if app.total_lines > inner_h { + format!(" ◆ OpenUI (scroll ↑↓ · {}/{}) ", app.scroll + 1, app.total_lines) + } else { + " ◆ OpenUI ".to_string() + }; + let block = Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::DarkGray)) + .title(Span::styled(title, Style::default().fg(Color::Cyan))); + let para = Paragraph::new(lines) + .block(block) + .scroll((app.scroll as u16, 0)); + f.render_widget(Clear, content_area); + f.render_widget(para, content_area); + + // Composer + let composer_focused = app.focus == Focus::Composer; + let border_color = if composer_focused { Color::Magenta } else { Color::DarkGray }; + let mut composer_spans = vec![Span::styled( + "❯ ", + Style::default().fg(if composer_focused { Color::Magenta } else { Color::Gray }), + )]; + if app.composer.is_empty() { + composer_spans.push(Span::styled( + if app.streaming { "waiting for response…" } else { "Message OpenUI…" }, + Style::default().add_modifier(Modifier::DIM), + )); + } else { + composer_spans.push(Span::raw(app.composer.clone())); + if composer_focused { + composer_spans.push(Span::styled("▏", Style::default().fg(Color::Magenta))); + } + } + let hint = if let Some(err) = &app.error { + Line::from(Span::styled(format!("error: {err}"), Style::default().fg(Color::Red))) + } else { + Line::from(Span::styled( + format!(" {}", app.status), + Style::default().add_modifier(Modifier::DIM), + )) + }; + let composer = Paragraph::new(vec![Line::from(composer_spans), hint]).block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(border_color)) + .title(Span::styled( + " message · Enter send · Tab focus · Ctrl+C quit ", + Style::default().add_modifier(Modifier::DIM), + )), + ); + f.render_widget(composer, chunks[2]); +} + +// ─────────────────────────── view builder (tree → lines + targets) ───────── + +fn prop<'a>(node: &'a Value, key: &str) -> Option<&'a Value> { + node.get("props").and_then(|p| p.get(key)) +} +fn pstr(node: &Value, key: &str) -> String { + prop(node, key).and_then(Value::as_str).unwrap_or("").to_string() +} +fn type_name(node: &Value) -> &str { + node.get("typeName").and_then(Value::as_str).unwrap_or("") +} + +struct ViewCtx<'a> { + app: &'a App, + lines: Vec>, + targets: Vec, + form: Option, +} + +fn build_view(app: &App) -> (Vec>, Vec) { + let mut ctx = ViewCtx { app, lines: Vec::new(), targets: Vec::new(), form: None }; + match &app.root { + None => { + if app.streaming { + ctx.lines.push(Line::from(Span::styled( + "◐ OpenUI is thinking…", + Style::default().fg(Color::Cyan), + ))); + } else { + ctx.lines.push(Line::from(Span::styled( + "Ask for a chart, a table, a callout, or a long form — it renders here.", + Style::default().add_modifier(Modifier::DIM), + ))); + } + } + Some(root) => { + // The root is a Card; render its children. + if let Some(children) = prop(root, "children").and_then(Value::as_array) { + for child in children { + walk(&mut ctx, child); + } + } else { + walk(&mut ctx, root); + } + } + } + (ctx.lines, ctx.targets) +} + +fn push_line(ctx: &mut ViewCtx, line: Line<'static>) { + ctx.lines.push(line); +} + +fn walk(ctx: &mut ViewCtx, node: &Value) { + match type_name(node) { + "Card" => { + if let Some(children) = prop(node, "children").and_then(Value::as_array) { + for c in children { + walk(ctx, c); + } + } + } + "CardHeader" => { + let title = pstr(node, "title"); + if !title.is_empty() { + push_line(ctx, gradient_line(&title, true)); + } + let subtitle = pstr(node, "subtitle"); + if !subtitle.is_empty() { + push_line(ctx, Line::from(Span::styled(subtitle, Style::default().add_modifier(Modifier::DIM)))); + } + push_line(ctx, Line::from("")); + } + "TextContent" => { + let text = pstr(node, "text"); + let size = pstr(node, "size"); + let heavy = size.contains("heavy"); + let large = size == "large" || size == "large-heavy"; + if large { + push_line(ctx, gradient_line(&text, heavy)); + } else { + let mut style = Style::default(); + if heavy { + style = style.add_modifier(Modifier::BOLD); + } + if size == "small" { + style = style.add_modifier(Modifier::DIM); + } + push_line(ctx, Line::from(Span::styled(text, style))); + } + push_line(ctx, Line::from("")); + } + "Callout" => { + let variant = pstr(node, "variant"); + let (color, icon) = match variant.as_str() { + "success" => (Color::Green, "✓"), + "warning" => (Color::Yellow, "⚠"), + "error" => (Color::Red, "✕"), + "neutral" => (Color::Gray, "•"), + _ => (Color::Cyan, "ℹ"), + }; + let title = pstr(node, "title"); + push_line( + ctx, + Line::from(vec![ + Span::styled("▌ ", Style::default().fg(color)), + Span::styled(format!("{icon} {title}"), Style::default().fg(color).add_modifier(Modifier::BOLD)), + ]), + ); + let desc = pstr(node, "description"); + if !desc.is_empty() { + push_line( + ctx, + Line::from(vec![Span::styled("▌ ", Style::default().fg(color)), Span::raw(desc)]), + ); + } + push_line(ctx, Line::from("")); + } + "TagBlock" => { + let tag_colors = [Color::Cyan, Color::Magenta, Color::Green, Color::Yellow, Color::Blue]; + let mut spans = Vec::new(); + if let Some(tags) = prop(node, "tags").and_then(Value::as_array) { + for (i, t) in tags.iter().enumerate() { + let label = t.as_str().unwrap_or("").to_string(); + spans.push(Span::styled( + format!(" {label} "), + Style::default().bg(tag_colors[i % tag_colors.len()]).fg(Color::Black), + )); + spans.push(Span::raw(" ")); + } + } + push_line(ctx, Line::from(spans)); + push_line(ctx, Line::from("")); + } + "Table" => render_table(ctx, node), + "BarChart" => render_bar_chart(ctx, node), + "FollowUpBlock" => { + push_line(ctx, Line::from(Span::styled("Related:", Style::default().add_modifier(Modifier::DIM)))); + if let Some(items) = prop(node, "items").and_then(Value::as_array) { + for it in items { + let text = pstr(it, "text"); + let idx = ctx.targets.len(); + let focused = ctx.app.focus == Focus::Target(idx); + let style = if focused { + Style::default().fg(Color::Black).bg(Color::Magenta) + } else { + Style::default().fg(Color::Magenta) + }; + let top = ctx.lines.len(); + push_line(ctx, Line::from(Span::styled(format!("{} {}", if focused { "❯" } else { "•" }, text), style))); + ctx.targets.push(Target { kind: TargetKind::FollowUp { text }, top, height: 1 }); + } + } + push_line(ctx, Line::from("")); + } + "Form" => { + let name = pstr(node, "name"); + let prev_form = ctx.form.clone(); + ctx.form = Some(if name.is_empty() { "form".to_string() } else { name }); + if let Some(fields) = prop(node, "fields").and_then(Value::as_array) { + for fc in fields { + walk(ctx, fc); + } + } + if let Some(buttons) = prop(node, "buttons") { + walk(ctx, buttons); + } + ctx.form = prev_form; + push_line(ctx, Line::from("")); + } + "FormControl" => { + let label = pstr(node, "label"); + push_line(ctx, Line::from(Span::styled(label, Style::default().add_modifier(Modifier::BOLD)))); + let hint = pstr(node, "hint"); + if !hint.is_empty() { + push_line(ctx, Line::from(Span::styled(hint, Style::default().add_modifier(Modifier::DIM)))); + } + if let Some(input) = prop(node, "input") { + walk(ctx, input); + } + push_line(ctx, Line::from("")); + } + "Input" => render_input(ctx, node), + "Select" => render_select(ctx, node), + "Buttons" => { + if let Some(buttons) = prop(node, "buttons").and_then(Value::as_array) { + for b in buttons { + walk(ctx, b); + } + } + } + "Button" => render_button(ctx, node), + other => { + push_line(ctx, Line::from(Span::styled(format!("[unsupported: {other}]"), Style::default().fg(Color::Yellow)))); + } + } +} + +fn render_table(ctx: &mut ViewCtx, node: &Value) { + let empty = Vec::new(); + let columns = prop(node, "columns").and_then(Value::as_array).unwrap_or(&empty); + let headers: Vec = columns.iter().map(|c| pstr(c, "label")).collect(); + let data: Vec> = columns + .iter() + .map(|c| { + prop(c, "data") + .and_then(Value::as_array) + .map(|d| d.iter().map(cell_to_string).collect()) + .unwrap_or_default() + }) + .collect(); + let rows = data.iter().map(|d| d.len()).max().unwrap_or(0); + let widths: Vec = headers + .iter() + .enumerate() + .map(|(ci, h)| { + let dw = data[ci].iter().map(|s| s.chars().count()).max().unwrap_or(0); + h.chars().count().max(dw) + }) + .collect(); + let pad = |s: &str, w: usize| format!("{:>() + .join(" "); + push_line(ctx, Line::from(Span::styled(header_line, Style::default().add_modifier(Modifier::BOLD)))); + let sep: String = widths.iter().map(|w| "─".repeat(*w)).collect::>().join(" "); + push_line(ctx, Line::from(Span::styled(sep, Style::default().add_modifier(Modifier::DIM)))); + for r in 0..rows { + let row: String = (0..headers.len()) + .map(|ci| pad(data[ci].get(r).map(String::as_str).unwrap_or(""), widths[ci])) + .collect::>() + .join(" "); + push_line(ctx, Line::from(row)); + } + push_line(ctx, Line::from("")); +} + +fn cell_to_string(v: &Value) -> String { + match v { + Value::String(s) => s.clone(), + Value::Number(n) => n.to_string(), + Value::Bool(b) => b.to_string(), + _ => String::new(), + } +} + +fn render_bar_chart(ctx: &mut ViewCtx, node: &Value) { + let ylabel = pstr(node, "yLabel"); + if !ylabel.is_empty() { + push_line(ctx, Line::from(Span::styled(ylabel, Style::default().add_modifier(Modifier::DIM)))); + } + let empty = Vec::new(); + let labels: Vec = prop(node, "labels") + .and_then(Value::as_array) + .unwrap_or(&empty) + .iter() + .map(|v| v.as_str().unwrap_or("").to_string()) + .collect(); + let series = prop(node, "series").and_then(Value::as_array).cloned().unwrap_or_default(); + let mut rows: Vec<(String, f64)> = Vec::new(); + let multi = series.len() > 1; + for (i, label) in labels.iter().enumerate() { + for s in &series { + let cat = pstr(s, "category"); + let vals = prop(s, "values").and_then(Value::as_array).cloned().unwrap_or_default(); + let v = vals.get(i).and_then(Value::as_f64).unwrap_or(0.0); + let name = if multi { format!("{label} · {cat}") } else { label.clone() }; + rows.push((name, v)); + } + } + let max = rows.iter().map(|(_, v)| v.abs()).fold(1.0_f64, f64::max); + let name_w = rows.iter().map(|(n, _)| n.chars().count()).max().unwrap_or(0); + for (name, v) in rows { + let bar_len = ((v.abs() / max) * 32.0).round() as usize; + let bar = "█".repeat(bar_len.max(1)); + push_line( + ctx, + Line::from(vec![ + Span::raw(format!("{: Span<'static> { + // Single-color approximation for a bar (cyan-ish); per-char gradient in a + // Span isn't possible, so we use the gradient start color. + Span::styled(text.to_string(), Style::default().fg(Color::Rgb(GRAD_START.0, GRAD_START.1, GRAD_START.2))) +} + +fn trim_num(v: f64) -> String { + if v.fract() == 0.0 { + format!("{}", v as i64) + } else { + format!("{v}") + } +} + +fn render_input(ctx: &mut ViewCtx, node: &Value) { + let form = ctx.form.clone().unwrap_or_default(); + let name = pstr(node, "name"); + let idx = ctx.targets.len(); + let focused = ctx.app.focus == Focus::Target(idx); + let value = ctx.app.fields.get(&(form.clone(), name.clone())).cloned().unwrap_or_default(); + let shown = if value.is_empty() { pstr(node, "placeholder") } else { value.clone() }; + let style = if focused { + Style::default().fg(Color::Cyan) + } else { + Style::default().fg(Color::Gray) + }; + let mut spans = vec![ + Span::styled(if focused { "❯ " } else { " " }, style), + Span::styled(shown, style), + ]; + if focused { + spans.push(Span::styled("▏", Style::default().fg(Color::Cyan))); + } + let top = ctx.lines.len(); + push_line(ctx, Line::from(spans)); + ctx.targets.push(Target { kind: TargetKind::Input { form, name }, top, height: 1 }); +} + +fn render_select(ctx: &mut ViewCtx, node: &Value) { + let form = ctx.form.clone().unwrap_or_default(); + let name = pstr(node, "name"); + let idx = ctx.targets.len(); + let focused = ctx.app.focus == Focus::Target(idx); + let empty = Vec::new(); + let items = prop(node, "items").and_then(Value::as_array).unwrap_or(&empty); + let options: Vec<(String, String)> = items + .iter() + .map(|it| { + let value = pstr(it, "value"); + let label = { + let l = pstr(it, "label"); + if l.is_empty() { value.clone() } else { l } + }; + (value, label) + }) + .collect(); + let selected = ctx.app.selects.get(&(form.clone(), name.clone())).cloned().unwrap_or_default(); + let cursor = *ctx.app.select_cursor.get(&(form.clone(), name.clone())).unwrap_or(&0); + let top = ctx.lines.len(); + for (i, (value, label)) in options.iter().enumerate() { + let is_sel = *value == selected; + let is_cursor = focused && i == cursor; + let marker = if is_sel { "(•)" } else { "( )" }; + let arrow = if is_cursor { "❯ " } else { " " }; + let style = if is_cursor { + Style::default().fg(Color::Cyan) + } else if is_sel { + Style::default().fg(Color::Green) + } else { + Style::default() + }; + push_line(ctx, Line::from(Span::styled(format!("{arrow}{marker} {}. {label}", i + 1), style))); + } + if focused { + push_line(ctx, Line::from(Span::styled(" click, ↑↓, or number to choose", Style::default().add_modifier(Modifier::DIM)))); + } + let height = ctx.lines.len() - top; + ctx.targets.push(Target { kind: TargetKind::Select { form, name, options }, top, height }); +} + +fn render_button(ctx: &mut ViewCtx, node: &Value) { + let label = { + let l = pstr(node, "label"); + if l.is_empty() { "Button".to_string() } else { l } + }; + let message = action_message(node).unwrap_or_else(|| label.clone()); + let idx = ctx.targets.len(); + let focused = ctx.app.focus == Focus::Target(idx); + let style = if focused { + Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD) + } else { + Style::default().fg(Color::Cyan) + }; + let top = ctx.lines.len(); + push_line(ctx, Line::from(Span::styled(format!(" [ {label} ]"), style))); + let form = ctx.form.clone(); + ctx.targets.push(Target { kind: TargetKind::Button { message, form }, top, height: 1 }); +} + +/// Extract the @ToAssistant message from a Button's evaluated action plan. +fn action_message(node: &Value) -> Option { + let steps = prop(node, "action")?.get("steps")?.as_array()?; + for step in steps { + if let Some(msg) = step.get("message").and_then(Value::as_str) { + return Some(msg.to_string()); + } + } + None +} + +// ─────────────────────────── input handling ─────────────────────────── + +fn send_message(app: &mut App, bridge_stdin: &mut ChildStdin, content: String) { + if app.streaming { + return; + } + let payload = serde_json::json!({ "type": "send", "content": content }); + let _ = writeln!(bridge_stdin, "{payload}"); + let _ = bridge_stdin.flush(); + app.streaming = true; + app.scroll = 0; + app.focus = Focus::Composer; + app.status = "sent — streaming response…".to_string(); +} + +fn focusable_count(app: &App) -> usize { + app.targets.len() +} + +fn cycle_focus(app: &mut App, forward: bool) { + let n = focusable_count(app); + app.focus = match app.focus { + Focus::Composer => { + if n == 0 { + Focus::Composer + } else if forward { + Focus::Target(0) + } else { + Focus::Target(n - 1) + } + } + Focus::Target(i) => { + if forward { + if i + 1 >= n { Focus::Composer } else { Focus::Target(i + 1) } + } else if i == 0 { + Focus::Composer + } else { + Focus::Target(i - 1) + } + } + }; +} + +fn activate_target(app: &mut App, idx: usize, bridge_stdin: &mut ChildStdin) { + let Some(target) = app.targets.get(idx).cloned() else { return }; + match target.kind { + TargetKind::FollowUp { text } => send_message(app, bridge_stdin, text), + TargetKind::Button { message, form } => { + let content = if let Some(form) = form { + let mut values = serde_json::Map::new(); + for ((f, n), v) in &app.fields { + if *f == form { + values.insert(n.clone(), Value::String(v.clone())); + } + } + for ((f, n), v) in &app.selects { + if *f == form { + values.insert(n.clone(), Value::String(v.clone())); + } + } + format!("{message}\n\n[form \"{form}\" values: {}]", Value::Object(values)) + } else { + message + }; + send_message(app, bridge_stdin, content); + } + TargetKind::Input { .. } => {} // focusing handled by caller; typing edits it + TargetKind::Select { form, name, options } => { + let cursor = *app.select_cursor.get(&(form.clone(), name.clone())).unwrap_or(&0); + if let Some((value, _)) = options.get(cursor) { + app.selects.insert((form, name), value.clone()); + } + } + } +} + +fn handle_key(app: &mut App, key: KeyEvent, bridge_stdin: &mut ChildStdin) { + if key.modifiers.contains(KeyModifiers::CONTROL) && matches!(key.code, KeyCode::Char('c')) { + app.should_quit = true; + return; + } + + match app.focus { + Focus::Composer => match key.code { + KeyCode::Enter => { + let text = app.composer.trim().to_string(); + if !text.is_empty() { + app.composer.clear(); + send_message(app, bridge_stdin, text); + } + } + KeyCode::Backspace => { + app.composer.pop(); + } + KeyCode::Tab => cycle_focus(app, true), + KeyCode::BackTab => cycle_focus(app, false), + KeyCode::Up => app.scroll = app.scroll.saturating_sub(1), + KeyCode::Down => app.scroll += 1, + KeyCode::Char(c) => app.composer.push(c), + _ => {} + }, + Focus::Target(i) => { + let kind = app.targets.get(i).map(|t| t.kind.clone()); + match key.code { + KeyCode::Tab => cycle_focus(app, true), + KeyCode::BackTab => cycle_focus(app, false), + KeyCode::Enter => activate_target(app, i, bridge_stdin), + KeyCode::Backspace => { + if let Some(TargetKind::Input { form, name }) = kind { + if let Some(v) = app.fields.get_mut(&(form, name)) { + v.pop(); + } + } + } + KeyCode::Up => { + if let Some(TargetKind::Select { form, name, .. }) = kind { + let e = app.select_cursor.entry((form, name)).or_insert(0); + *e = e.saturating_sub(1); + } else { + cycle_focus(app, false); + } + } + KeyCode::Down => { + if let Some(TargetKind::Select { form, name, options }) = kind { + let e = app.select_cursor.entry((form, name)).or_insert(0); + if *e + 1 < options.len() { + *e += 1; + } + } else { + cycle_focus(app, true); + } + } + KeyCode::Char(c) => match kind { + Some(TargetKind::Input { form, name }) => { + app.fields.entry((form, name)).or_default().push(c); + } + Some(TargetKind::Select { form, name, options }) => { + if let Some(d) = c.to_digit(10) { + let n = d as usize; + if n >= 1 && n <= options.len() { + app.select_cursor.insert((form.clone(), name.clone()), n - 1); + app.selects.insert((form, name), options[n - 1].0.clone()); + } + } + } + _ => {} + }, + _ => {} + } + } + } +} + +fn handle_mouse(app: &mut App, m: MouseEvent, bridge_stdin: &mut ChildStdin) { + match m.kind { + MouseEventKind::ScrollDown => app.scroll += 1, + MouseEventKind::ScrollUp => app.scroll = app.scroll.saturating_sub(1), + MouseEventKind::Down(MouseButton::Left) => { + let (col, row) = (m.column, m.row); + let ca = app.content_area; + // Inside content border? + if row > ca.y && row < ca.y + ca.height.saturating_sub(1) && col > ca.x && col < ca.x + ca.width.saturating_sub(1) { + let content_line = (row - ca.y - 1) as usize + app.scroll; + // Find the target containing this line. + let mut hit: Option = None; + let mut sel_option: Option = None; + for (ti, t) in app.targets.iter().enumerate() { + if content_line >= t.top && content_line < t.top + t.height { + hit = Some(ti); + if let TargetKind::Select { .. } = t.kind { + sel_option = Some(content_line - t.top); + } + break; + } + } + if let Some(ti) = hit { + app.focus = Focus::Target(ti); + let kind = app.targets[ti].kind.clone(); + match kind { + TargetKind::Button { .. } | TargetKind::FollowUp { .. } => { + activate_target(app, ti, bridge_stdin) + } + TargetKind::Select { form, name, options } => { + if let Some(oi) = sel_option { + if oi < options.len() { + app.select_cursor.insert((form.clone(), name.clone()), oi); + app.selects.insert((form, name), options[oi].0.clone()); + } + } + } + TargetKind::Input { .. } => {} + } + } + } else { + // Clicking outside the content (e.g., composer) focuses the composer. + app.focus = Focus::Composer; + } + } + _ => {} + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 759229d5a..49a13e649 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -320,7 +320,7 @@ importers: version: 0.0.45 '@ag-ui/mastra': specifier: ^1.0.1 - version: 1.0.1(mo2d2k6urt4bfbrhfsfscm4ram) + version: 1.0.1(dd1f7da5383fb7dbf1cafaa81aeabe83) '@mastra/core': specifier: 1.15.0 version: 1.15.0(@cfworker/json-schema@4.1.1)(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(openapi-types@12.1.3)(zod@4.3.6) @@ -703,6 +703,28 @@ importers: specifier: ~5.9.2 version: 5.9.3 + examples/openui-rust-tui/bridge: + dependencies: + '@openuidev/lang-core': + specifier: workspace:* + version: link:../../../packages/lang-core + openai: + specifier: ^6.22.0 + version: 6.34.0(ws@8.20.0)(zod@4.3.6) + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@types/node': + specifier: ^20 + version: 20.19.35 + tsx: + specifier: ^4.19.2 + version: 4.20.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + examples/react-email: dependencies: '@openuidev/cli': @@ -2420,10 +2442,12 @@ packages: '@copilotkitnext/agent@0.0.0-mme-ag-ui-0-0-46-20260227141603': resolution: {integrity: sha512-HAaAVKWD+WS1/GTxY6xLMj65Ro9evnOM5UC0DueTFwlmgCkuHPVE4rDveiGVaNc0x4X75tofi5Ul9g6Tb9FT/w==} engines: {node: '>=18'} + deprecated: Moved into @copilotkit/runtime. Import from '@copilotkit/runtime/v2'. '@copilotkitnext/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603': resolution: {integrity: sha512-c6vosi7xzKvyujmmwb4rNvAnlr656ybCrPeeX3kO1V70zrnUVkS4EXlSz1T0fGqqNgf5Tfqmssy3xqyLLZwgbQ==} engines: {node: '>=18'} + deprecated: Moved into @copilotkit/runtime. Import from '@copilotkit/runtime/v2'. peerDependencies: '@ag-ui/client': 0.0.46 '@ag-ui/core': 0.0.46 @@ -2433,6 +2457,7 @@ packages: '@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603': resolution: {integrity: sha512-tbw37m+MgOO58dxYsXvGTN9YqHt6DPLMqtDEQftJHrUrQkNqXOxhOporx4p2DG0R+RiQqWrT+r44D2eRCQhlkA==} engines: {node: '>=18'} + deprecated: Use @copilotkit/shared instead. '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} @@ -3583,89 +3608,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -4078,72 +4119,84 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-gnu@16.1.6': resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-gnu@16.2.3': resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@15.5.12': resolution: {integrity: sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-arm64-musl@16.2.3': resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@15.5.12': resolution: {integrity: sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-gnu@16.2.3': resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@15.5.12': resolution: {integrity: sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-linux-x64-musl@16.2.3': resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@15.5.12': resolution: {integrity: sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==} @@ -4580,48 +4633,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-arm64-musl@0.117.0': resolution: {integrity: sha512-C3zapJconWpl2Y7LR3GkRkH6jxpuV2iVUfkFcHT5Ffn4Zu7l88mZa2dhcfdULZDybN1Phka/P34YUzuskUUrXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-minify/binding-linux-ppc64-gnu@0.117.0': resolution: {integrity: sha512-2T/Bm+3/qTfuNS4gKSzL8qbiYk+ErHW2122CtDx+ilZAzvWcJ8IbqdZIbEWOlwwe03lESTxPwTBLFqVgQU2OeQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-riscv64-gnu@0.117.0': resolution: {integrity: sha512-MKLjpldYkeoB4T+yAi4aIAb0waifxUjLcKkCUDmYAY3RqBJTvWK34KtfaKZL0IBMIXfD92CbKkcxQirDUS9Xcg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-riscv64-musl@0.117.0': resolution: {integrity: sha512-UFVcbPvKUStry6JffriobBp8BHtjmLLPl4bCY+JMxIn/Q3pykCpZzRwFTcDurG/kY8tm+uSNfKKdRNa5Nh9A7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-minify/binding-linux-s390x-gnu@0.117.0': resolution: {integrity: sha512-B9GyPQ1NKbvpETVAMyJMfRlD3c6UJ7kiuFUAlx9LTYiQL+YIyT6vpuRlq1zgsXxavZluVrfeJv6x0owV4KDx4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-x64-gnu@0.117.0': resolution: {integrity: sha512-fXfhtr+WWBGNy4M5GjAF5vu/lpulR4Me34FjTyaK9nDrTZs7LM595UDsP1wliksqp4hD/KdoqHGmbCrC+6d4vA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-x64-musl@0.117.0': resolution: {integrity: sha512-jFBgGbx1oLadb83ntJmy1dWlAHSQanXTS21G4PgkxyONmxZdZ/UMKr7KsADzMuoPsd2YhJHxzRpwJd9U+4BFBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-minify/binding-openharmony-arm64@0.117.0': resolution: {integrity: sha512-nxPd9vx1vYz8IlIMdl9HFdOK/ood1H5hzbSFsyO8JU55tkcJoBL8TLCbuFf9pHpOy27l2gcPyV6z3p4eAcTH5Q==} @@ -4699,48 +4760,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-arm64-musl@0.117.0': resolution: {integrity: sha512-QagKTDF4lrz8bCXbUi39Uq5xs7C7itAseKm51f33U+Dyar9eJY/zGKqfME9mKLOiahX7Fc1J3xMWVS0AdDXLPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-ppc64-gnu@0.117.0': resolution: {integrity: sha512-RPddpcE/0xxWaommWy0c5i/JdrXcXAkxBS2GOrAUh5LKmyCh03hpJedOAWszG4ADsKQwoUQQ1/tZVGRhZIWtKA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-gnu@0.117.0': resolution: {integrity: sha512-ur/WVZF9FSOiZGxyP+nfxZzuv6r5OJDYoVxJnUR7fM/hhXLh4V/be6rjbzm9KLCDBRwYCEKJtt+XXNccwd06IA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-musl@0.117.0': resolution: {integrity: sha512-ujGcAx8xAMvhy7X5sBFi3GXML1EtyORuJZ5z2T6UV3U416WgDX/4OCi3GnoteeenvxIf6JgP45B+YTHpt71vpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-s390x-gnu@0.117.0': resolution: {integrity: sha512-hbsfKjUwRjcMZZvvmpZSc+qS0bHcHRu8aV/I3Ikn9BzOA0ZAgUE7ctPtce5zCU7bM8dnTLi4sJ1Pi9YHdx6Urw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-gnu@0.117.0': resolution: {integrity: sha512-1QrTrf8rige7UPJrYuDKJLQOuJlgkt+nRSJLBMHWNm9TdivzP48HaK3f4q18EjNlglKtn03lgjMu4fryDm8X4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-musl@0.117.0': resolution: {integrity: sha512-gRvK6HPzF5ITRL68fqb2WYYs/hGviPIbkV84HWCgiJX+LkaOpp+HIHQl3zVZdyKHwopXToTbXbtx/oFjDjl8pg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-parser/binding-openharmony-arm64@0.117.0': resolution: {integrity: sha512-QPJvFbnnDZZY7xc+xpbIBWLThcGBakwaYA9vKV8b3+oS5MGfAZUoTFJcix5+Zg2Ri46sOfrUim6Y6jsKNcssAQ==} @@ -4824,48 +4893,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-arm64-musl@0.117.0': resolution: {integrity: sha512-ykxpPQp0eAcSmhy0Y3qKvdanHY4d8THPonDfmCoktUXb6r0X6qnjpJB3V+taN1wevW55bOEZd97kxtjTKjqhmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-ppc64-gnu@0.117.0': resolution: {integrity: sha512-Rvspti4Kr7eq6zSrURK5WjscfWQPvmy/KjJZV45neRKW8RLonE3r9+NgrwSLGoHvQ3F24fbqlkplox1RtlhH5A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-gnu@0.117.0': resolution: {integrity: sha512-Dr2ZW9ZZ4l1eQ5JUEUY3smBh4JFPCPuybWaDZTLn3ADZjyd8ZtNXEjeMT8rQbbhbgSL9hEgbwaqraole3FNThQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-musl@0.117.0': resolution: {integrity: sha512-oD1Bnes1bIC3LVBSrWEoSUBj6fvatESPwAVWfJVGVQlqWuOs/ZBn1e4Nmbipo3KGPHK7DJY75r/j7CQCxhrOFQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-s390x-gnu@0.117.0': resolution: {integrity: sha512-qT//IAPLvse844t99Kff5j055qEbXfwzWgvCMb0FyjisnB8foy25iHZxZIocNBe6qwrCYWUP1M8rNrB/WyfS1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-gnu@0.117.0': resolution: {integrity: sha512-2YEO5X+KgNzFqRVO5dAkhjcI5gwxus4NSWVl/+cs2sI6P0MNPjqE3VWPawl4RTC11LvetiiZdHcujUCPM8aaUw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-musl@0.117.0': resolution: {integrity: sha512-3wqWbTSaIFZvDr1aqmTul4cg8PRWYh6VC52E8bLI7ytgS/BwJLW+sDUU2YaGIds4sAf/1yKeJRmudRCDPW9INg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-transform/binding-openharmony-arm64@0.117.0': resolution: {integrity: sha512-Ebxx6NPqhzlrjvx4+PdSqbOq+li0f7X59XtJljDghkbJsbnkHvhLmPR09ifHt5X32UlZN63ekjwcg/nbmHLLlA==} @@ -4925,36 +5002,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.5.6': resolution: {integrity: sha512-byAiBZ1t3tXQvc8dMD/eoyE7lTXYorhn+6uVW5AC+JGI1KtJC/LvDche5cfUE+qiefH+Ybq0bUCJU0aB1cSHUA==} @@ -6883,6 +6966,7 @@ packages: '@rolldown/binding-darwin-arm64@1.0.0-rc.16': resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==} engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] os: [darwin] '@rolldown/binding-darwin-x64@1.0.0-rc.12': @@ -6908,36 +6992,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} @@ -7107,121 +7197,145 @@ packages: resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.60.1': resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.43.0': resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -7752,48 +7866,56 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -7876,24 +7998,28 @@ packages: engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@takumi-rs/core-linux-arm64-musl@0.68.17': resolution: {integrity: sha512-4CiEF518wDnujF0fjql2XN6uO+OXl0svy0WgAF2656dCx2gJtWscaHytT2rsQ0ZmoFWE0dyWcDW1g/FBVPvuvA==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@takumi-rs/core-linux-x64-gnu@0.68.17': resolution: {integrity: sha512-jm8lTe2E6Tfq2b97GJC31TWK1JAEv+MsVbvL9DCLlYcafgYFlMXDUnOkZFMjlrmh0HcFAYDaBkniNDgIQfXqzg==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@takumi-rs/core-linux-x64-musl@0.68.17': resolution: {integrity: sha512-nbdzQgC4ywzltDDV1fer1cKswwGE+xXZHdDiacdd7RM5XBng209Bmo3j1iv9dsX+4xXhByzCCGbxdWhhHqVXmw==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@takumi-rs/core-win32-arm64-msvc@0.68.17': resolution: {integrity: sha512-kE4F0LRmuhSwiNkFG7dTY9ID8+B7zb97QedyN/IO2fBJmRQDkqCGcip2gloh8YPPhCuKGjCqqqh2L+Tg9PKW7w==} @@ -8369,41 +8495,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -12098,48 +12232,56 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -16294,12 +16436,12 @@ snapshots: - react - react-dom - '@ag-ui/mastra@1.0.1(mo2d2k6urt4bfbrhfsfscm4ram)': + '@ag-ui/mastra@1.0.1(dd1f7da5383fb7dbf1cafaa81aeabe83)': dependencies: '@ag-ui/client': 0.0.49 '@ag-ui/core': 0.0.45 '@ai-sdk/ui-utils': 1.2.11(zod@4.3.6) - '@copilotkit/runtime': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/encoder@0.0.49)(@cfworker/json-schema@4.1.1)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(@langchain/langgraph-sdk@0.1.10(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@copilotkit/runtime': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/encoder@0.0.46)(@cfworker/json-schema@4.1.1)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(@langchain/langgraph-sdk@0.1.10(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mastra/client-js': 1.11.2(@cfworker/json-schema@4.1.1)(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(openapi-types@12.1.3)(zod@4.3.6) '@mastra/core': 1.15.0(@cfworker/json-schema@4.1.1)(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(openapi-types@12.1.3)(zod@4.3.6) rxjs: 7.8.1 @@ -17251,7 +17393,7 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@copilotkit/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/encoder@0.0.49)(@cfworker/json-schema@4.1.1)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(@langchain/langgraph-sdk@0.1.10(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@copilotkit/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/encoder@0.0.46)(@cfworker/json-schema@4.1.1)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(@langchain/langgraph-sdk@0.1.10(@langchain/core@0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@ag-ui/client': 0.0.46 '@ag-ui/core': 0.0.46 @@ -17260,7 +17402,7 @@ snapshots: '@ai-sdk/openai': 2.0.101(zod@3.25.76) '@copilotkit/shared': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/core@0.0.46) '@copilotkitnext/agent': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@cfworker/json-schema@4.1.1) - '@copilotkitnext/runtime': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.49)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603) + '@copilotkitnext/runtime': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.46)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603) '@graphql-yoga/plugin-defer-stream': 3.19.0(graphql-yoga@5.19.0(graphql@16.13.2))(graphql@16.13.2) '@hono/node-server': 1.19.12(hono@4.12.9) '@langchain/core': 0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.20.0)(zod@4.3.6)) @@ -17320,11 +17462,11 @@ snapshots: - '@cfworker/json-schema' - supports-color - '@copilotkitnext/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.49)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)': + '@copilotkitnext/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.46)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)': dependencies: '@ag-ui/client': 0.0.46 '@ag-ui/core': 0.0.46 - '@ag-ui/encoder': 0.0.49 + '@ag-ui/encoder': 0.0.46 '@copilotkitnext/shared': 0.0.0-mme-ag-ui-0-0-46-20260227141603 cors: 2.8.6 express: 4.22.1 @@ -19393,7 +19535,7 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/vite-builder@3.21.2(thirvmyz7u7sotpvl5josuvsem)': + '@nuxt/vite-builder@3.21.2(cda632d3acfa8c47554ee5be1e146aef)': dependencies: '@nuxt/kit': 3.21.2(magicast@0.5.2) '@rollup/plugin-replace': 6.0.3(rollup@4.60.1) @@ -30785,7 +30927,7 @@ snapshots: '@nuxt/nitro-server': 3.21.2(db0@0.3.4)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@3.21.2(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@parcel/watcher@2.5.1)(@types/node@25.3.2)(@vue/compiler-sfc@3.5.31)(cac@6.7.14)(db0@0.3.4)(eslint@9.29.0(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(optionator@0.9.4)(rolldown@1.0.0-rc.12(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.12(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1))(rollup@4.60.1))(rollup@4.60.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.3))(rolldown@1.0.0-rc.12(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1))(typescript@5.9.3) '@nuxt/schema': 3.21.2 '@nuxt/telemetry': 2.7.0(@nuxt/kit@3.21.2(magicast@0.5.2)) - '@nuxt/vite-builder': 3.21.2(thirvmyz7u7sotpvl5josuvsem) + '@nuxt/vite-builder': 3.21.2(cda632d3acfa8c47554ee5be1e146aef) '@unhead/vue': 2.1.13(vue@3.5.31(typescript@5.9.3)) '@vue/shared': 3.5.31 c12: 3.3.3(magicast@0.5.2) @@ -33731,7 +33873,7 @@ snapshots: tsx@4.20.3: dependencies: esbuild: 0.25.12 - get-tsconfig: 4.10.1 + get-tsconfig: 4.13.7 optionalDependencies: fsevents: 2.3.3