diff --git a/.gitignore b/.gitignore index b186605..c8a119f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ dist node_modules .DS_Store +gui/target +*.bun-build diff --git a/CHANGELOG.md b/CHANGELOG.md index 246de57..236df6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- [2026-09-24] a native macOS app for tokenmaxx, with a menu bar item - [2026-08-21] codex keeps its shell and apply_patch tools on the gpt-5.6 models - [2026-08-18] meter clients that hang up early - [2026-08-18] reclaim bare provider tables diff --git a/README.md b/README.md index 4354428..882f4d9 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,19 @@ Run `tokenmaxx`. **Accounts** shows every account and its live rate-limit window routing, auto-rotation, threshold, and cooldown, tuned per provider +## The app + +`gui/` is a native macOS app built with [GPUI](https://gpui.rs) and [GPUI Kit](https://gpui-kit.com). It covers everything the dashboard does: accounts, switching, sign-in, reset credits, analytics, and every setting. It can also live in the menu bar, where each provider's accounts and their rate-limit windows are one click away. + +The app ships its own compiled `tokenmaxx` and drives the same daemon and `~/.tokenmaxx` state as the command line, so both always agree. Settings → System → Command-line tool links that binary onto your PATH. Settings → App picks the appearance (auto, light, dark), whether tokenmaxx shows in the menu bar, the Dock, or both, and which terminal runs subscription sign-in. + +```bash +bun run gui:dev # run against a dev build of the CLI +bun run gui:bundle # gui/target/tokenmaxx.app +``` + +Building needs Rust (stable) and Xcode's command-line tools. + ## Auto-rotation Turn it on and tokenmaxx watches the active account's rate-limit windows. When the fullest one crosses your threshold, it switches to whichever of your accounts has the most room. The default threshold is 90%, which leaves the last stretch of every window alone in case you want it later. If an account hits a hard limit in the middle of a request, the proxy retries that request on your next account with room. diff --git a/biome.json b/biome.json index d2f7e55..72cfbd0 100644 --- a/biome.json +++ b/biome.json @@ -4,6 +4,7 @@ "includes": [ "**", "!dist", + "!gui", "!node_modules", "!remotion", "!assets/generated", diff --git a/gui/Cargo.lock b/gui/Cargo.lock new file mode 100644 index 0000000..57cdf9c --- /dev/null +++ b/gui/Cargo.lock @@ -0,0 +1,9488 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "phf 0.13.1", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "ashpd" +version = "0.13.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "getrandom 0.4.3", + "serde", + "serde_repr", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.21", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide 0.8.9", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base62" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a647dce9631f152a99ec16cfc09267ae55fe280d5dc288964027d458076323" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.2", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.2", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.2", + "polling", + "rustix", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" +dependencies = [ + "bitflags 2.13.2", + "block", + "cocoa-foundation 0.2.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.13.2", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "compression-codecs" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.13.2", + "block", + "cfg-if", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.1", + "core-graphics2", + "io-surface", + "libc", + "metal", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" +dependencies = [ + "bitflags 2.13.2", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 2.1.3", + "self_cell", + "skrifa 0.40.0", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "libc", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.6+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[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 = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +dependencies = [ + "getrandom 0.4.3", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.9", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "font-types" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link 0.2.1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.21", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.2", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "gpui-base" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e904ee0d351f4e85b967e26ae986d3aa195a8d9d35fcd18184a2c40989694e" +dependencies = [ + "aho-corasick", + "anyhow", + "ashpd", + "async-channel", + "chrono", + "data-url", + "futures", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", + "html5ever", + "instant", + "lsp-types", + "markdown", + "markup5ever_rcdom", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "raw-window-handle", + "regex", + "ropey", + "schemars", + "serde", + "serde_json", + "smallvec", + "smol", + "tracing", + "unicode-segmentation", + "web-time", + "windows 0.58.0", +] + +[[package]] +name = "gpui-component" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf6c72516b44bb2e66a1bd2ccf77a122ecd1b54a75559ac299bb296bfcb5bb7" +dependencies = [ + "anyhow", + "chrono", + "core-text", + "enum-iterator", + "gpui-base", + "gpui-component-macros", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", + "instant", + "itertools 0.13.0", + "log", + "lsp-types", + "markdown", + "notify", + "num-traits", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "once_cell", + "paste", + "raw-window-handle", + "regex", + "resvg 0.45.1", + "ropey", + "rust-i18n", + "schemars", + "serde", + "serde_json", + "serde_repr", + "smallvec", + "smol", + "tracing", + "uuid", + "windows 0.58.0", +] + +[[package]] +name = "gpui-component-macros" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3867545508c7cf6a5f6da586f60d894a3f8e7cce94b7fe82df7fad5f868ec20e" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-kit" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42732dc20f50926278b2abf7a99d427a7741ab28bca6bfb49685f9ce3a53a1c7" +dependencies = [ + "gpui-base", + "gpui-component", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-platform", + "gpui-pre-web", +] + +[[package]] +name = "gpui-kit-assets" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111d3d03a68454318a193f137419864ccebdc7d3dc0194b0b0b3f818a6bc3d55" +dependencies = [ + "anyhow", + "gpui-pre", + "gpui-pre-reqwest", + "log", + "rust-embed", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "gpui-pre" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0437c0b83e636a92bd1a39fa1d05fb632ae671289537497b35871ffbe231b84" +dependencies = [ + "accesskit", + "anyhow", + "async-channel", + "async-task", + "bindgen", + "bitflags 2.13.2", + "chrono", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-macros", + "gpui-pre-refineable", + "gpui-pre-scheduler", + "gpui-pre-shared-string", + "gpui-pre-sum-tree", + "gpui-pre-util", + "gpui-pre-util-macros", + "heapless", + "image", + "inventory", + "itertools 0.14.0", + "log", + "lyon", + "num_cpus", + "objc2-core-foundation", + "objc2-core-video", + "parking", + "parking_lot", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "rand 0.9.5", + "raw-window-handle", + "regex", + "resvg 0.46.0", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.1", + "strum", + "taffy", + "thiserror 2.0.21", + "ttf-parser", + "url", + "usvg 0.46.0", + "uuid", + "waker-fn", + "web-time", + "windows 0.62.2", + "zed-font-kit", + "zed-scap", +] + +[[package]] +name = "gpui-pre-apple" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af534f0746eb9e014114bcc0ee00dc250c4b8d75df2e437180c403aa65649c1" +dependencies = [ + "anyhow", + "block", + "cbindgen", + "cocoa 0.26.1", + "core-foundation 0.10.1", + "core-video", + "derive_more", + "etagere", + "foreign-types", + "gpui-pre", + "image", + "log", + "metal", + "objc", + "objc2 0.6.4", + "parking_lot", +] + +[[package]] +name = "gpui-pre-collections" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e7a65204039187d1f4b3d4c2e0233eeac54d4acb0b13f9dc1a3b5adad26c1f" +dependencies = [ + "gpui-pre-util", + "indexmap", + "rustc-hash 2.1.3", +] + +[[package]] +name = "gpui-pre-derive-refineable" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79dfb4a05f3cd9893d7768dbf5dba1bbbe99cab7394e89b360ccb3b2bcb6b34e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-http-client" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd5a7542c77887a5aff997d78c95884119c8463409569b63fcf060956da1682" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "gpui-pre-linux" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab655cda1d6dcd549ac869e8678babc84cf573ab6794a1fe1a39c6c70c60b48" +dependencies = [ + "accesskit", + "accesskit_unix", + "anyhow", + "as-raw-xcb-connection", + "ashpd", + "bitflags 2.13.2", + "bytemuck", + "calloop", + "calloop-wayland-source", + "filedescriptor", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-util", + "gpui-pre-wgpu", + "libc", + "log", + "notify-rust", + "oo7", + "open", + "parking_lot", + "raw-window-handle", + "smallvec", + "smol", + "strum", + "url", + "uuid", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-plasma", + "wayland-protocols-wlr", + "x11-clipboard", + "x11rb", + "xkbcommon", + "zed-scap", + "zed-xim", +] + +[[package]] +name = "gpui-pre-macos" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951d17e41a72067ad2d35c60e08a62e8ad7fa9400b41c802e26cfb459f6c05da" +dependencies = [ + "accesskit", + "accesskit_macos", + "anyhow", + "async-task", + "block2 0.6.2", + "cocoa 0.26.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "ctor", + "dispatch2", + "foreign-types", + "futures", + "gpui-pre", + "gpui-pre-apple", + "gpui-pre-collections", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "libc", + "log", + "mach2", + "objc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-graphics", + "objc2-core-media", + "objc2-foundation 0.3.2", + "objc2-screen-capture-kit", + "objc2-user-notifications", + "parking_lot", + "pathfinder_geometry", + "raw-window-handle", + "semver", + "smallvec", + "strum", + "uuid", + "zed-font-kit", +] + +[[package]] +name = "gpui-pre-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6324092b40ea8ec7c327a4a28427e366a489b4d5d23b5d1bd0243fc2f4ca7a9e" +dependencies = [ + "heck 0.5.0", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-perf" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94bff26ccd8105f6206144f8dab16a2dc02e7a68f39dada02cdf492f0afd940" +dependencies = [ + "gpui-pre-collections", + "serde", + "serde_json", +] + +[[package]] +name = "gpui-pre-platform" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8423d1e0a693d2f9f16c0326bb01b5e81ec0531854d596a18476934ac6880478" +dependencies = [ + "console_error_panic_hook", + "gpui-pre", + "gpui-pre-linux", + "gpui-pre-macos", + "gpui-pre-web", + "gpui-pre-windows", +] + +[[package]] +name = "gpui-pre-refineable" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b4b0a8d296a3ee5beb1136bc48f8167a6028eb1e243b56851dcd8c9fc6d7a1" +dependencies = [ + "gpui-pre-derive-refineable", +] + +[[package]] +name = "gpui-pre-reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05be23908e707966824f8c51a609904b7f30739e8d915e120a53c1b995ba964c" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", +] + +[[package]] +name = "gpui-pre-scheduler" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7b10b3d2fc3fda24847a04436eca9531612c8414874a37ddc005f20e521e12" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.5", + "wasm_thread", + "web-time", +] + +[[package]] +name = "gpui-pre-shared-string" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe17f81bd98a0253140ea79f7f76442d7499bd8d17db302861af8e46c11d38df" +dependencies = [ + "schemars", + "serde", + "smol_str", +] + +[[package]] +name = "gpui-pre-sum-tree" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6d35e998a51d24248dad731de1f09d386cbbf46a04edde3b67f469b2ce3f16" +dependencies = [ + "gpui-pre-ztracing", + "heapless", + "log", + "rayon", + "tracing", +] + +[[package]] +name = "gpui-pre-util" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a026a30c578930d20974fd8c3d77de36223ac7ba193bd4259e7238929bc8806" +dependencies = [ + "anyhow", + "log", + "which", +] + +[[package]] +name = "gpui-pre-util-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbf79627a6afc626d4e887dad9d2d92bb069413e8cc692ba3e90bff219eb7a9" +dependencies = [ + "gpui-pre-perf", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-web" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34be433b9fcb370b1fe9a1c7fe911f0eb6058791ef8167dcf6c5a5deb624975c" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "futures", + "gpui-pre", + "gpui-pre-http-client", + "gpui-pre-scheduler", + "gpui-pre-wgpu", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", + "smallvec", + "unicode-properties", + "unicode-script", + "unicode-segmentation", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_thread", + "web-sys", + "web-time", +] + +[[package]] +name = "gpui-pre-wgpu" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05515b32799b1767d69300921c1b562cd53104965211a0e8905acdc7aa18e693" +dependencies = [ + "anyhow", + "bytemuck", + "cosmic-text", + "etagere", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", + "itertools 0.14.0", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "swash", + "unicode-bidi", + "unicode-segmentation", + "web-sys", + "wgpu", + "zed-font-kit", +] + +[[package]] +name = "gpui-pre-windows" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27662e65bfcf4445d2cccfad91f1570ec07f4b1b5de98400a9f207fd19c464cf" +dependencies = [ + "accesskit", + "accesskit_windows", + "anyhow", + "dunce", + "etagere", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.5", + "raw-window-handle", + "smallvec", + "uuid", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-numerics 0.3.1", + "windows-registry 0.6.1", +] + +[[package]] +name = "gpui-pre-zlog" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a5af0b734a391a84872eb770b3ee9830091ec2487b79d06371fe14eefb2b5d" +dependencies = [ + "anyhow", + "chrono", + "gpui-pre-collections", + "log", +] + +[[package]] +name = "gpui-pre-ztracing" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24cb19f78c5bc3aab8ddf1e8e0bd6b402fbcb9c7f112f64ddbedef8f8768a59" +dependencies = [ + "gpui-pre-zlog", + "gpui-pre-ztracing-macro", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "gpui-pre-ztracing-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1bb25850ad31b88bbdef454afb93ec4cdb81f5e200ba5b3f1a0368111780d2c" + +[[package]] +name = "granit-parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0" +dependencies = [ + "arraydeque", + "smallvec", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "core_maths", + "read-fonts 0.37.0", + "smallvec", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif 0.14.2", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "tiff", + "zune-core 0.5.3", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "imgref" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.1", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" +dependencies = [ + "bitflags 2.13.2", + "serde", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.9", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.2", + "libc", +] + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "lsp-types" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" +dependencies = [ + "bitflags 1.3.2", + "fluent-uri", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac-notification-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +dependencies = [ + "cc", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "time", + "uuid", +] + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "serde", + "unicode-id", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.13.2", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "libxdo", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.18.1", + "thiserror 2.0.21", + "windows-sys 0.61.2", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "naga" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.21", + "unicode-ident", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "normpath" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11ce00d2594068e8a27c9146fdc5cf9f3ac38eb42c7cd34d05fea618873ac9f" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.13.2", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-rust" +version = "4.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-cloud-kit", + "objc2-core-data 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-screen-capture-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "objc2 0.6.4", + "objc2-core-graphics", + "objc2-core-media", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-location", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oo7" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" +dependencies = [ + "aes", + "ashpd", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher", + "digest 0.10.7", + "endi", + "futures-lite", + "futures-util", + "getrandom 0.4.3", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "serde", + "serde_bytes", + "sha2 0.10.9", + "subtle", + "zbus", + "zbus_macros", + "zeroize", + "zvariant", +] + +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.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 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.8", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.15+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[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 = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror 2.0.21", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.21", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.5", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.21", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.11.3", +] + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types 0.12.5", + "once_cell", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror 2.0.21", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.15.3", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif 0.14.2", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.16.1", + "tiny-skia", + "usvg 0.46.0", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ropey" +version = "2.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4045a00dc327d084a2bbf126976e14125b54f23bd30511d45b842eba76c52d74" +dependencies = [ + "str_indices", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "globset", + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rust-i18n" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c43fd69c20da13756643a5bf51ae799965465c78e05c57a1242a736aaa56527" +dependencies = [ + "globwalk", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086ec8a9eaa6afda33919b9bc1661c1f4e017c40e99bbd33ca98f6e1cb6ca91f" +dependencies = [ + "glob", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "rust-i18n-support" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac02dcb9a01ec145d0a5534ea9554a966050ca250b346bb2a9ab0fcdafab5979" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools 0.11.0", + "normpath", + "serde", + "serde-saphyr", + "serde_json", + "siphasher", + "toml 0.8.23", + "triomphe", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.6", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[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-saphyr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8" +dependencies = [ + "annotate-snippets", + "base64 0.23.1", + "encoding_rs_io", + "granit-parser", + "nohash-hasher", + "num-traits", + "serde_core", + "smallvec", + "zmij", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[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.6", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "dirs 6.0.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[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 = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts 0.41.0", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sval" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81b254da21fe1fcc4e3a74fe39b46e25e3a863078f8b71c954d47f84889dbc6" + +[[package]] +name = "sval_buffer" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50be352d2822ffafb59e3e2ddac9d5ee60f2eeadbb7b5a2a951b9f3651e87a6f" +dependencies = [ + "sval", + "sval_ref", + "zerocopy", +] + +[[package]] +name = "sval_dynamic" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048ca293b998d9a45659159f94a64063791e74cdc670164943dbb434405573d" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b5888e40f80568733217f27b7317b845f463400ced36c424b1a804730e53b2" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17664d6bb6b74947afaab9d7c991caa9bf5638d4dee16fcbef637f440796049" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c059969ca5ca163ea7fef6c9661758973d17691aba92abdcf5c428f4ec122c" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d6b29ff568c85c87561807f51d2adfff4b6016c6363133f7cd1652a12548f3" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33ec9edc42b12764d5c90ca0a1d84189c6bde81ed27507f1e661c6e4e05853" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.3", + "siphasher", +] + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo 0.13.1", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" +dependencies = [ + "skrifa 0.44.0", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "taffy" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034e05f6ee85a12daa63863c2245797715075c70649947aa0da54f3f2ab1d0f" +dependencies = [ + "arrayvec", + "serde", + "slotmap", + "smallvec", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri-winrt-notification" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" +dependencies = [ + "thiserror 2.0.21", + "windows 0.61.3", + "windows-version", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokenmaxx-gui" +version = "0.0.66" +dependencies = [ + "anyhow", + "chrono", + "dirs 6.0.0", + "gpui-kit", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "regex", + "serde", + "serde_json", + "smol", + "tray-icon", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tray-icon" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" +dependencies = [ + "crossbeam-channel", + "dirs 7.0.0", + "libappindicator", + "muda", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.18.1", + "thiserror 2.0.21", + "windows-sys 0.61.2", +] + +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-id" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd75ab2d9d379dbe86d4059e91d8b5a6e7054865eaba1ddbfc7a817ff96392" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.15.3", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize 0.14.0", + "kurbo 0.13.1", + "log", + "pico-args", + "roxmltree 0.21.1", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.16.1", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941feceafbe7a8f59ea1096d45b97002884a41306315ad797b3684b63a81d8c" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839752af8179287d27eb2b94164641b1ede9e60ab7424163388dc21ebd0508cd" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "log", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.2", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec", + "bitflags 2.13.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.21", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-wasm" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.2", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.21", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + +[[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-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.21", + "windows 0.61.3", + "windows-future 0.2.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "rustix", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcb" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" +dependencies = [ + "bitflags 2.13.2", + "libc", + "quick-xml", + "x11", +] + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xim-ctext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac61a7062c40f3c37b6e82eeeef835d5cc7824b632a72784a89b3963c33284c" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "xim-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcee45f89572d5a65180af3a84e7ddb24f5ea690a6d3aa9de231281544dd7b7" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "as-raw-xcb-connection", + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.6", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow 1.0.4", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" +dependencies = [ + "bitflags 2.13.2", + "byteorder", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-text", + "dirs 5.0.1", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.8", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-xim" +version = "0.4.0-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b46ed118eba34d9ba53d94ddc0b665e0e06a2cf874cfa2dd5dec278148642" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "log", + "x11rb", + "xim-ctext", + "xim-parser", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e8fafad82b542ff3717217ecdc736231b59e387768c9630123b4ce4d2db44" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595f56e044df4f46a0c9a626f65c3d99eb8488f7e8a8baa12dd76326d9710bf2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core 0.5.3", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "serde_bytes", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.6", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.6", + "winnow 1.0.4", +] diff --git a/gui/Cargo.toml b/gui/Cargo.toml new file mode 100644 index 0000000..22126a6 --- /dev/null +++ b/gui/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tokenmaxx-gui" +version = "0.0.66" +edition = "2024" +publish = false + +[dependencies] +anyhow = "1" +chrono = { version = "0.4", default-features = false, features = ["clock", "std", "serde"] } +dirs = "6" +gpui-kit = "=0.6.6" +objc2 = "0.6" +objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSRunningApplication", "NSWorkspace"] } +objc2-foundation = { version = "0.3", features = ["NSString", "NSURL"] } +regex = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +smol = "2" +tray-icon = "0.25" + +[profile.release] +codegen-units = 4 +lto = "thin" +strip = true diff --git a/gui/Info.plist b/gui/Info.plist new file mode 100644 index 0000000..73da5d9 --- /dev/null +++ b/gui/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + tokenmaxx + CFBundleExecutable + tokenmaxx-gui + CFBundleIconFile + tokenmaxx + CFBundleIdentifier + com.rubriclabs.tokenmaxx + CFBundleName + tokenmaxx + CFBundlePackageType + APPL + CFBundleShortVersionString + __VERSION__ + CFBundleVersion + __VERSION__ + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + 13.0 + LSUIElement + + NSHighResolutionCapable + + + diff --git a/gui/assets/icon.svg b/gui/assets/icon.svg new file mode 100644 index 0000000..fb8f425 --- /dev/null +++ b/gui/assets/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/gui/scripts/bundle.sh b/gui/scripts/bundle.sh new file mode 100755 index 0000000..ce9f3c1 --- /dev/null +++ b/gui/scripts/bundle.sh @@ -0,0 +1,31 @@ +#!/bin/sh +set -eu + +gui="$(cd "$(dirname "$0")/.." && pwd)" +repo="$(dirname "$gui")" +version="$(sed -n 's/^ "version": "\(.*\)",$/\1/p' "$repo/package.json")" +app="$gui/target/tokenmaxx.app" +work="$gui/target/bundle" + +(cd "$repo" && bun run build:bin) +cargo build --release --manifest-path "$gui/Cargo.toml" + +rm -rf "$app" "$work" +mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources/bin" "$work/tokenmaxx.iconset" + +cp "$gui/target/release/tokenmaxx-gui" "$app/Contents/MacOS/tokenmaxx-gui" +cp "$gui/target/bin/tokenmaxx" "$app/Contents/Resources/bin/tokenmaxx" +sed "s/__VERSION__/$version/g" "$gui/Info.plist" > "$app/Contents/Info.plist" + +qlmanage -t -s 1024 -o "$work" "$gui/assets/icon.svg" > /dev/null +for size in 16 32 128 256 512; do + sips -z "$size" "$size" "$work/icon.svg.png" --out "$work/tokenmaxx.iconset/icon_${size}x${size}.png" > /dev/null + double=$((size * 2)) + sips -z "$double" "$double" "$work/icon.svg.png" --out "$work/tokenmaxx.iconset/icon_${size}x${size}@2x.png" > /dev/null +done +iconutil -c icns "$work/tokenmaxx.iconset" -o "$app/Contents/Resources/tokenmaxx.icns" + +codesign --force --sign - "$app/Contents/Resources/bin/tokenmaxx" +codesign --force --sign - "$app" + +echo "$app" diff --git a/gui/src/cli.rs b/gui/src/cli.rs new file mode 100644 index 0000000..c9fbfd9 --- /dev/null +++ b/gui/src/cli.rs @@ -0,0 +1,201 @@ +use std::cmp::Ordering; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; +use std::sync::OnceLock; + +use anyhow::{Context as _, Result, bail}; + +const SYMLINK: &str = "/usr/local/bin/tokenmaxx"; + +/// The `tokenmaxx` binary shipped inside the app bundle; `TOKENMAXX_BIN` points at a dev build. +pub fn bundled_binary() -> PathBuf { + if let Some(path) = std::env::var_os("TOKENMAXX_BIN") { + return std::fs::canonicalize(&path).unwrap_or_else(|_| PathBuf::from(path)); + } + std::env::current_exe() + .ok() + .and_then(|exe| Some(exe.parent()?.parent()?.join("Resources/bin/tokenmaxx"))) + .unwrap_or_else(|| PathBuf::from("tokenmaxx")) +} + +const MARKER: &str = "__tokenmaxx__"; + +fn login_shell(script: &str) -> Option { + let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".into()); + let wrapped = format!("printf '{MARKER}%s{MARKER}' \"$({script})\""); + let output = Command::new(shell).args(["-lic", &wrapped]).output().ok()?; + let text = String::from_utf8_lossy(&output.stdout); + let (_, rest) = text.split_once(MARKER)?; + let (value, _) = rest.split_once(MARKER)?; + Some(value.trim().to_string()).filter(|value| !value.is_empty()) +} + +/// Apps launched from Finder get a bare PATH; the daemon needs the user's to find bun, codex, claude, and pi. +pub fn login_path() -> &'static str { + static PATH: OnceLock = OnceLock::new(); + PATH.get_or_init(|| { + login_shell("printf %s \"$PATH\"") + .or_else(|| std::env::var("PATH").ok()) + .unwrap_or_else(|| "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin".into()) + }) +} + +fn run(binary: &Path, arguments: &[&str]) -> Result { + let output = Command::new(binary) + .args(arguments) + .env("PATH", login_path()) + .output() + .with_context(|| format!("could not run {}", binary.display()))?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("{}", stderr.trim().trim_start_matches("tokenmaxx: ")); + } + Ok(output) +} + +/// `None` for builds from before `--version`, which answer with an unknown-command error. +fn version_of(binary: &Path) -> Option { + let output = run(binary, &["--version"]).ok()?; + let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); + version + .starts_with(|c: char| c.is_ascii_digit()) + .then_some(version) +} + +/// Semver order for `major.minor.patch[-prerelease]`; a prerelease sorts before its release. +pub fn compare_versions(left: &str, right: &str) -> Ordering { + let parse = |version: &str| { + let (release, prerelease) = version.split_once('-').unwrap_or((version, "")); + let numbers: Vec = release + .split('.') + .map(|part| part.parse().unwrap_or(0)) + .collect(); + (numbers, prerelease.is_empty()) + }; + parse(left).cmp(&parse(right)) +} + +#[derive(Clone, Debug, PartialEq)] +pub enum CommandLineTool { + /// `tokenmaxx` in the terminal is this app's own binary. + Bundled, + /// The user's installed `tokenmaxx` is at least as new as the app's, so the app drives it. + Shared { + path: String, + }, + /// The installed `tokenmaxx` is older; the app runs its own until the user updates. + Outdated { + path: String, + version: Option, + }, + Missing, +} + +/// The one `tokenmaxx` the app starts the daemon with and runs sign-in through. +#[derive(Clone, Debug, PartialEq)] +pub struct Runtime { + pub binary: PathBuf, + pub version: String, + pub bundled_version: String, + pub tool: CommandLineTool, +} + +/// Prefers the user's installed `tokenmaxx` whenever it is at least as new as the bundled one, +/// so the terminal and the app never restart the shared daemon on two different versions. +pub fn resolve() -> Result { + let bundled = bundled_binary(); + let bundled_version = version_of(&bundled) + .with_context(|| format!("{} did not report a version", bundled.display()))?; + let own = |tool| Runtime { + binary: bundled.clone(), + version: bundled_version.clone(), + bundled_version: bundled_version.clone(), + tool, + }; + let Some(found) = login_shell("command -v tokenmaxx") else { + return Ok(own(CommandLineTool::Missing)); + }; + let installed = PathBuf::from(&found); + if std::fs::canonicalize(&installed).is_ok_and(|resolved| resolved == bundled) { + return Ok(own(CommandLineTool::Bundled)); + } + match version_of(&installed) { + Some(version) if compare_versions(&version, &bundled_version) != Ordering::Less => { + Ok(Runtime { + binary: installed, + version, + bundled_version, + tool: CommandLineTool::Shared { path: found }, + }) + } + version => Ok(own(CommandLineTool::Outdated { + path: found, + version, + })), + } +} + +/// Starts the daemon, or replaces one another tokenmaxx version started. +pub fn start_daemon(binary: &Path) -> Result<()> { + run(binary, &["daemon", "start"]).map(|_| ()) +} + +fn shell_quote(path: &Path) -> String { + format!("'{}'", path.display().to_string().replace('\'', r"'\''")) +} + +/// Links `/usr/local/bin/tokenmaxx` to the bundled binary, asking for an administrator password. +pub fn install_command_line_tool() -> Result<()> { + let command = format!( + "mkdir -p /usr/local/bin && ln -sf {} {SYMLINK}", + shell_quote(&bundled_binary()) + ); + let script = format!( + "do shell script \"{}\" with administrator privileges", + command.replace('\\', "\\\\").replace('"', "\\\"") + ); + let output = Command::new("osascript").args(["-e", &script]).output()?; + if !output.status.success() { + bail!("{}", String::from_utf8_lossy(&output.stderr).trim()); + } + Ok(()) +} + +/// The package-manager command that brings an installed `tokenmaxx` up to `version`. +pub fn update_command(installed_path: &str, version: &str) -> Vec { + let package = format!("tokenmaxx@{version}"); + if installed_path.contains("/.bun/") { + vec!["bun".into(), "add".into(), "-g".into(), package] + } else { + vec!["npm".into(), "install".into(), "-g".into(), package] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn versions_order_like_semver() { + assert_eq!(compare_versions("0.0.67", "0.0.66"), Ordering::Greater); + assert_eq!(compare_versions("0.0.66", "0.0.66"), Ordering::Equal); + assert_eq!(compare_versions("0.0.9", "0.0.10"), Ordering::Less); + assert_eq!(compare_versions("0.0.67-alpha.3", "0.0.67"), Ordering::Less); + assert_eq!( + compare_versions("0.0.67-alpha.3", "0.0.66"), + Ordering::Greater + ); + } + + #[test] + fn updates_with_the_manager_that_installed_it() { + assert_eq!( + update_command("/Users/me/.bun/bin/tokenmaxx", "0.0.70")[0], + "bun" + ); + assert_eq!( + update_command("/opt/homebrew/bin/tokenmaxx", "0.0.70")[0], + "npm" + ); + } +} diff --git a/gui/src/format.rs b/gui/src/format.rs new file mode 100644 index 0000000..606cb61 --- /dev/null +++ b/gui/src/format.rs @@ -0,0 +1,393 @@ +use std::sync::LazyLock; + +use chrono::{DateTime, Utc}; +use regex::Regex; + +use crate::model::{Account, DashboardSnapshot, Health, Provider, UsageWindow, WindowKind}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Pressure { + Unknown, + Good, + Warn, + Bad, +} + +pub fn pressure(used_percent: Option) -> Pressure { + match used_percent { + None => Pressure::Unknown, + Some(value) if value >= 85.0 => Pressure::Bad, + Some(value) if value >= 60.0 => Pressure::Warn, + Some(_) => Pressure::Good, + } +} + +pub fn clamp_percent(value: f64) -> f64 { + value.clamp(0.0, 100.0) +} + +pub fn percent_label(used_percent: Option) -> String { + match used_percent { + None => "?%".into(), + Some(value) => format!("{}%", clamp_percent(value).round()), + } +} + +pub fn parse_time(iso: &str) -> Option> { + DateTime::parse_from_rfc3339(iso) + .ok() + .map(|time| time.with_timezone(&Utc)) +} + +pub fn short_reset(reset_at: Option<&str>, now: DateTime) -> Option { + let remaining = (parse_time(reset_at?)? - now).num_milliseconds(); + if remaining <= 0 { + return Some("now".into()); + } + let minutes = (remaining as f64 / 60_000.0).round(); + if minutes < 60.0 { + return Some(format!("{minutes}m")); + } + let hours = (minutes / 60.0).round(); + if hours < 24.0 { + return Some(format!("{hours}h")); + } + Some(format!("{}d", (hours / 24.0).round())) +} + +pub fn relative_age(observed: DateTime, now: DateTime) -> String { + let seconds = ((now - observed).num_milliseconds() as f64 / 1000.0) + .round() + .max(0.0) as i64; + if seconds < 60 { + return format!("{seconds}s"); + } + let minutes = seconds / 60; + if minutes < 60 { + return format!("{minutes}m"); + } + let hours = minutes / 60; + if hours < 48 { + format!("{hours}h") + } else { + format!("{}d", hours / 24) + } +} + +static MULTIPLIER: LazyLock = LazyLock::new(|| Regex::new(r"(\d+)\s*x").unwrap()); +static WORD_SPLIT: LazyLock = LazyLock::new(|| Regex::new(r"[\s_-]+").unwrap()); + +pub fn plan_label(plan: Option<&str>) -> Option { + let raw = plan?.trim().to_lowercase(); + if raw.is_empty() { + return None; + } + if raw.contains("max") { + return Some(match MULTIPLIER.captures(&raw) { + Some(captures) => format!("Max {}×", &captures[1]), + None => "Max".into(), + }); + } + Some( + WORD_SPLIT + .split(&raw) + .filter(|word| !word.is_empty()) + .map(|word| { + let mut characters = word.chars(); + characters + .next() + .map(|first| first.to_uppercase().chain(characters).collect::()) + .unwrap_or_default() + }) + .collect::>() + .join(" "), + ) +} + +pub struct HealthBadge { + pub text: &'static str, + pub pressure: Pressure, +} + +pub fn health_badge(health: Health) -> Option { + let (text, pressure) = match health { + Health::Ready | Health::Unchecked | Health::RefreshDue | Health::Refreshing => return None, + Health::LoginExpiring => ("Login expiring", Pressure::Warn), + Health::ScopeMissing => ("Missing scope", Pressure::Warn), + Health::ReauthenticationRequired => ("Login required", Pressure::Bad), + Health::TemporarilyUnreachable => ("Offline", Pressure::Warn), + Health::UsageRateLimited => ("Rate-limited", Pressure::Warn), + Health::Disabled => ("Off", Pressure::Unknown), + }; + Some(HealthBadge { text, pressure }) +} + +static FIVE_HOUR_LABEL: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)^(5 hour|5h session|five hour)$").unwrap()); +static SEVEN_DAY_LABEL: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)^7 day( · all models)?$").unwrap()); +static SEVEN_DAY_PREFIX: LazyLock = LazyLock::new(|| Regex::new(r"(?i)^7 day · ").unwrap()); +static WINDOW_TOKEN_SPLIT: LazyLock = LazyLock::new(|| Regex::new(r"[\s·-]+").unwrap()); +static NUMERIC: LazyLock = LazyLock::new(|| Regex::new(r"^\d+(\.\d+)?$").unwrap()); + +pub fn short_window(label: &str) -> String { + if FIVE_HOUR_LABEL.is_match(label) { + return "5h".into(); + } + if SEVEN_DAY_LABEL.is_match(label) { + return "7d".into(); + } + const GENERIC: [&str; 8] = [ + "day", "days", "hour", "hours", "week", "all", "models", "window", + ]; + let stripped = SEVEN_DAY_PREFIX.replace(label, ""); + let chosen = WINDOW_TOKEN_SPLIT + .split(&stripped) + .filter(|token| { + token.chars().count() > 1 + && !GENERIC.contains(&token.to_lowercase().as_str()) + && !NUMERIC.is_match(token) + }) + .last() + .unwrap_or(label); + if chosen.chars().count() > 8 { + format!("{}…", chosen.chars().take(7).collect::()) + } else { + chosen.to_string() + } +} + +static FIVE_HOUR: LazyLock = LazyLock::new(|| Regex::new(r"(?i)5 ?h").unwrap()); +static WEEKLY_LABEL: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)^(7 day(?: · all models)?)$").unwrap()); +static WEEKLY_ID: LazyLock = + LazyLock::new(|| Regex::new(r"^(weekly_all|seven_day|weekly|codex:primary)$").unwrap()); +static SCOPED: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)scoped|fable|opus|sonnet|spark").unwrap()); + +pub fn is_five_hour_window(window: &UsageWindow) -> bool { + FIVE_HOUR.is_match(&window.label) || window.id == "session" || window.id == "five-hour" +} + +fn window_priority(window: &UsageWindow) -> u8 { + if is_five_hour_window(window) { + 0 + } else if WEEKLY_LABEL.is_match(&window.label) || WEEKLY_ID.is_match(&window.id) { + 1 + } else if SCOPED.is_match(&format!("{} {}", window.id, window.label)) { + 2 + } else { + 3 + } +} + +pub fn visible_windows<'a>(windows: &'a [UsageWindow], hidden: &[String]) -> Vec<&'a UsageWindow> { + let mut visible: Vec<_> = windows + .iter() + .filter(|window| window.kind == WindowKind::Hard && !hidden.contains(&window.id)) + .collect(); + visible.sort_by_key(|window| window_priority(window)); + visible +} + +pub fn account_windows<'a>( + snapshot: &'a DashboardSnapshot, + account: &Account, +) -> Vec<&'a UsageWindow> { + let hidden = snapshot + .state(account.provider) + .map(|state| state.policy.hidden_window_ids.as_slice()) + .unwrap_or_default(); + snapshot + .usage(&account.id) + .map(|usage| visible_windows(&usage.windows, hidden)) + .unwrap_or_default() +} + +pub fn fullest_window(snapshot: &DashboardSnapshot, account: &Account) -> Option { + account_windows(snapshot, account) + .into_iter() + .map(|window| window.used_percent) + .reduce(f64::max) +} + +/// Accounts of one provider, fullest first, the order the dashboard and the menu bar share. +pub fn ordered_accounts(snapshot: &DashboardSnapshot, provider: Provider) -> Vec<&Account> { + let mut accounts: Vec<_> = snapshot + .accounts + .iter() + .filter(|account| account.provider == provider) + .collect(); + accounts.sort_by(|left, right| { + let peak = |account: &Account| fullest_window(snapshot, account).unwrap_or(-1.0); + peak(right) + .total_cmp(&peak(left)) + .then_with(|| left.label.cmp(&right.label)) + }); + accounts +} + +/// Distinct hard windows across a provider's accounts, 5h first, for the per-window visibility settings. +pub fn provider_windows(snapshot: &DashboardSnapshot, provider: Provider) -> Vec { + let mut seen: Vec = Vec::new(); + for usage in snapshot + .usage + .iter() + .filter(|usage| usage.provider == provider) + { + for window in usage + .windows + .iter() + .filter(|window| window.kind == WindowKind::Hard) + { + if !seen.iter().any(|known| known.id == window.id) { + seen.push(window.clone()); + } + } + } + seen.sort_by_key(window_priority); + seen +} + +pub fn compact_number(value: f64) -> String { + let magnitude = value.abs(); + if magnitude >= 1e9 { + format!("{:.1}B", value / 1e9) + } else if magnitude >= 1e6 { + format!("{:.1}M", value / 1e6) + } else if magnitude >= 1e3 { + format!("{:.1}k", value / 1e3) + } else { + format!("{}", value.round()) + } +} + +pub fn money_usd(value: f64) -> String { + let cents = (value * 100.0).round() as i64; + let whole = (cents / 100).abs().to_string(); + let grouped: Vec = whole + .as_bytes() + .rchunks(3) + .rev() + .map(|chunk| String::from_utf8_lossy(chunk).into_owned()) + .collect(); + let sign = if cents < 0 { "-" } else { "" }; + format!("{sign}${}.{:02}", grouped.join(","), (cents % 100).abs()) +} + +pub fn minutes_label(milliseconds: u64) -> String { + match milliseconds / 60_000 { + 0 => "Off".into(), + 1 => "1 min".into(), + minutes => format!("{minutes} min"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn window(id: &str, label: &str, used: f64) -> UsageWindow { + UsageWindow { + id: id.into(), + kind: WindowKind::Hard, + label: label.into(), + reset_at: None, + used_percent: used, + } + } + + #[test] + fn pressure_thresholds() { + assert_eq!(pressure(None), Pressure::Unknown); + assert_eq!(pressure(Some(59.9)), Pressure::Good); + assert_eq!(pressure(Some(60.0)), Pressure::Warn); + assert_eq!(pressure(Some(85.0)), Pressure::Bad); + } + + #[test] + fn resets_round_to_the_largest_unit() { + let now = parse_time("2026-09-24T12:00:00Z").unwrap(); + assert_eq!( + short_reset(Some("2026-09-24T11:00:00Z"), now).as_deref(), + Some("now") + ); + assert_eq!( + short_reset(Some("2026-09-24T12:42:00Z"), now).as_deref(), + Some("42m") + ); + assert_eq!( + short_reset(Some("2026-09-24T15:10:00Z"), now).as_deref(), + Some("3h") + ); + assert_eq!( + short_reset(Some("2026-09-27T12:00:00Z"), now).as_deref(), + Some("3d") + ); + assert_eq!(short_reset(None, now), None); + } + + #[test] + fn ages() { + let now = parse_time("2026-09-24T12:00:00Z").unwrap(); + assert_eq!( + relative_age(parse_time("2026-09-24T11:59:30Z").unwrap(), now), + "30s" + ); + assert_eq!( + relative_age(parse_time("2026-09-24T11:30:00Z").unwrap(), now), + "30m" + ); + assert_eq!( + relative_age(parse_time("2026-09-22T11:00:00Z").unwrap(), now), + "2d" + ); + } + + #[test] + fn plans() { + assert_eq!(plan_label(Some("max_20x")).as_deref(), Some("Max 20×")); + assert_eq!(plan_label(Some("pro")).as_deref(), Some("Pro")); + assert_eq!(plan_label(Some("team_plus")).as_deref(), Some("Team Plus")); + assert_eq!(plan_label(Some(" ")), None); + } + + #[test] + fn window_names() { + assert_eq!(short_window("5 hour"), "5h"); + assert_eq!(short_window("7 day · all models"), "7d"); + assert_eq!(short_window("7 day · Opus"), "Opus"); + assert_eq!(short_window("GPT-5.3-Codex-Spark weekly"), "weekly"); + } + + #[test] + fn windows_order_session_then_week_then_scoped() { + let windows = vec![ + window("opus", "7 day · Opus", 1.0), + window("weekly_all", "7 day", 2.0), + window("session", "5 hour", 3.0), + UsageWindow { + kind: WindowKind::Soft, + ..window("soft", "soft", 4.0) + }, + ]; + let ids: Vec<_> = visible_windows(&windows, &[]) + .iter() + .map(|w| w.id.as_str()) + .collect(); + assert_eq!(ids, ["session", "weekly_all", "opus"]); + let hidden = vec!["opus".to_string()]; + assert_eq!(visible_windows(&windows, &hidden).len(), 2); + } + + #[test] + fn numbers() { + assert_eq!(compact_number(950.0), "950"); + assert_eq!(compact_number(12_345.0), "12.3k"); + assert_eq!(compact_number(4_200_000.0), "4.2M"); + assert_eq!(money_usd(1234.5), "$1,234.50"); + assert_eq!(money_usd(0.004), "$0.00"); + assert_eq!(minutes_label(300_000), "5 min"); + assert_eq!(minutes_label(0), "Off"); + } +} diff --git a/gui/src/ipc.rs b/gui/src/ipc.rs new file mode 100644 index 0000000..6323392 --- /dev/null +++ b/gui/src/ipc.rs @@ -0,0 +1,161 @@ +use std::path::PathBuf; +use std::time::Duration; + +use anyhow::{Context as _, Result, anyhow}; +use serde::Deserialize; +use serde::de::DeserializeOwned; +use serde_json::{Value, json}; +use smol::io::{AsyncBufReadExt as _, AsyncWriteExt as _, BufReader}; +use smol::net::unix::UnixStream; + +use crate::model::{ + Account, AnalyticsSnapshot, Ping, Provider, ResetCreditsView, ResetOutcome, RoutingStatus, + RoutingTarget, +}; + +pub fn tokenmaxx_home() -> PathBuf { + std::env::var_os("TOKENMAXX_HOME") + .map(PathBuf::from) + .unwrap_or_else(|| dirs::home_dir().unwrap_or_default().join(".tokenmaxx")) +} + +pub fn socket_path() -> PathBuf { + tokenmaxx_home().join("runtime").join("manager.sock") +} + +#[derive(Debug, Deserialize)] +pub struct RpcError { + pub message: String, +} + +impl std::fmt::Display for RpcError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str(&self.message) + } +} + +impl std::error::Error for RpcError {} + +#[derive(Deserialize)] +struct Response { + #[serde(default)] + result: Option, + #[serde(default)] + error: Option, +} + +async fn exchange(method: &str, params: Value) -> Result { + let mut stream = UnixStream::connect(socket_path()) + .await + .context("tokenmaxx is not running")?; + let request = json!({ "id": 1, "method": method, "params": params }); + stream.write_all(format!("{request}\n").as_bytes()).await?; + let mut line = String::new(); + BufReader::new(stream).read_line(&mut line).await?; + let response: Response = serde_json::from_str(&line)?; + match (response.result, response.error) { + (_, Some(error)) => Err(error.into()), + (Some(result), None) => Ok(result), + (None, None) => Ok(Value::Null), + } +} + +pub async fn request( + method: &str, + params: Value, + timeout: Duration, +) -> Result { + let expired = async { + smol::Timer::after(timeout).await; + Err(anyhow!("{method} timed out")) + }; + let value = smol::future::or(exchange(method, params), expired).await?; + Ok(serde_json::from_value(value)?) +} + +const QUICK: Duration = Duration::from_secs(15); + +pub async fn ping() -> Result { + request("manager/ping", Value::Null, Duration::from_millis(500)).await +} + +pub async fn analytics() -> Result { + request("dashboard/analytics", Value::Null, QUICK).await +} + +pub async fn routing() -> Result { + request("routing/read", Value::Null, QUICK).await +} + +pub async fn set_routing(target: RoutingTarget, enable: bool) -> Result { + request( + "routing/set", + json!({ "target": target, "enable": enable }), + QUICK, + ) + .await +} + +pub async fn refresh() -> Result { + request("usage/refresh", Value::Null, Duration::from_secs(60)).await +} + +pub async fn switch(provider: Provider, account_id: &str) -> Result { + let params = json!({ "provider": provider, "reason": "manual", "targetAccountId": account_id }); + request("provider/switch", params, Duration::from_secs(30)).await +} + +pub async fn set_policy(provider: Provider, mut changes: Value) -> Result { + changes["provider"] = json!(provider); + request("policy/set", changes, QUICK).await +} + +pub async fn save_account(account: &Account) -> Result { + let params = json!({ + "account": account, + "removePrevious": { "profilePath": null, "secretReference": null } + }); + request("account/save", params, QUICK).await +} + +pub async fn remove_account(account_id: &str) -> Result { + request( + "account/remove", + json!({ "accountId": account_id }), + Duration::from_secs(20), + ) + .await +} + +pub async fn add_api_key(provider: Provider, key: &str, label: &str) -> Result { + let params = json!({ "provider": provider, "key": key, "label": label }); + request("account/addApiKey", params, Duration::from_secs(30)).await +} + +pub async fn reset_credits(account_id: &str) -> Result { + request( + "codex/resetCredits", + json!({ "accountId": account_id }), + Duration::from_secs(20), + ) + .await +} + +pub async fn consume_reset(account_id: &str) -> Result { + request( + "codex/consumeReset", + json!({ "accountId": account_id }), + Duration::from_secs(45), + ) + .await +} + +#[derive(Deserialize)] +struct Latest { + latest: Option, +} + +pub async fn latest_version() -> Result> { + let latest: Latest = request("version/latest", Value::Null, Duration::from_secs(5)).await?; + Ok(latest.latest) +} diff --git a/gui/src/main.rs b/gui/src/main.rs new file mode 100644 index 0000000..ae2eef8 --- /dev/null +++ b/gui/src/main.rs @@ -0,0 +1,61 @@ +mod cli; +mod format; +mod ipc; +mod model; +mod prefs; +mod shell; +mod store; +mod terminal; +mod tray; +mod views; + +use gpui_kit::*; + +use crate::shell::Shell; +use crate::store::Store; +use crate::views::root::Page; + +gpui_kit::actions!(tokenmaxx, [Quit, CloseWindow]); + +struct MainShell(Entity); + +impl Global for MainShell {} + +fn open(page: Page, cx: &mut App) { + if let Some(MainShell(shell)) = cx + .try_global::() + .map(|global| MainShell(global.0.clone())) + { + shell.update(cx, |shell, cx| shell.open(page, cx)); + } +} + +fn main() { + let application = gpui_kit::application().with_assets(gpui_kit::assets::AllAssets); + application.on_reopen(|cx| open(Page::Accounts, cx)); + application.run(|cx| { + gpui_kit::init(cx); + cx.bind_keys([ + KeyBinding::new("cmd-q", Quit, None), + KeyBinding::new("cmd-w", CloseWindow, None), + ]); + cx.on_action(|_: &Quit, cx| cx.quit()); + cx.on_action(|_: &CloseWindow, cx| { + if let Some(window) = cx.active_window() { + window + .update(cx, |_, window, _| window.remove_window()) + .ok(); + } + }); + cx.set_menus([Menu { + name: "tokenmaxx".into(), + items: vec![MenuItem::action("Quit tokenmaxx", Quit)], + disabled: false, + }]); + + let store = cx.new(Store::new); + let shell = cx.new(|cx| Shell::new(store, cx)); + cx.set_global(MainShell(shell)); + open(Page::Accounts, cx); + }); +} diff --git a/gui/src/model.rs b/gui/src/model.rs new file mode 100644 index 0000000..29a45ab --- /dev/null +++ b/gui/src/model.rs @@ -0,0 +1,372 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Provider { + Openai, + Anthropic, +} + +impl Provider { + pub const ALL: [Provider; 2] = [Provider::Openai, Provider::Anthropic]; + + pub fn cli(self) -> &'static str { + match self { + Provider::Openai => "codex", + Provider::Anthropic => "claude", + } + } + + pub fn title(self) -> &'static str { + match self { + Provider::Openai => "Codex", + Provider::Anthropic => "Claude Code", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Health { + Unchecked, + Ready, + RefreshDue, + Refreshing, + LoginExpiring, + ScopeMissing, + ReauthenticationRequired, + TemporarilyUnreachable, + UsageRateLimited, + Disabled, +} + +impl Health { + pub fn needs_login(self) -> bool { + matches!( + self, + Health::ReauthenticationRequired | Health::LoginExpiring | Health::ScopeMissing + ) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum AuthKind { + Oauth, + ApiKey, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum OnThreshold { + Switch, + Spill, +} + +/// Round-trips through `account/save`, so every field the daemon's strict schema knows is kept. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Account { + pub auth: AuthKind, + pub created_at: String, + pub enabled: bool, + pub external_account_id: Option, + #[serde(default)] + pub external_user_id: Option, + pub health: Health, + pub id: String, + pub identity: String, + pub label: String, + pub on_threshold: OnThreshold, + #[serde(default)] + pub plan: Option, + pub profile_path: Option, + pub provider: Provider, + pub secret_reference: Option, + pub updated_at: String, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum WindowKind { + Hard, + Soft, + Spend, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UsageWindow { + pub id: String, + pub kind: WindowKind, + pub label: String, + pub reset_at: Option, + pub used_percent: f64, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExtraUsage { + pub balance_usd: Option, + pub enabled: bool, + pub exhausted: bool, + pub limit_usd: Option, + pub spent_usd: Option, + pub used_percent: Option, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResetCreditCounts { + pub applicable: u32, + pub available: u32, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UsageSnapshot { + pub account_id: String, + pub hard_limit_reached: bool, + pub observed_at: String, + pub provider: Provider, + pub windows: Vec, + #[serde(default)] + pub extra_usage: Option, + #[serde(default)] + pub measured_spend_usd: Option, + #[serde(default)] + pub reset_credits: Option, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AutomationPolicy { + pub enabled: bool, + pub hidden_window_ids: Vec, + pub hysteresis_percent: f64, + pub minimum_dwell_milliseconds: u64, + pub threshold_percent: f64, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProviderState { + pub active_account_id: Option, + pub policy: AutomationPolicy, + pub provider: Provider, + pub switched_at: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DashboardSnapshot { + pub accounts: Vec, + pub providers: Vec, + pub sampled_at: String, + pub usage: Vec, +} + +impl DashboardSnapshot { + pub fn state(&self, provider: Provider) -> Option<&ProviderState> { + self.providers + .iter() + .find(|state| state.provider == provider) + } + + pub fn usage(&self, account_id: &str) -> Option<&UsageSnapshot> { + self.usage + .iter() + .find(|usage| usage.account_id == account_id) + } + + pub fn account(&self, account_id: &str) -> Option<&Account> { + self.accounts + .iter() + .find(|account| account.id == account_id) + } + + pub fn active_account(&self, provider: Provider) -> Option<&Account> { + let active = self.state(provider)?.active_account_id.as_deref()?; + self.account(active) + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenBreakdown { + pub cache_creation: f64, + pub cached: f64, + pub cost_usd: f64, + pub input: f64, + pub output: f64, + pub tokens: f64, + pub provider: Provider, + #[serde(default)] + pub model: Option, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenTimeframe { + pub bucket_ms: f64, + pub buckets: Vec, + pub by_provider: Vec, + pub cost_cache_creation: f64, + pub cost_cached: f64, + pub cost_input: f64, + pub cost_output: f64, + pub cost_usd: f64, + pub key: String, + pub models: Vec, + pub peak_per_hour: f64, + pub total_cache_creation: f64, + pub total_cached: f64, + pub total_input: f64, + pub total_output: f64, + pub total_tokens: f64, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenAnalytics { + pub now_per_hour: f64, + pub timeframes: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AnalyticsSnapshot { + pub snapshot: DashboardSnapshot, + #[serde(default)] + pub tokens: Option, +} + +impl AnalyticsSnapshot { + pub fn timeframe(&self, key: &str) -> Option<&TokenTimeframe> { + self.tokens + .as_ref()? + .timeframes + .iter() + .find(|timeframe| timeframe.key == key) + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResetCredit { + pub expires_at: Option, + pub id: String, + pub title: Option, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResetCreditsView { + pub available: u32, + pub credits: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ResetCode { + Reset, + NothingToReset, + NoCredit, + AlreadyRedeemed, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResetOutcome { + pub code: ResetCode, + pub windows_reset: u32, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize)] +pub struct ProviderFlags { + pub openai: bool, + pub anthropic: bool, +} + +impl ProviderFlags { + pub fn get(&self, provider: Provider) -> bool { + match provider { + Provider::Openai => self.openai, + Provider::Anthropic => self.anthropic, + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize)] +pub struct PiStatus { + pub present: bool, + pub routed: bool, +} + +#[derive(Clone, Debug, Default, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RoutingStatus { + pub clis: ProviderFlags, + pub codex_stale: bool, + pub pi: PiStatus, + pub routed: ProviderFlags, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum RoutingTarget { + Openai, + Anthropic, + Pi, +} + +impl From for RoutingTarget { + fn from(provider: Provider) -> Self { + match provider { + Provider::Openai => RoutingTarget::Openai, + Provider::Anthropic => RoutingTarget::Anthropic, + } + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Ping { + #[serde(default)] + pub version: Option, +} + +pub const TIMEFRAMES: [&str; 5] = ["1h", "5h", "24h", "7d", "31d"]; + +#[cfg(test)] +mod tests { + use super::*; + + const FIXTURES: [&str; 7] = [ + include_str!("../tests/fixtures/blitz.json"), + include_str!("../tests/fixtures/cruising.json"), + include_str!("../tests/fixtures/onboarding.json"), + include_str!("../tests/fixtures/oneHot.json"), + include_str!("../tests/fixtures/relay.json"), + include_str!("../tests/fixtures/rotated.json"), + include_str!("../tests/fixtures/tuned.json"), + ]; + + #[test] + fn parses_every_dashboard_scenario() { + for fixture in FIXTURES { + let analytics: AnalyticsSnapshot = serde_json::from_str(fixture).unwrap(); + for provider in Provider::ALL { + assert!(analytics.snapshot.state(provider).is_some()); + } + } + } + + #[test] + fn relay_has_every_timeframe() { + let analytics: AnalyticsSnapshot = serde_json::from_str(FIXTURES[4]).unwrap(); + for key in TIMEFRAMES { + assert!(analytics.timeframe(key).is_some(), "{key}"); + } + } +} diff --git a/gui/src/prefs.rs b/gui/src/prefs.rs new file mode 100644 index 0000000..3392ab7 --- /dev/null +++ b/gui/src/prefs.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; + +use crate::ipc::tokenmaxx_home; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Appearance { + #[default] + Auto, + Light, + Dark, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Presence { + #[default] + MenuBarAndDock, + MenuBar, + Dock, +} + +impl Presence { + pub fn shows_menu_bar(self) -> bool { + self != Presence::Dock + } + + pub fn shows_dock(self) -> bool { + self != Presence::MenuBar + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct Preferences { + pub appearance: Appearance, + pub presence: Presence, + /// Bundle identifier of the terminal for sign-in; `None` picks the first installed one. + pub terminal: Option, +} + +fn path() -> std::path::PathBuf { + tokenmaxx_home().join("gui.json") +} + +pub fn load() -> Preferences { + std::fs::read_to_string(path()) + .ok() + .and_then(|raw| serde_json::from_str(&raw).ok()) + .unwrap_or_default() +} + +pub fn save(preferences: &Preferences) -> anyhow::Result<()> { + std::fs::create_dir_all(tokenmaxx_home())?; + std::fs::write(path(), serde_json::to_string_pretty(preferences)? + "\n")?; + Ok(()) +} diff --git a/gui/src/shell.rs b/gui/src/shell.rs new file mode 100644 index 0000000..6680dcf --- /dev/null +++ b/gui/src/shell.rs @@ -0,0 +1,131 @@ +use gpui_kit::component::{Root, TitleBar}; +use gpui_kit::*; +use objc2::MainThreadMarker; +use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy}; + +use crate::prefs::Presence; +use crate::store::{Store, StoreEvent}; +use crate::tray::{self, Tray, TrayCommand}; +use crate::views::root::{Page, RootView}; + +/// Owns the one main window, the menu bar item, and the Dock presence the preferences ask for. +pub struct Shell { + store: Entity, + window: Option<(WindowHandle, Entity)>, + tray: Option, + presence: Option, +} + +fn set_dock_visible(visible: bool) { + let Some(main_thread) = MainThreadMarker::new() else { + return; + }; + let policy = if visible { + NSApplicationActivationPolicy::Regular + } else { + NSApplicationActivationPolicy::Accessory + }; + NSApplication::sharedApplication(main_thread).setActivationPolicy(policy); +} + +impl Shell { + pub fn new(store: Entity, cx: &mut Context) -> Self { + cx.observe(&store, |this, store, cx| { + if let Some(tray) = this.tray.as_mut() { + tray.update(&store.read(cx).analytics); + } + }) + .detach(); + cx.subscribe(&store, |this, _, event: &StoreEvent, cx| match event { + StoreEvent::PreferencesChanged => this.apply_presence(cx), + _ => {} + }) + .detach(); + let commands = tray::listen(); + cx.spawn(async move |this, cx| { + while let Ok(command) = commands.recv().await { + if this.update(cx, |this, cx| this.run(command, cx)).is_err() { + return; + } + } + }) + .detach(); + let mut shell = Self { + store, + window: None, + tray: None, + presence: None, + }; + shell.apply_presence(cx); + shell + } + + fn apply_presence(&mut self, cx: &mut Context) { + let presence = self.store.read(cx).preferences.presence; + if self.presence == Some(presence) { + return; + } + self.presence = Some(presence); + set_dock_visible(presence.shows_dock()); + if presence.shows_menu_bar() && self.tray.is_none() { + match Tray::new(&self.store.read(cx).analytics) { + Ok(tray) => self.tray = Some(tray), + Err(error) => self.store.update(cx, |_, cx| { + cx.emit(StoreEvent::Error( + format!("Menu bar item unavailable: {error}").into(), + )) + }), + } + } else if !presence.shows_menu_bar() { + self.tray = None; + } + cx.activate(true); + } + + pub fn open(&mut self, page: Page, cx: &mut Context) { + cx.activate(true); + if let Some((handle, root)) = self.window.clone() { + let shown = handle + .update(cx, |_, window, _| window.activate_window()) + .is_ok(); + if shown { + root.update(cx, |root, cx| root.show(page, cx)); + return; + } + } + let store = self.store.clone(); + let options = WindowOptions { + titlebar: Some(TitleBar::title_bar_options()), + window_bounds: Some(WindowBounds::Windowed(Bounds::centered( + None, + size(px(1000.), px(720.)), + cx, + ))), + window_min_size: Some(size(px(780.), px(520.))), + ..Default::default() + }; + let mut created = None; + let handle = cx.open_window(options, |window, cx| { + let root = cx.new(|cx| RootView::new(store, page, window, cx)); + created = Some(root.clone()); + cx.new(|cx| Root::new(root, window, cx)) + }); + match (handle, created) { + (Ok(handle), Some(root)) => self.window = Some((handle, root)), + (Err(error), _) => eprintln!("tokenmaxx: could not open the window: {error}"), + _ => {} + } + } + + fn run(&mut self, command: TrayCommand, cx: &mut Context) { + match command { + TrayCommand::Switch(provider, account) => self + .store + .update(cx, |store, cx| store.switch(provider, account, cx)), + TrayCommand::OpenAccounts => self.open(Page::Accounts, cx), + TrayCommand::OpenSettings => self.open(Page::Settings, cx), + TrayCommand::Refresh => self.store.update(cx, |store, cx| store.refresh(cx)), + TrayCommand::Quit => cx.quit(), + } + } +} diff --git a/gui/src/store.rs b/gui/src/store.rs new file mode 100644 index 0000000..d7ad867 --- /dev/null +++ b/gui/src/store.rs @@ -0,0 +1,332 @@ +use std::cmp::Ordering; +use std::future::Future; +use std::time::Duration; + +use anyhow::Result; +use gpui_kit::*; + +use crate::cli::{self, CommandLineTool, Runtime}; +use crate::ipc; +use crate::model::{AnalyticsSnapshot, Provider, RoutingStatus}; +use crate::prefs::{self, Preferences}; +use crate::terminal::{self, Terminal}; + +const POLL: Duration = Duration::from_secs(2); +const RESOLVE: Duration = Duration::from_secs(60); + +#[derive(Clone, Debug, PartialEq)] +pub enum Connection { + Connecting, + Ready, + Failed(SharedString), +} + +pub enum StoreEvent { + Notice(SharedString), + Error(SharedString), + PreferencesChanged, +} + +/// The daemon's state as last read, plus the one mutation in flight. Every screen and the menu bar read from here. +pub struct Store { + pub connection: Connection, + pub analytics: AnalyticsSnapshot, + pub routing: RoutingStatus, + pub runtime: Option, + pub latest: Option, + pub busy: Option, + pub preferences: Preferences, + pub terminals: Vec, +} + +impl EventEmitter for Store {} + +async fn read_state() -> Result<(AnalyticsSnapshot, RoutingStatus)> { + let (analytics, routing) = smol::future::zip(ipc::analytics(), ipc::routing()).await; + Ok((analytics?, routing?)) +} + +/// Starts the daemon with the resolved `tokenmaxx` unless one at least as new already answers. +async fn connect() -> Result { + let runtime = cli::resolve()?; + let running = ipc::ping().await.ok().and_then(|ping| ping.version); + let behind = running + .as_deref() + .is_none_or(|running| cli::compare_versions(running, &runtime.version) == Ordering::Less); + if behind { + cli::start_daemon(&runtime.binary)?; + } + Ok(runtime) +} + +impl Store { + pub fn new(cx: &mut Context) -> Self { + cx.spawn(async move |this, cx| Self::run(this, cx).await) + .detach(); + Self { + connection: Connection::Connecting, + analytics: AnalyticsSnapshot::default(), + routing: RoutingStatus::default(), + runtime: None, + latest: None, + busy: None, + preferences: prefs::load(), + terminals: terminal::installed_terminals(), + } + } + + async fn run(this: WeakEntity, cx: &mut AsyncApp) { + loop { + let connected = cx.background_executor().spawn(connect()).await; + let failed = connected + .as_ref() + .err() + .map(|error| SharedString::from(error.to_string())); + let alive = this.update(cx, |this, cx| { + match connected { + Ok(runtime) => { + this.runtime = Some(runtime); + this.connection = Connection::Ready; + } + Err(_) => { + this.connection = Connection::Failed(failed.clone().unwrap_or_default()) + } + } + cx.notify(); + }); + if alive.is_err() { + return; + } + if failed.is_none() { + break; + } + cx.background_executor().timer(Duration::from_secs(5)).await; + } + this.update(cx, |this, cx| this.check_background_state(cx)) + .ok(); + loop { + let state = cx.background_executor().spawn(read_state()).await; + let alive = this.update(cx, |this, cx| { + if this.busy.is_none() { + this.apply(state, cx); + } + }); + if alive.is_err() { + return; + } + cx.background_executor().timer(POLL).await; + } + } + + fn apply(&mut self, state: Result<(AnalyticsSnapshot, RoutingStatus)>, cx: &mut Context) { + match state { + Ok((analytics, routing)) => { + self.connection = Connection::Ready; + if self.analytics != analytics || self.routing != routing { + self.analytics = analytics; + self.routing = routing; + cx.notify(); + } + } + Err(error) => { + let failed = Connection::Failed(error.to_string().into()); + if self.connection != failed { + self.connection = failed; + cx.notify(); + } + } + } + } + + /// Checks for a newer release once, then follows the installed `tokenmaxx` as the user updates it. + fn check_background_state(&mut self, cx: &mut Context) { + cx.spawn(async move |this, cx| { + let latest = ipc::latest_version().await.ok().flatten(); + if this + .update(cx, |this, cx| { + this.latest = latest; + cx.notify(); + }) + .is_err() + { + return; + } + loop { + cx.background_executor().timer(RESOLVE).await; + let Ok(runtime) = cx.background_executor().spawn(connect()).await else { + continue; + }; + let alive = this.update(cx, |this, cx| { + if this.runtime.as_ref() != Some(&runtime) { + this.runtime = Some(runtime); + cx.notify(); + } + }); + if alive.is_err() { + return; + } + } + }) + .detach(); + } + + pub fn is_busy(&self) -> bool { + self.busy.is_some() + } + + /// Runs one daemon mutation, re-reads state when it lands, and reports the outcome as a notification. + pub fn perform( + &mut self, + label: impl Into, + work: F, + done: impl FnOnce(&mut Self, T, &mut Context) + 'static, + cx: &mut Context, + ) where + T: Send + 'static, + F: Future> + Send + 'static, + { + if self.busy.is_some() { + return; + } + self.busy = Some(label.into()); + cx.notify(); + let work = cx.background_executor().spawn(work); + cx.spawn(async move |this, cx| { + let result = work.await; + let state = cx.background_executor().spawn(read_state()).await; + this.update(cx, |this, cx| { + this.busy = None; + match result { + Ok(value) => done(this, value, cx), + Err(error) => cx.emit(StoreEvent::Error(error.to_string().into())), + } + this.apply(state, cx); + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + pub fn refresh(&mut self, cx: &mut Context) { + self.perform("Refreshing…", ipc::refresh(), |_, _, _| {}, cx); + } + + pub fn switch(&mut self, provider: Provider, account_id: String, cx: &mut Context) { + let label = self + .analytics + .snapshot + .account(&account_id) + .map(|account| account.label.clone()) + .unwrap_or_default(); + self.perform( + format!("Switching to {label}…"), + async move { ipc::switch(provider, &account_id).await }, + move |_, _, cx| { + cx.emit(StoreEvent::Notice( + format!("{} now uses {label}", provider.title()).into(), + )) + }, + cx, + ); + } + + pub fn set_policy( + &mut self, + provider: Provider, + changes: serde_json::Value, + cx: &mut Context, + ) { + self.perform( + "Saving…", + async move { ipc::set_policy(provider, changes).await }, + |_, _, _| {}, + cx, + ); + } + + pub fn set_routing( + &mut self, + target: crate::model::RoutingTarget, + enable: bool, + cx: &mut Context, + ) { + self.perform( + if enable { + "Routing…" + } else { + "Restoring config…" + }, + async move { ipc::set_routing(target, enable).await }, + |_, _, _| {}, + cx, + ); + } + + pub fn set_preferences(&mut self, preferences: Preferences, cx: &mut Context) { + if let Err(error) = prefs::save(&preferences) { + cx.emit(StoreEvent::Error( + format!("Preferences not saved: {error}").into(), + )); + } + self.preferences = preferences; + cx.emit(StoreEvent::PreferencesChanged); + cx.notify(); + } + + pub fn install_command_line_tool(&mut self, cx: &mut Context) { + self.perform( + "Installing the command-line tool…", + async { + cli::install_command_line_tool()?; + cli::resolve() + }, + |this, runtime, cx| { + this.runtime = Some(runtime); + cx.emit(StoreEvent::Notice("tokenmaxx is on your PATH".into())); + }, + cx, + ); + } + + /// Brings an older installed `tokenmaxx` up to the app's version; the next resolve adopts it. + pub fn update_command_line_tool(&mut self, cx: &mut Context) { + let Some(Runtime { + tool: CommandLineTool::Outdated { path, .. }, + bundled_version, + .. + }) = self.runtime.clone() + else { + return; + }; + self.run_in_terminal("update", cli::update_command(&path, &bundled_version), cx); + } + + /// Runs an interactive command in the chosen terminal; the poll picks up its result. + fn run_in_terminal(&mut self, name: &str, command: Vec, cx: &mut Context) { + let terminal = terminal::resolve(self.preferences.terminal.as_deref()); + match terminal::run_in_terminal(terminal, name, &command) { + Ok(()) => cx.emit(StoreEvent::Notice( + format!("Continue in {}", terminal.name).into(), + )), + Err(error) => cx.emit(StoreEvent::Error(error.to_string().into())), + } + } + + pub fn sign_in(&mut self, provider: Provider, cx: &mut Context) { + let binary = self + .runtime + .as_ref() + .map(|runtime| runtime.binary.clone()) + .unwrap_or_else(cli::bundled_binary); + self.run_in_terminal( + &format!("login-{}", provider.cli()), + vec![ + binary.display().to_string(), + "login".into(), + provider.cli().into(), + ], + cx, + ); + } +} diff --git a/gui/src/terminal.rs b/gui/src/terminal.rs new file mode 100644 index 0000000..88740f1 --- /dev/null +++ b/gui/src/terminal.rs @@ -0,0 +1,135 @@ +use std::os::unix::fs::PermissionsExt as _; +use std::path::Path; +use std::process::Command; + +use anyhow::{Result, bail}; +use objc2_app_kit::NSWorkspace; +use objc2_foundation::NSString; + +use crate::cli; +use crate::ipc::tokenmaxx_home; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Terminal { + pub bundle_id: &'static str, + pub name: &'static str, + launch: Launch, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Launch { + OpenDocument, + Execute, + Argument, + WezTerm, +} + +/// Preference order for "Automatic"; Terminal.app ships with macOS and closes the list. +pub const TERMINALS: [Terminal; 6] = [ + Terminal { + bundle_id: "com.mitchellh.ghostty", + name: "Ghostty", + launch: Launch::Execute, + }, + Terminal { + bundle_id: "com.googlecode.iterm2", + name: "iTerm", + launch: Launch::OpenDocument, + }, + Terminal { + bundle_id: "com.github.wez.wezterm", + name: "WezTerm", + launch: Launch::WezTerm, + }, + Terminal { + bundle_id: "net.kovidgoyal.kitty", + name: "kitty", + launch: Launch::Argument, + }, + Terminal { + bundle_id: "org.alacritty", + name: "Alacritty", + launch: Launch::Execute, + }, + Terminal { + bundle_id: "com.apple.Terminal", + name: "Terminal", + launch: Launch::OpenDocument, + }, +]; + +fn installed(terminal: &Terminal) -> bool { + let workspace = NSWorkspace::sharedWorkspace(); + let identifier = NSString::from_str(terminal.bundle_id); + workspace + .URLForApplicationWithBundleIdentifier(&identifier) + .is_some() +} + +pub fn installed_terminals() -> Vec { + TERMINALS.iter().copied().filter(installed).collect() +} + +/// The preferred terminal when it is installed, otherwise the first installed one in `TERMINALS` order. +pub fn resolve(preferred: Option<&str>) -> Terminal { + let available = installed_terminals(); + preferred + .and_then(|id| available.iter().find(|terminal| terminal.bundle_id == id)) + .or(available.first()) + .copied() + .unwrap_or(TERMINALS[TERMINALS.len() - 1]) +} + +fn single_quoted(text: &str) -> String { + format!("'{}'", text.replace('\'', r"'\''")) +} + +fn write_script(name: &str, command: &[String]) -> Result { + let directory = tokenmaxx_home().join("runtime"); + std::fs::create_dir_all(&directory)?; + let path = directory.join(format!("gui-{name}.command")); + let command = command + .iter() + .map(|part| single_quoted(part)) + .collect::>() + .join(" "); + let script = format!( + "#!/bin/sh\nexport PATH={}\nclear\n{command}\nprintf '\\nPress return to close. '\nread _\n", + single_quoted(cli::login_path()) + ); + std::fs::write(&path, script)?; + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700))?; + Ok(path) +} + +fn open_arguments(terminal: &Terminal, script: &Path) -> Vec { + let script = script.display().to_string(); + let name = terminal.name.to_string(); + match terminal.launch { + Launch::OpenDocument => vec!["-a".into(), name, script], + Launch::Execute => vec!["-na".into(), name, "--args".into(), "-e".into(), script], + Launch::Argument => vec!["-na".into(), name, "--args".into(), script], + Launch::WezTerm => { + vec![ + "-na".into(), + name, + "--args".into(), + "start".into(), + "--".into(), + script, + ] + } + } +} + +/// Runs `command` in a new terminal window, where interactive sign-in has a TTY. +pub fn run_in_terminal(terminal: Terminal, name: &str, command: &[String]) -> Result<()> { + let script = write_script(name, command)?; + let status = Command::new("open") + .args(open_arguments(&terminal, &script)) + .status()?; + if !status.success() { + bail!("could not open {}", terminal.name); + } + Ok(()) +} diff --git a/gui/src/tray.rs b/gui/src/tray.rs new file mode 100644 index 0000000..49c7058 --- /dev/null +++ b/gui/src/tray.rs @@ -0,0 +1,230 @@ +use anyhow::Result; +use tray_icon::menu::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem}; +use tray_icon::{Icon, TrayIcon, TrayIconBuilder}; + +use crate::format; +use crate::model::{AnalyticsSnapshot, Provider}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TrayCommand { + Switch(Provider, String), + OpenAccounts, + OpenSettings, + Refresh, + Quit, +} + +impl TrayCommand { + fn id(&self) -> String { + match self { + TrayCommand::Switch(provider, account) => { + format!("switch:{}:{account}", provider.cli()) + } + TrayCommand::OpenAccounts => "open".into(), + TrayCommand::OpenSettings => "settings".into(), + TrayCommand::Refresh => "refresh".into(), + TrayCommand::Quit => "quit".into(), + } + } + + fn parse(id: &str) -> Option { + match id { + "open" => Some(TrayCommand::OpenAccounts), + "settings" => Some(TrayCommand::OpenSettings), + "refresh" => Some(TrayCommand::Refresh), + "quit" => Some(TrayCommand::Quit), + _ => { + let (provider, account) = id.strip_prefix("switch:")?.split_once(':')?; + let provider = Provider::ALL + .into_iter() + .find(|candidate| candidate.cli() == provider)?; + Some(TrayCommand::Switch(provider, account.to_string())) + } + } + } +} + +/// One menu line, compared between polls so the menu is only rebuilt when something visible changed. +#[derive(Clone, Debug, PartialEq)] +enum Line { + Heading(String), + Account { + command: TrayCommand, + text: String, + active: bool, + }, + Note(String), + Command(TrayCommand, &'static str), + Separator, +} + +fn lines(analytics: &AnalyticsSnapshot) -> Vec { + let snapshot = &analytics.snapshot; + let mut lines = Vec::new(); + for provider in Provider::ALL { + let accounts = format::ordered_accounts(snapshot, provider); + if accounts.is_empty() { + continue; + } + let policy = snapshot.state(provider).map(|state| &state.policy); + let auto = match policy { + Some(policy) if policy.enabled => format!("auto at {}%", policy.threshold_percent), + _ => "auto off".into(), + }; + lines.push(Line::Heading(format!("{} · {auto}", provider.title()))); + let active = snapshot + .state(provider) + .and_then(|state| state.active_account_id.clone()); + for account in accounts { + let windows = format::account_windows(snapshot, account) + .into_iter() + .take(2) + .map(|window| { + format!( + "{} {}", + format::short_window(&window.label), + format::percent_label(Some(window.used_percent)) + ) + }) + .collect::>() + .join(" · "); + let text = if windows.is_empty() { + account.label.clone() + } else { + format!("{} {windows}", account.label) + }; + lines.push(Line::Account { + command: TrayCommand::Switch(provider, account.id.clone()), + text, + active: active.as_deref() == Some(account.id.as_str()), + }); + } + lines.push(Line::Separator); + } + if let Some(day) = analytics.timeframe("24h") { + let now = analytics + .tokens + .as_ref() + .map(|tokens| tokens.now_per_hour) + .unwrap_or_default(); + lines.push(Line::Note(format!( + "{}/h now · {} ≈ {} in 24h", + format::compact_number(now), + format::compact_number(day.total_tokens), + format::money_usd(day.cost_usd) + ))); + lines.push(Line::Separator); + } + lines.extend([ + Line::Command(TrayCommand::Refresh, "Refresh usage"), + Line::Command(TrayCommand::OpenAccounts, "Open tokenmaxx"), + Line::Command(TrayCommand::OpenSettings, "Settings…"), + Line::Separator, + Line::Command(TrayCommand::Quit, "Quit tokenmaxx"), + ]); + lines +} + +fn build_menu(lines: &[Line]) -> Result { + let menu = Menu::new(); + for line in lines { + match line { + Line::Heading(text) | Line::Note(text) => { + menu.append(&MenuItem::new(text, false, None))? + } + Line::Account { + command, + text, + active, + } => menu.append(&CheckMenuItem::with_id( + command.id(), + text, + true, + *active, + None, + ))?, + Line::Command(command, text) => { + menu.append(&MenuItem::with_id(command.id(), text, true, None))? + } + Line::Separator => menu.append(&PredefinedMenuItem::separator())?, + } + } + Ok(menu) +} + +/// A gauge of three rising bars, drawn as a template image so macOS tints it for the menu bar. +fn icon() -> Result { + const SIZE: u32 = 36; + let bars = [(6, 14, 16), (15, 23, 24), (24, 32, 32)]; + let mut pixels = vec![0u8; (SIZE * SIZE * 4) as usize]; + for y in 0..SIZE { + for x in 0..SIZE { + let filled = bars.iter().any(|&(left, right, height)| { + x >= left && x < right && y >= SIZE - 2 - height && y < SIZE - 2 + }); + if filled { + let index = ((y * SIZE + x) * 4) as usize; + pixels[index..index + 4].copy_from_slice(&[0, 0, 0, 255]); + } + } + } + Ok(Icon::from_rgba(pixels, SIZE, SIZE)?) +} + +pub struct Tray { + icon: TrayIcon, + lines: Vec, +} + +impl Tray { + pub fn new(analytics: &AnalyticsSnapshot) -> Result { + let lines = lines(analytics); + let icon = TrayIconBuilder::new() + .with_icon(icon()?) + .with_icon_as_template(true) + .with_tooltip("tokenmaxx") + .with_menu(Box::new(build_menu(&lines)?)) + .build()?; + Ok(Self { icon, lines }) + } + + pub fn update(&mut self, analytics: &AnalyticsSnapshot) { + let next = lines(analytics); + if next == self.lines { + return; + } + if let Ok(menu) = build_menu(&next) { + self.icon.set_menu(Some(Box::new(menu))); + self.lines = next; + } + } +} + +/// Forwards menu clicks, which arrive on AppKit's callback, into a channel the app drains on its own executor. +pub fn listen() -> smol::channel::Receiver { + let (sender, receiver) = smol::channel::unbounded(); + MenuEvent::set_event_handler(Some(move |event: MenuEvent| { + if let Some(command) = TrayCommand::parse(&event.id.0) { + sender.try_send(command).ok(); + } + })); + receiver +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn command_ids_round_trip() { + for command in [ + TrayCommand::Switch(Provider::Anthropic, "0b8f-uuid".into()), + TrayCommand::OpenAccounts, + TrayCommand::OpenSettings, + TrayCommand::Refresh, + TrayCommand::Quit, + ] { + assert_eq!(TrayCommand::parse(&command.id()), Some(command)); + } + } +} diff --git a/gui/src/views/accounts.rs b/gui/src/views/accounts.rs new file mode 100644 index 0000000..ae902e3 --- /dev/null +++ b/gui/src/views/accounts.rs @@ -0,0 +1,489 @@ +use chrono::{DateTime, Utc}; +use gpui_kit::assets::IconName; +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::menu::{DropdownMenu as _, PopupMenuItem}; +use gpui_kit::component::progress::Progress; +use gpui_kit::component::tag::Tag; +use gpui_kit::component::{ActiveTheme as _, Icon, Sizable as _, h_flex, v_flex}; +use gpui_kit::component::{Disableable as _, StyledExt as _}; +use gpui_kit::prelude::FluentBuilder as _; +use gpui_kit::*; + +use crate::format::{self, Pressure}; +use crate::model::{ + Account, AuthKind, DashboardSnapshot, OnThreshold, Provider, RoutingTarget, UsageWindow, +}; +use crate::store::{Connection, Store}; +use crate::views::{dialogs, muted, page_header, pressure_color, section_title}; + +const RECENT_SWITCH_SECONDS: i64 = 120; +const WINDOWS_PER_ROW: usize = 3; + +pub struct AccountsPage { + store: Entity, +} + +impl AccountsPage { + pub fn new(store: Entity, cx: &mut Context) -> Self { + cx.observe(&store, |_, _, cx| cx.notify()).detach(); + Self { store } + } +} + +fn window_meter(window: &UsageWindow, now: DateTime, cx: &App) -> Div { + let color = pressure_color(format::pressure(Some(window.used_percent)), cx); + let reset = format::short_reset(window.reset_at.as_deref(), now) + .map(|reset| format!("resets in {reset}")) + .unwrap_or_default(); + v_flex() + .w_32() + .flex_shrink_0() + .gap_1() + .child( + h_flex() + .justify_between() + .text_xs() + .child( + div() + .text_color(cx.theme().muted_foreground) + .child(format::short_window(&window.label)), + ) + .child( + div() + .text_color(color) + .child(format::percent_label(Some(window.used_percent))), + ), + ) + .child( + Progress::new(SharedString::from(format!("meter-{}", window.id))) + .value(format::clamp_percent(window.used_percent) as f32) + .color(color), + ) + .child(muted(reset, cx)) +} + +fn extra_usage_cell(snapshot: &DashboardSnapshot, account: &Account, cx: &App) -> Option
{ + let extra = snapshot.usage(&account.id)?.extra_usage.as_ref()?; + if !extra.enabled || account.auth == AuthKind::ApiKey { + return None; + } + let value = extra + .used_percent + .map(|percent| format::percent_label(Some(percent))) + .or(extra.balance_usd.map(format::money_usd)) + .or(extra.spent_usd.map(format::money_usd)) + .unwrap_or_else(|| "On".into()); + let color = pressure_color(format::pressure(extra.used_percent), cx); + let title = if account.on_threshold == OnThreshold::Spill { + "Extra · spills" + } else { + "Extra usage" + }; + Some( + v_flex() + .w_24() + .flex_shrink_0() + .gap_1() + .child(muted(title, cx)) + .child(div().text_sm().text_color(color).child(value)), + ) +} + +impl AccountsPage { + fn account_row( + &self, + snapshot: &DashboardSnapshot, + account: &Account, + now: DateTime, + busy: bool, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let state = snapshot.state(account.provider); + let active = + state.and_then(|state| state.active_account_id.as_deref()) == Some(account.id.as_str()); + let just_switched = active + && state + .and_then(|state| state.switched_at.as_deref()) + .and_then(format::parse_time) + .is_some_and(|at| (now - at).num_seconds() < RECENT_SWITCH_SECONDS); + let usage = snapshot.usage(&account.id); + let credits = usage + .and_then(|usage| usage.reset_credits.as_ref()) + .filter(|credits| credits.available > 0); + let extra_enabled = usage + .and_then(|usage| usage.extra_usage.as_ref()) + .is_some_and(|extra| extra.enabled) + && account.auth != AuthKind::ApiKey; + + let mut details: Vec = Vec::new(); + if let Some(plan) = format::plan_label(account.plan.as_deref()) { + details.push(Tag::secondary().small().child(plan).into_any_element()); + } + if account.auth == AuthKind::ApiKey { + details.push(Tag::secondary().small().child("API key").into_any_element()); + } + if let Some(credits) = credits { + let tag = if credits.applicable > 0 { + Tag::success() + } else { + Tag::secondary() + }; + details.push( + tag.small() + .child(format!( + "{} reset{}", + credits.available, + if credits.available == 1 { "" } else { "s" } + )) + .into_any_element(), + ); + } + if let Some(badge) = format::health_badge(account.health) { + let tag = match badge.pressure { + Pressure::Bad => Tag::danger(), + Pressure::Warn => Tag::warning(), + _ => Tag::secondary(), + }; + details.push(tag.small().child(badge.text).into_any_element()); + } + if just_switched { + details.push( + Tag::info() + .small() + .child("Just switched") + .into_any_element(), + ); + } + + let windows = format::account_windows(snapshot, account); + let meters: AnyElement = if account.auth == AuthKind::ApiKey { + let spend = usage + .and_then(|usage| usage.measured_spend_usd) + .unwrap_or_default(); + v_flex() + .w_32() + .gap_1() + .child(muted("Spend · 31 days", cx)) + .child(div().text_sm().child(format::money_usd(spend))) + .into_any_element() + } else if windows.is_empty() { + muted("No limits reported yet", cx).into_any_element() + } else { + h_flex() + .gap_4() + .children( + windows + .iter() + .take(WINDOWS_PER_ROW) + .map(|usage_window| window_meter(usage_window, now, cx)), + ) + .into_any_element() + }; + + let store = self.store.clone(); + let provider = account.provider; + let id = account.id.clone(); + let primary: AnyElement = if account.health.needs_login() { + Button::new(SharedString::from(format!("relogin-{id}"))) + .small() + .label("Sign in again") + .disabled(busy) + .on_click( + window.listener_for(&store, move |store, _, _, cx| store.sign_in(provider, cx)), + ) + .into_any_element() + } else if active { + div().w_16().into_any_element() + } else { + let target = id.clone(); + Button::new(SharedString::from(format!("use-{id}"))) + .small() + .outline() + .label("Use") + .disabled(busy) + .on_click(window.listener_for(&store, move |store, _, _, cx| { + store.switch(provider, target.clone(), cx) + })) + .into_any_element() + }; + + let account_for_menu = account.clone(); + let has_credits = credits.is_some(); + let menu_store = store.clone(); + let more = Button::new(SharedString::from(format!("more-{id}"))) + .small() + .ghost() + .icon(Icon::new(IconName::Ellipsis)) + .disabled(busy) + .dropdown_menu(move |menu, window, _| { + let account = account_for_menu.clone(); + let mut menu = menu.min_w(px(200.)); + if !active && !account.health.needs_login() { + let target = account.id.clone(); + menu = menu.item(PopupMenuItem::new("Use this account").on_click( + window.listener_for(&menu_store, move |store, _, _, cx| { + store.switch(account.provider, target.clone(), cx) + }), + )); + } + if has_credits { + let store = menu_store.clone(); + let account = account.clone(); + menu = menu.item(PopupMenuItem::new("Reset a window…").on_click( + move |_, window, cx| { + dialogs::open_reset_credits(store.clone(), account.clone(), window, cx) + }, + )); + } + if extra_enabled { + let spill = account.on_threshold == OnThreshold::Spill; + let toggled = Account { + on_threshold: if spill { + OnThreshold::Switch + } else { + OnThreshold::Spill + }, + ..account.clone() + }; + menu = menu.item( + PopupMenuItem::new("Spill into extra usage at the threshold") + .checked(spill) + .on_click(window.listener_for(&menu_store, move |store, _, _, cx| { + let toggled = toggled.clone(); + store.perform( + "Saving…", + async move { crate::ipc::save_account(&toggled).await }, + |_, _, _| {}, + cx, + ) + })), + ); + } + if account.auth == AuthKind::Oauth { + menu = menu.item(PopupMenuItem::new("Sign in again").on_click( + window.listener_for(&menu_store, move |store, _, _, cx| { + store.sign_in(account.provider, cx) + }), + )); + } + let store = menu_store.clone(); + menu.separator() + .item( + PopupMenuItem::new("Sign out…").on_click(move |_, window, cx| { + dialogs::open_sign_out(store.clone(), account.clone(), window, cx) + }), + ) + }); + + h_flex() + .id(SharedString::from(format!("row-{}", account.id))) + .gap_4() + .px_4() + .py_3() + .items_center() + .child( + div() + .w_4() + .flex_shrink_0() + .flex() + .justify_center() + .when(active, |this| { + this.child(div().size_2().rounded_full().bg(cx.theme().success)) + }), + ) + .child( + v_flex() + .flex_1() + .min_w_0() + .gap_1() + .child( + div() + .text_sm() + .truncate() + .when(active, |this| this.font_semibold()) + .child(account.label.clone()), + ) + .child(h_flex().gap_1().flex_wrap().children(details)), + ) + .child(meters) + .children(extra_usage_cell(snapshot, account, cx)) + .child( + h_flex() + .gap_1() + .w_24() + .justify_end() + .flex_shrink_0() + .child(primary) + .child(more), + ) + } + + fn provider_section( + &self, + provider: Provider, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let store = self.store.read(cx); + let snapshot = store.analytics.snapshot.clone(); + let routed = store.routing.routed.get(provider); + let cli_present = store.routing.clis.get(provider); + let busy = store.is_busy(); + let now = Utc::now(); + let policy = snapshot.state(provider).map(|state| state.policy.clone()); + + let status = if !routed { + Tag::warning().small().child("Off") + } else if let Some(policy) = policy.as_ref().filter(|policy| policy.enabled) { + Tag::success() + .small() + .child(format!("Auto-switch at {}%", policy.threshold_percent)) + } else { + Tag::secondary().small().child("Auto-switch off") + }; + + let session = snapshot.active_account(provider).and_then(|account| { + snapshot + .usage(&account.id)? + .windows + .iter() + .find(|window| format::is_five_hour_window(window)) + .and_then(|window| format::short_reset(window.reset_at.as_deref(), now)) + .map(|reset| format!("{} · session resets in {reset}", account.label)) + }); + + let accounts = format::ordered_accounts(&snapshot, provider); + let rows: Vec = accounts + .iter() + .enumerate() + .map(|(index, account)| { + div() + .when(index > 0, |this| { + this.border_t_1().border_color(cx.theme().border) + }) + .child(self.account_row(&snapshot, account, now, busy, window, cx)) + .into_any_element() + }) + .collect(); + + let entity = self.store.clone(); + let add = Button::new(SharedString::from(format!("add-{}", provider.cli()))) + .small() + .icon(Icon::new(IconName::Plus)) + .label("Add account") + .disabled(busy) + .on_click(move |_, window, cx| { + dialogs::open_add_account(entity.clone(), provider, cli_present, window, cx) + }); + + let turn_on_store = self.store.clone(); + v_flex() + .gap_2() + .child( + h_flex() + .gap_2() + .items_center() + .child(section_title(provider.title(), cx)) + .child(status) + .child(div().flex_1()) + .child(add), + ) + .when(!routed, |this| { + this.child( + h_flex() + .gap_3() + .px_4() + .py_2() + .items_center() + .rounded(cx.theme().radius) + .bg(cx.theme().warning.opacity(0.12)) + .child(Icon::new(IconName::TriangleAlert).text_color(cx.theme().warning)) + .child(div().flex_1().text_sm().child(format!( + "tokenmaxx is off for {}. It talks straight to the provider with its own login.", + provider.cli() + ))) + .child( + Button::new(SharedString::from(format!("route-{}", provider.cli()))) + .small() + .label("Turn on") + .disabled(busy) + .on_click(window.listener_for(&turn_on_store, move |store, _, _, cx| { + store.set_routing(RoutingTarget::from(provider), true, cx) + })), + ), + ) + }) + .child( + v_flex() + .rounded(cx.theme().radius_lg) + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().group_box) + .when(rows.is_empty(), |this| { + this.child( + div().px_4().py_3().child(muted( + if cli_present { + format!("No {} accounts yet.", provider.title()) + } else { + format!("Install {} to sign in, or add an API key.", provider.cli()) + }, + cx, + )), + ) + }) + .children(rows), + ) + .children(session.map(|session| muted(session, cx))) + } +} + +impl Render for AccountsPage { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let store = self.store.read(cx); + let age = store + .analytics + .snapshot + .usage + .iter() + .filter_map(|usage| format::parse_time(&usage.observed_at)) + .max() + .map(|observed| format!("Updated {} ago", format::relative_age(observed, Utc::now()))); + let refreshing = store.busy.as_deref() == Some("Refreshing…"); + let busy = store.is_busy(); + let connection = store.connection.clone(); + let refresh_store = self.store.clone(); + + v_flex() + .gap_6() + .child(page_header( + "Accounts", + h_flex() + .gap_3() + .items_center() + .children(age.map(|age| muted(age, cx))) + .child( + Button::new("refresh") + .small() + .icon(Icon::new(IconName::RefreshCw)) + .label("Refresh") + .loading(refreshing) + .disabled(busy) + .on_click( + window.listener_for(&refresh_store, |store, _, _, cx| { + store.refresh(cx) + }), + ), + ), + )) + .when_some( + match connection { + Connection::Failed(message) => Some(message), + _ => None, + }, + |this, message| this.child(muted(format!("Not connected: {message}"), cx)), + ) + .child(self.provider_section(Provider::Openai, window, cx)) + .child(self.provider_section(Provider::Anthropic, window, cx)) + } +} diff --git a/gui/src/views/analytics.rs b/gui/src/views/analytics.rs new file mode 100644 index 0000000..c087641 --- /dev/null +++ b/gui/src/views/analytics.rs @@ -0,0 +1,314 @@ +use gpui_kit::component::StyledExt as _; +use gpui_kit::component::chart::AreaChart; +use gpui_kit::component::tab::TabBar; +use gpui_kit::component::{ActiveTheme as _, Sizable as _, h_flex, v_flex}; +use gpui_kit::prelude::FluentBuilder as _; +use gpui_kit::*; + +use crate::format; +use crate::model::{TIMEFRAMES, TokenBreakdown, TokenTimeframe}; +use crate::store::Store; +use crate::views::{muted, page_header, section_title}; + +const DEFAULT_TIMEFRAME: usize = 2; +const CHART_POINTS: usize = 48; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum View { + Chart, + Pricing, +} + +pub struct AnalyticsPage { + store: Entity, + timeframe: usize, + view: View, +} + +impl AnalyticsPage { + pub fn new(store: Entity, cx: &mut Context) -> Self { + cx.observe(&store, |_, _, cx| cx.notify()).detach(); + Self { + store, + timeframe: DEFAULT_TIMEFRAME, + view: View::Chart, + } + } +} + +/// Downsamples by keeping each column's peak, so short bursts survive the resampling. +fn columns(buckets: &[f64], count: usize) -> Vec { + if buckets.len() <= count { + return buckets.to_vec(); + } + (0..count) + .map(|column| { + let low = column * buckets.len() / count; + let high = ((column + 1) * buckets.len() / count).max(low + 1); + buckets[low..high].iter().copied().fold(0.0, f64::max) + }) + .collect() +} + +fn ago_label(milliseconds: f64) -> String { + let minutes = (milliseconds / 60_000.0).round(); + if minutes < 1.0 { + "now".into() + } else if minutes < 60.0 { + format!("{minutes}m") + } else if minutes < 48.0 * 60.0 { + format!("{}h", (minutes / 60.0).round()) + } else { + format!("{}d", (minutes / 1440.0).round()) + } +} + +fn stat(title: &'static str, value: String, cx: &App) -> Div { + v_flex() + .flex_1() + .gap_1() + .p_4() + .rounded(cx.theme().radius_lg) + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().group_box) + .child(muted(title, cx)) + .child(div().text_xl().font_semibold().child(value)) +} + +fn table_row(cells: [String; 5], emphasis: bool, cx: &App) -> Div { + let [name, rest @ ..] = cells; + h_flex() + .px_4() + .py_2() + .gap_4() + .text_sm() + .when(emphasis, |this| this.font_semibold()) + .child(div().flex_1().min_w_0().truncate().child(name)) + .children( + rest.into_iter() + .map(|cell| div().w_24().flex_shrink_0().text_right().child(cell)), + ) + .border_t_1() + .border_color(cx.theme().border) +} + +fn table_head(titles: [&'static str; 5], cx: &App) -> Div { + let [name, rest @ ..] = titles; + h_flex() + .px_4() + .py_2() + .gap_4() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(div().flex_1().child(name)) + .children( + rest.into_iter() + .map(|title| div().w_24().flex_shrink_0().text_right().child(title)), + ) +} + +fn breakdown_row(name: String, breakdown: &TokenBreakdown, cx: &App) -> Div { + table_row( + [ + name, + format::compact_number(breakdown.input), + format::compact_number(breakdown.output), + format::compact_number(breakdown.cached + breakdown.cache_creation), + format::money_usd(breakdown.cost_usd), + ], + false, + cx, + ) +} + +fn card(cx: &App) -> Div { + v_flex() + .rounded(cx.theme().radius_lg) + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().group_box) + .overflow_hidden() +} + +impl AnalyticsPage { + fn chart(&self, timeframe: &TokenTimeframe, cx: &App) -> impl IntoElement { + let values = columns(&timeframe.buckets, CHART_POINTS); + let span = timeframe.bucket_ms * timeframe.buckets.len() as f64; + let step = span / values.len().max(1) as f64; + let points: Vec<(SharedString, f64)> = values + .iter() + .enumerate() + .map(|(index, value)| { + let remaining = span - step * (index + 1) as f64; + (SharedString::from(ago_label(remaining)), *value) + }) + .collect(); + let accent = cx.theme().primary; + card(cx) + .p_4() + .gap_2() + .child(section_title("Token throughput · all accounts", cx)) + .child( + div().h_64().child( + AreaChart::new(points) + .x(|point: &(SharedString, f64)| point.0.clone()) + .y(|point: &(SharedString, f64)| point.1) + .stroke(accent) + .fill(accent.opacity(0.2)) + .tick_margin(CHART_POINTS / 6) + .id("throughput"), + ), + ) + } + + fn pricing(&self, timeframe: &TokenTimeframe, cx: &App) -> impl IntoElement { + let total = table_row( + [ + "Total".into(), + format::compact_number(timeframe.total_input), + format::compact_number(timeframe.total_output), + format::compact_number(timeframe.total_cached + timeframe.total_cache_creation), + format::money_usd(timeframe.cost_usd), + ], + true, + cx, + ); + let by_class = table_row( + [ + "Value by class".into(), + format::money_usd(timeframe.cost_input), + format::money_usd(timeframe.cost_output), + format::money_usd(timeframe.cost_cached + timeframe.cost_cache_creation), + format::money_usd(timeframe.cost_usd), + ], + false, + cx, + ); + v_flex() + .gap_4() + .child( + card(cx) + .child(table_head( + ["Provider", "Input", "Output", "Cache", "Value"], + cx, + )) + .children(timeframe.by_provider.iter().map(|breakdown| { + let title = crate::model::Provider::title(breakdown.provider); + breakdown_row(title.into(), breakdown, cx) + })) + .child(total) + .child(by_class), + ) + .child( + card(cx) + .child(table_head( + ["Model", "Input", "Output", "Cache", "Value"], + cx, + )) + .children(timeframe.models.iter().map(|breakdown| { + breakdown_row( + breakdown.model.clone().unwrap_or_else(|| "unknown".into()), + breakdown, + cx, + ) + })), + ) + .child(muted( + "Priced at API list rates, for comparison with what a subscription covers.", + cx, + )) + } +} + +impl Render for AnalyticsPage { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let key = TIMEFRAMES[self.timeframe]; + let analytics = self.store.read(cx).analytics.clone(); + let timeframe = analytics + .timeframe(key) + .filter(|timeframe| timeframe.total_tokens > 0.0) + .cloned(); + let now_per_hour = analytics + .tokens + .as_ref() + .map(|tokens| tokens.now_per_hour) + .unwrap_or_default(); + + let ranges = TabBar::new("timeframe") + .segmented() + .small() + .selected_index(self.timeframe) + .on_click(cx.listener(|this, index: &usize, _, cx| { + this.timeframe = *index; + cx.notify(); + })) + .children(TIMEFRAMES); + let views = TabBar::new("view") + .segmented() + .small() + .selected_index(if self.view == View::Chart { 0 } else { 1 }) + .on_click(cx.listener(|this, index: &usize, _, cx| { + this.view = if *index == 0 { + View::Chart + } else { + View::Pricing + }; + cx.notify(); + })) + .children(["Chart", "Pricing"]); + + v_flex() + .gap_6() + .child(page_header( + "Analytics", + h_flex().gap_3().child(views).child(ranges), + )) + .map(|this| match &timeframe { + None => this.child( + card(cx) + .p_6() + .items_center() + .gap_1() + .child( + div() + .font_semibold() + .child(format!("No token usage in the last {key}")), + ) + .child(muted( + "Run codex or claude and the throughput shows up here as it streams.", + cx, + )), + ), + Some(timeframe) => this + .child( + h_flex() + .gap_4() + .child(stat( + "Tokens", + format::compact_number(timeframe.total_tokens), + cx, + )) + .child(stat( + "Value at list price", + format::money_usd(timeframe.cost_usd), + cx, + )) + .child(stat( + "Peak per hour", + format::compact_number(timeframe.peak_per_hour), + cx, + )) + .child(stat( + "Now per hour", + format::compact_number(now_per_hour), + cx, + )), + ) + .map(|this| match self.view { + View::Chart => this.child(self.chart(timeframe, cx)), + View::Pricing => this.child(self.pricing(timeframe, cx)), + }), + }) + } +} diff --git a/gui/src/views/dialogs.rs b/gui/src/views/dialogs.rs new file mode 100644 index 0000000..cb65eb7 --- /dev/null +++ b/gui/src/views/dialogs.rs @@ -0,0 +1,219 @@ +use gpui_kit::assets::IconName; +use gpui_kit::component::Disableable as _; +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::input::{Input, InputState}; +use gpui_kit::component::{ActiveTheme as _, Icon, WindowExt as _, h_flex, v_flex}; +use gpui_kit::*; + +use crate::format; +use crate::ipc; +use crate::model::{Account, Provider, ResetCode}; +use crate::store::{Store, StoreEvent}; +use crate::terminal; +use crate::views::muted; + +fn footer(buttons: impl IntoIterator) -> Div { + h_flex().gap_2().justify_end().children(buttons) +} + +fn cancel() -> Button { + Button::new("cancel") + .label("Cancel") + .on_click(|_, window, cx| window.close_dialog(cx)) +} + +pub fn open_add_account( + store: Entity, + provider: Provider, + cli_present: bool, + window: &mut Window, + cx: &mut App, +) { + let key = cx.new(|cx| { + InputState::new(window, cx) + .masked(true) + .placeholder("API key") + }); + let label = cx.new(|cx| InputState::new(window, cx).placeholder("Name shown in tokenmaxx")); + let terminal = terminal::resolve(store.read(cx).preferences.terminal.as_deref()); + window.open_dialog(cx, move |dialog, _, cx| { + let sign_in_store = store.clone(); + let add_store = store.clone(); + let (key_input, label_input) = (key.clone(), label.clone()); + let submit = move |_: &ClickEvent, window: &mut Window, cx: &mut App| { + let secret = key_input.read(cx).value().trim().to_string(); + let name = label_input.read(cx).value().trim().to_string(); + if secret.is_empty() || name.is_empty() { + return; + } + add_store.update(cx, |store, cx| { + store.perform( + format!("Adding {name}…"), + { + let name = name.clone(); + async move { ipc::add_api_key(provider, &secret, &name).await } + }, + move |_, _, cx| cx.emit(StoreEvent::Notice(format!("Added {name}").into())), + cx, + ) + }); + window.close_dialog(cx); + }; + dialog + .title(format!("Add a {} account", provider.title())) + .w(px(440.)) + .child( + v_flex() + .gap_4() + .child( + v_flex() + .gap_2() + .child( + Button::new("sign-in") + .icon(Icon::new(IconName::Terminal)) + .label(format!("Sign in with {} in {}", provider.cli(), terminal.name)) + .disabled(!cli_present) + .on_click(move |_, window, cx| { + sign_in_store.update(cx, |store, cx| store.sign_in(provider, cx)); + window.close_dialog(cx); + }), + ) + .child(muted( + if cli_present { + "Your subscription login. Finish it in the terminal window; the account appears here when done.".to_string() + } else { + format!("Install {} first to sign in with a subscription.", provider.cli()) + }, + cx, + )), + ) + .child(div().h_px().bg(cx.theme().border)) + .child( + v_flex() + .gap_2() + .child(div().text_sm().font_weight(FontWeight::MEDIUM).child("Or add an API key")) + .child(Input::new(&key)) + .child(Input::new(&label)), + ), + ) + .footer(footer([ + cancel(), + Button::new("add-key").primary().label("Add key").on_click(submit), + ])) + }); +} + +pub fn open_sign_out(store: Entity, account: Account, window: &mut Window, cx: &mut App) { + window.open_dialog(cx, move |dialog, _, _| { + let store = store.clone(); + let account = account.clone(); + let name = account.label.clone(); + dialog + .title(format!("Sign out {name}?")) + .w(px(420.)) + .child(format!( + "tokenmaxx deletes this {} credential from your Keychain. You can sign in again at any time.", + account.provider.title() + )) + .footer(footer([ + cancel(), + Button::new("sign-out").danger().label("Sign out").on_click(move |_, window, cx| { + let id = account.id.clone(); + let name = account.label.clone(); + store.update(cx, |store, cx| { + store.perform( + format!("Signing out {name}…"), + async move { ipc::remove_account(&id).await }, + move |_, _, cx| cx.emit(StoreEvent::Notice(format!("Signed out {name}").into())), + cx, + ) + }); + window.close_dialog(cx); + }), + ])) + }); +} + +fn outcome_message(code: ResetCode, windows: u32) -> String { + match code { + ResetCode::Reset => format!( + "Reset {windows} window{}", + if windows == 1 { "" } else { "s" } + ), + ResetCode::NothingToReset => "Nothing to reset; the credit stays banked".into(), + ResetCode::NoCredit => "No reset credit is available".into(), + ResetCode::AlreadyRedeemed => "That credit was already redeemed".into(), + } +} + +pub fn open_reset_credits( + store: Entity, + account: Account, + window: &mut Window, + cx: &mut App, +) { + let id = account.id.clone(); + window + .spawn(cx, async move |cx| { + let view = match ipc::reset_credits(&id).await { + Ok(view) => view, + Err(error) => { + store.update(cx, |_, cx| cx.emit(StoreEvent::Error(error.to_string().into()))); + return; + } + }; + let soonest = view + .credits + .iter() + .filter_map(|credit| credit.expires_at.as_deref()) + .filter_map(format::parse_time) + .min(); + cx.update(|window, cx| { + window.open_dialog(cx, move |dialog, _, cx| { + let store = store.clone(); + let account = account.clone(); + let expiry = soonest + .map(|at| { + format!( + "The soonest expires in {}.", + format::short_reset(Some(&at.to_rfc3339()), chrono::Utc::now()).unwrap_or_default() + ) + }) + .unwrap_or_default(); + dialog + .title("Reset a rate-limit window?") + .w(px(420.)) + .child( + v_flex() + .gap_2() + .child(format!( + "Spend one of {}'s {} banked reset credits to clear its current Codex windows.", + account.label, view.available + )) + .child(muted(expiry, cx)), + ) + .footer(footer([ + Button::new("keep").label("Keep it banked").on_click(|_, window, cx| window.close_dialog(cx)), + Button::new("reset").primary().label("Reset now").on_click(move |_, window, cx| { + let id = account.id.clone(); + store.update(cx, |store, cx| { + store.perform( + "Resetting…", + async move { ipc::consume_reset(&id).await }, + |_, outcome, cx| { + cx.emit(StoreEvent::Notice( + outcome_message(outcome.code, outcome.windows_reset).into(), + )) + }, + cx, + ) + }); + window.close_dialog(cx); + }), + ])) + }); + }) + .ok(); + }) + .detach(); +} diff --git a/gui/src/views/mod.rs b/gui/src/views/mod.rs new file mode 100644 index 0000000..eceac3c --- /dev/null +++ b/gui/src/views/mod.rs @@ -0,0 +1,46 @@ +pub mod accounts; +pub mod analytics; +pub mod dialogs; +pub mod root; +pub mod settings; + +use gpui_kit::component::{ActiveTheme as _, StyledExt as _}; +use gpui_kit::*; + +use crate::format::Pressure; + +pub fn pressure_color(pressure: Pressure, cx: &App) -> Hsla { + let theme = cx.theme(); + match pressure { + Pressure::Unknown => theme.muted_foreground, + Pressure::Good => theme.success, + Pressure::Warn => theme.warning, + Pressure::Bad => theme.danger, + } +} + +/// A page's title band: the heading on the leading edge and its commands on the trailing edge. +pub fn page_header(title: &'static str, trailing: impl IntoElement) -> Div { + div() + .flex() + .items_center() + .justify_between() + .gap_3() + .child(div().text_xl().font_semibold().child(title)) + .child(trailing) +} + +pub fn section_title(title: impl Into, cx: &App) -> Div { + div() + .text_sm() + .font_semibold() + .text_color(cx.theme().foreground) + .child(title.into()) +} + +pub fn muted(text: impl Into, cx: &App) -> Div { + div() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(text.into()) +} diff --git a/gui/src/views/root.rs b/gui/src/views/root.rs new file mode 100644 index 0000000..0c92747 --- /dev/null +++ b/gui/src/views/root.rs @@ -0,0 +1,151 @@ +use gpui_kit::assets::IconName; +use gpui_kit::component::StyledExt as _; +use gpui_kit::component::notification::Notification; +use gpui_kit::component::scroll::ScrollableElement as _; +use gpui_kit::component::sidebar::{Sidebar, SidebarGroup, SidebarMenu, SidebarMenuItem}; +use gpui_kit::component::{ + ActiveTheme as _, Icon, Root, Theme, ThemeMode, TitleBar, WindowExt as _, h_flex, v_flex, +}; +use gpui_kit::*; + +use crate::prefs::Appearance; +use crate::store::{Store, StoreEvent}; +use crate::views::accounts::AccountsPage; +use crate::views::analytics::AnalyticsPage; +use crate::views::settings::SettingsPage; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Page { + Accounts, + Analytics, + Settings, +} + +pub struct RootView { + store: Entity, + page: Page, + accounts: Entity, + analytics: Entity, + settings: Entity, + _subscriptions: Vec, +} + +pub fn apply_appearance(appearance: Appearance, window: &mut Window, cx: &mut App) { + match appearance { + Appearance::Auto => Theme::sync_system_appearance(Some(window), cx), + Appearance::Light => Theme::change(ThemeMode::Light, Some(window), cx), + Appearance::Dark => Theme::change(ThemeMode::Dark, Some(window), cx), + } +} + +impl RootView { + pub fn new( + store: Entity, + page: Page, + window: &mut Window, + cx: &mut Context, + ) -> Self { + apply_appearance(store.read(cx).preferences.appearance, window, cx); + let subscriptions = vec![ + cx.subscribe_in( + &store, + window, + |_, store, event: &StoreEvent, window, cx| match event { + StoreEvent::Notice(message) => { + window.push_notification(Notification::success(message.clone()), cx) + } + StoreEvent::Error(message) => { + window.push_notification(Notification::error(message.clone()), cx) + } + StoreEvent::PreferencesChanged => { + apply_appearance(store.read(cx).preferences.appearance, window, cx) + } + }, + ), + cx.observe_window_appearance(window, |this, window, cx| { + if this.store.read(cx).preferences.appearance == Appearance::Auto { + Theme::sync_system_appearance(Some(window), cx); + } + }), + ]; + Self { + accounts: cx.new(|cx| AccountsPage::new(store.clone(), cx)), + analytics: cx.new(|cx| AnalyticsPage::new(store.clone(), cx)), + settings: cx.new(|cx| SettingsPage::new(store.clone(), window, cx)), + store, + page, + _subscriptions: subscriptions, + } + } + + pub fn show(&mut self, page: Page, cx: &mut Context) { + self.page = page; + cx.notify(); + } + + fn menu_item( + &self, + page: Page, + label: &'static str, + icon: IconName, + cx: &mut Context, + ) -> SidebarMenuItem { + SidebarMenuItem::new(label) + .icon(Icon::new(icon)) + .active(self.page == page) + .on_click(cx.listener(move |this, _, _, cx| this.show(page, cx))) + } +} + +impl Render for RootView { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let content: AnyView = match self.page { + Page::Accounts => self.accounts.clone().into(), + Page::Analytics => self.analytics.clone().into(), + Page::Settings => self.settings.clone().into(), + }; + let busy = self.store.read(cx).busy.clone(); + v_flex() + .size_full() + .bg(cx.theme().background) + .text_color(cx.theme().foreground) + .child( + TitleBar::new().child( + h_flex() + .flex_1() + .justify_between() + .pr_3() + .child(div().text_sm().font_semibold().child("tokenmaxx")) + .children(busy.map(|busy| { + div() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(busy) + })), + ), + ) + .child( + h_flex() + .flex_1() + .min_h_0() + .child(Sidebar::new("navigation").w(px(200.)).child( + SidebarGroup::new("tokenmaxx").child(SidebarMenu::new().children([ + self.menu_item(Page::Accounts, "Accounts", IconName::Users, cx), + self.menu_item(Page::Analytics, "Analytics", IconName::ChartArea, cx), + self.menu_item(Page::Settings, "Settings", IconName::Settings, cx), + ])), + )) + .child( + div().flex_1().min_w_0().h_full().child( + div() + .id("content") + .size_full() + .overflow_y_scrollbar() + .child(div().p_6().max_w(px(960.)).child(content)), + ), + ), + ) + .children(Root::render_dialog_layer(window, cx)) + .children(Root::render_notification_layer(window, cx)) + } +} diff --git a/gui/src/views/settings.rs b/gui/src/views/settings.rs new file mode 100644 index 0000000..3c6d82b --- /dev/null +++ b/gui/src/views/settings.rs @@ -0,0 +1,491 @@ +use gpui_kit::component::Disableable as _; +use gpui_kit::component::button::Button; +use gpui_kit::component::menu::{DropdownMenu as _, PopupMenuItem}; +use gpui_kit::component::slider::{Slider, SliderEvent, SliderState}; +use gpui_kit::component::switch::Switch; +use gpui_kit::component::tab::TabBar; +use gpui_kit::component::{ActiveTheme as _, Sizable as _, h_flex, v_flex}; +use gpui_kit::prelude::FluentBuilder as _; +use gpui_kit::*; +use serde_json::json; + +use crate::cli::CommandLineTool; +use crate::format; +use crate::model::{Provider, RoutingTarget}; +use crate::prefs::{Appearance, Preferences, Presence}; +use crate::store::Store; +use crate::terminal; +use crate::views::{muted, page_header, section_title}; + +const MINUTE: f32 = 60_000.0; + +struct PolicySliders { + threshold: Entity, + cooldown: Entity, +} + +pub struct SettingsPage { + store: Entity, + sliders: [PolicySliders; 2], + _subscriptions: Vec, +} + +fn slot(provider: Provider) -> usize { + match provider { + Provider::Openai => 0, + Provider::Anthropic => 1, + } +} + +impl SettingsPage { + pub fn new(store: Entity, window: &mut Window, cx: &mut Context) -> Self { + let sliders = Provider::ALL.map(|_| PolicySliders { + threshold: cx.new(|_| { + SliderState::new() + .min(10.) + .max(100.) + .step(5.) + .default_value(90.) + }), + cooldown: cx.new(|_| { + SliderState::new() + .min(0.) + .max(60.) + .step(1.) + .default_value(5.) + }), + }); + let mut subscriptions = vec![cx.observe_in(&store, window, |this, _, window, cx| { + this.sync_sliders(window, cx); + cx.notify(); + })]; + for provider in Provider::ALL { + let PolicySliders { + threshold, + cooldown, + } = &sliders[slot(provider)]; + subscriptions.push( + cx.subscribe(threshold, move |this, _, event: &SliderEvent, cx| { + if let SliderEvent::Release(value) = event { + let percent = value.start().round(); + this.store.update(cx, |store, cx| { + store.set_policy(provider, json!({ "thresholdPercent": percent }), cx) + }); + } + }), + ); + subscriptions.push( + cx.subscribe(cooldown, move |this, _, event: &SliderEvent, cx| { + if let SliderEvent::Release(value) = event { + let milliseconds = (value.start().round() * MINUTE) as u64; + this.store.update(cx, |store, cx| { + store.set_policy( + provider, + json!({ "minimumDwellMilliseconds": milliseconds }), + cx, + ) + }); + } + }), + ); + } + let mut page = Self { + store, + sliders, + _subscriptions: subscriptions, + }; + page.sync_sliders(window, cx); + page + } + + fn sync_sliders(&mut self, window: &mut Window, cx: &mut Context) { + let snapshot = self.store.read(cx).analytics.snapshot.clone(); + for provider in Provider::ALL { + let Some(policy) = snapshot.state(provider).map(|state| &state.policy) else { + continue; + }; + let sliders = &self.sliders[slot(provider)]; + let threshold = policy.threshold_percent as f32; + let cooldown = policy.minimum_dwell_milliseconds as f32 / MINUTE; + sliders.threshold.update(cx, |state, cx| { + if state.value().start() != threshold { + state.set_value(threshold, window, cx); + } + }); + sliders.cooldown.update(cx, |state, cx| { + if state.value().start() != cooldown { + state.set_value(cooldown, window, cx); + } + }); + } + } +} + +fn row( + title: impl Into, + description: Option, + control: impl IntoElement, + cx: &App, +) -> Div { + h_flex() + .gap_4() + .px_4() + .py_3() + .items_center() + .child( + v_flex() + .flex_1() + .min_w_0() + .gap_1() + .child(div().text_sm().child(title.into())) + .children(description.map(|description| muted(description, cx))), + ) + .child(control) +} + +fn group(title: impl Into, rows: Vec
, cx: &App) -> Div { + v_flex().gap_2().child(section_title(title, cx)).child( + v_flex() + .rounded(cx.theme().radius_lg) + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().group_box) + .children(rows.into_iter().enumerate().map(|(index, row)| { + row.when(index > 0, |this| { + this.border_t_1().border_color(cx.theme().border) + }) + })), + ) +} + +fn slider_control(state: &Entity, label: String, disabled: bool) -> Div { + h_flex() + .gap_3() + .w_64() + .child(div().flex_1().child(Slider::new(state).disabled(disabled))) + .child(div().w_12().text_sm().text_right().child(label)) +} + +impl SettingsPage { + fn provider_group( + &self, + provider: Provider, + window: &mut Window, + cx: &mut Context, + ) -> Div { + let store = self.store.read(cx); + let busy = store.is_busy(); + let routed = store.routing.routed.get(provider); + let snapshot = &store.analytics.snapshot; + let Some(policy) = snapshot.state(provider).map(|state| state.policy.clone()) else { + return div(); + }; + let windows = format::provider_windows(snapshot, provider); + let sliders = &self.sliders[slot(provider)]; + let threshold = sliders.threshold.read(cx).value().start().round(); + let cooldown = (sliders.cooldown.read(cx).value().start().round() * MINUTE) as u64; + let entity = self.store.clone(); + + let mut rows = vec![ + row( + "Route through tokenmaxx", + Some( + format!( + "{} sends requests through the local proxy, which picks the account.", + provider.cli() + ) + .into(), + ), + Switch::new(SharedString::from(format!("routing-{}", provider.cli()))) + .checked(routed) + .disabled(busy) + .on_click( + window.listener_for(&entity, move |store, checked: &bool, _, cx| { + store.set_routing(RoutingTarget::from(provider), *checked, cx) + }), + ), + cx, + ), + row( + "Switch accounts automatically", + Some( + "Moves to the account with the most room when the active one fills up.".into(), + ), + Switch::new(SharedString::from(format!("auto-{}", provider.cli()))) + .checked(policy.enabled) + .disabled(busy) + .on_click( + window.listener_for(&entity, move |store, checked: &bool, _, cx| { + let changes = if *checked { + json!({ "enabled": true, "authorizationConfirmed": true }) + } else { + json!({ "enabled": false }) + }; + store.set_policy(provider, changes, cx) + }), + ), + cx, + ), + row( + "Switch at", + Some("Measured against the active account's fullest rate-limit window.".into()), + slider_control(&sliders.threshold, format!("{threshold}%"), busy), + cx, + ), + row( + "Cooldown", + Some( + "Minimum time on an account before a threshold switch. Hard limits ignore it." + .into(), + ), + slider_control(&sliders.cooldown, format::minutes_label(cooldown), busy), + cx, + ), + ]; + for usage_window in windows { + let hidden = policy.hidden_window_ids.contains(&usage_window.id); + let all_hidden = policy.hidden_window_ids.clone(); + let id = usage_window.id.clone(); + rows.push(row( + format!( + "Show the {} limit", + format::short_window(&usage_window.label) + ), + Some(usage_window.label.clone().into()), + Switch::new(SharedString::from(format!( + "window-{}-{}", + provider.cli(), + usage_window.id + ))) + .checked(!hidden) + .disabled(busy) + .on_click(window.listener_for( + &entity, + move |store, checked: &bool, _, cx| { + let next: Vec = if *checked { + all_hidden + .iter() + .filter(|known| **known != id) + .cloned() + .collect() + } else { + all_hidden.iter().cloned().chain([id.clone()]).collect() + }; + store.set_policy(provider, json!({ "hiddenWindowIds": next }), cx) + }, + )), + cx, + )); + } + group(provider.title(), rows, cx) + } + + fn app_group(&self, cx: &mut Context) -> Div { + let store = self.store.read(cx); + let preferences = store.preferences.clone(); + let terminals = store.terminals.clone(); + let entity = self.store.clone(); + + let appearance_index = match preferences.appearance { + Appearance::Auto => 0, + Appearance::Light => 1, + Appearance::Dark => 2, + }; + let presence_index = match preferences.presence { + Presence::MenuBarAndDock => 0, + Presence::MenuBar => 1, + Presence::Dock => 2, + }; + let update = |change: fn(&mut Preferences, usize)| { + let entity = entity.clone(); + move |index: &usize, _: &mut Window, cx: &mut App| { + entity.update(cx, |store, cx| { + let mut preferences = store.preferences.clone(); + change(&mut preferences, *index); + store.set_preferences(preferences, cx) + }) + } + }; + let current_terminal = terminal::resolve(preferences.terminal.as_deref()); + let terminal_label = if preferences.terminal.is_none() { + format!("Automatic ({})", current_terminal.name) + } else { + current_terminal.name.to_string() + }; + let menu_store = entity.clone(); + let chosen = preferences.terminal.clone(); + + group( + "App", + vec![ + row( + "Appearance", + None, + TabBar::new("appearance") + .segmented() + .small() + .selected_index(appearance_index) + .on_click(update(|preferences, index| { + preferences.appearance = + [Appearance::Auto, Appearance::Light, Appearance::Dark][index] + })) + .children(["Auto", "Light", "Dark"]), + cx, + ), + row( + "Show tokenmaxx in", + Some( + "Closing the window keeps tokenmaxx running in the menu bar or Dock." + .into(), + ), + TabBar::new("presence") + .segmented() + .small() + .selected_index(presence_index) + .on_click(update(|preferences, index| { + preferences.presence = + [Presence::MenuBarAndDock, Presence::MenuBar, Presence::Dock][index] + })) + .children(["Menu bar and Dock", "Menu bar", "Dock"]), + cx, + ), + row( + "Terminal for sign-in", + Some("Subscription logins run codex or claude interactively there.".into()), + Button::new("terminal") + .small() + .outline() + .label(terminal_label) + .dropdown_menu(move |menu, window, _| { + let options = std::iter::once((None, "Automatic")).chain( + terminals.iter().map(|terminal| { + (Some(terminal.bundle_id.to_string()), terminal.name) + }), + ); + options.fold(menu, |menu, (bundle_id, name)| { + let checked = bundle_id == chosen; + menu.item(PopupMenuItem::new(name).checked(checked).on_click( + window.listener_for(&menu_store, move |store, _, _, cx| { + let preferences = Preferences { + terminal: bundle_id.clone(), + ..store.preferences.clone() + }; + store.set_preferences(preferences, cx) + }), + )) + }) + }), + cx, + ), + ], + cx, + ) + } + + fn system_group(&self, window: &mut Window, cx: &mut Context) -> Div { + let store = self.store.read(cx); + let busy = store.is_busy(); + let pi = store.routing.pi; + let entity = self.store.clone(); + let runtime = store.runtime.clone(); + let version = runtime + .as_ref() + .map(|runtime| runtime.version.clone()) + .unwrap_or_else(|| "…".into()); + let latest = store.latest.clone(); + + let pi_control: AnyElement = if pi.present { + Switch::new("pi") + .checked(pi.routed) + .disabled(busy) + .on_click( + window.listener_for(&entity, |store, checked: &bool, _, cx| { + store.set_routing(RoutingTarget::Pi, *checked, cx) + }), + ) + .into_any_element() + } else { + muted("Not installed", cx).into_any_element() + }; + + let button = |id: &'static str, + label: &'static str, + action: fn(&mut Store, &mut Context)| { + Button::new(id) + .small() + .label(label) + .disabled(busy) + .on_click(window.listener_for(&entity, move |store, _, _, cx| action(store, cx))) + .into_any_element() + }; + let (tool_description, tool_control): (String, AnyElement) = match runtime + .as_ref() + .map(|runtime| (&runtime.tool, runtime)) + { + None => ("Checking your PATH…".into(), div().into_any_element()), + Some((CommandLineTool::Bundled, _)) => ( + "tokenmaxx in your terminal is this app's own copy.".into(), + muted("Installed", cx).into_any_element(), + ), + Some((CommandLineTool::Shared { path }, runtime)) => ( + format!( + "The app runs your installed tokenmaxx v{} at {path}, so both share one daemon.", + runtime.version + ), + muted("Shared", cx).into_any_element(), + ), + Some((CommandLineTool::Outdated { path, version }, runtime)) => ( + format!( + "{path} is {}, older than this app's v{}. Until it is updated, the app runs its own copy and each restarts the daemon on its version.", + version + .as_deref() + .map(|version| format!("v{version}")) + .unwrap_or_else(|| "an older version".into()), + runtime.bundled_version + ), + button("update-cli", "Update", Store::update_command_line_tool), + ), + Some((CommandLineTool::Missing, _)) => ( + "Use tokenmaxx from any terminal, sharing this app's accounts.".into(), + button("install-cli", "Install", Store::install_command_line_tool), + ), + }; + + group( + "System", + vec![ + row( + "Route pi through tokenmaxx", + Some("Adds tokenmaxx providers to pi's models.json.".into()), + pi_control, + cx, + ), + row( + "Command-line tool", + Some(tool_description.into()), + tool_control, + cx, + ), + row( + "Version", + latest.map(|latest| format!("v{latest} is available at tokenmaxx.sh").into()), + div().text_sm().child(format!("v{version}")), + cx, + ), + ], + cx, + ) + } +} + +impl Render for SettingsPage { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .gap_6() + .child(page_header("Settings", div())) + .child(self.provider_group(Provider::Openai, window, cx)) + .child(self.provider_group(Provider::Anthropic, window, cx)) + .child(self.app_group(cx)) + .child(self.system_group(window, cx)) + } +} diff --git a/gui/tests/fixtures/blitz.json b/gui/tests/fixtures/blitz.json new file mode 100644 index 0000000..901e7b3 --- /dev/null +++ b/gui/tests/fixtures/blitz.json @@ -0,0 +1,1411 @@ +{ + "snapshot": { + "accounts": [ + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "b31c07d2", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000001", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_1", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:b31c07d2" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "9f4ae815", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000002", + "identity": "ship@rubriclabs.com", + "label": "ship@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_2", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:9f4ae815" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "7c91e0b6", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000006", + "identity": "ops@rubriclabs.com", + "label": "ops@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_6", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:7c91e0b6" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "c8d2f6a1", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000003", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/3", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "4e7b93c5", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000004", + "identity": "research@rubriclabs.com", + "label": "research@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/4", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "a25d18f4", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000005", + "identity": "zero@rubriclabs.com", + "label": "zero@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/5", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "d48f2a91", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000007", + "identity": "design@rubriclabs.com", + "label": "design@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/7", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "61e7c3b0", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000008", + "identity": "agents@rubriclabs.com", + "label": "agents@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/8", + "provider": "anthropic", + "secretReference": null + } + ], + "providers": [ + { + "activeAccountId": "00000000-0000-4000-8000-000000000001", + "generation": 2, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "openai", + "thresholdPercent": 90 + }, + "provider": "openai", + "switchedAt": null + }, + { + "activeAccountId": "00000000-0000-4000-8000-000000000008", + "generation": 6, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": "2026-07-15T15:53:00.000Z" + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [ + { + "accountId": "00000000-0000-4000-8000-000000000001", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-20T04:13:12.000Z", + "usedPercent": 76 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000002", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T18:08:24.000Z", + "usedPercent": 22 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000006", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-20T04:13:12.000Z", + "usedPercent": 11 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000003", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T21:24:00.000Z", + "usedPercent": 4 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-20T14:18:00.000Z", + "usedPercent": 48 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000004", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T17:38:00.000Z", + "usedPercent": 92 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-20T04:13:12.000Z", + "usedPercent": 39 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000005", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T18:43:00.000Z", + "usedPercent": 92 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-19T18:08:24.000Z", + "usedPercent": 31 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000007", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T19:48:00.000Z", + "usedPercent": 92 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-18T21:58:48.000Z", + "usedPercent": 21 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000008", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T20:53:00.000Z", + "usedPercent": 71 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-20T14:18:00.000Z", + "usedPercent": 11 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + } + ] + }, + "tokens": { + "nowPerHour": 282903528, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 1873881, + 0, + 2273617, + 2148729, + 2632952, + 3156528, + 1347101, + 1412728, + 1519664, + 2328201, + 1266931, + 0, + 1153231, + 521115, + 0, + 1414592, + 268986, + 1499492, + 944147, + 2786525, + 2324463, + 2650957, + 1575558, + 110322, + 10906, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 281020, + 45026, + 1274392, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 234053, + 250721, + 681165, + 3116273, + 2563644, + 2301864, + 2889248, + 2471845, + 2203651, + 1418455, + 3146434, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1014589, + 0, + 1003433, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1213197, + 1789251, + 1885029, + 2429365, + 0, + 0, + 0, + 0, + 567892, + 0, + 0, + 0, + 949873, + 0, + 1246740, + 2484754, + 3939659, + 2060908, + 3834507, + 1951447, + 1698763, + 2773564, + 1931649, + 1102989, + 1641564, + 0, + 132409 + ], + "byProvider": [ + { + "cacheCreation": 843750, + "cached": 39571875, + "costUsd": 36.60525, + "input": 1265625, + "output": 506250, + "tokens": 42187500, + "provider": "anthropic" + }, + { + "cacheCreation": 1031250, + "cached": 48365625, + "costUsd": 14.166796875, + "input": 1546875, + "output": 618750, + "tokens": 51562500, + "provider": "openai" + } + ], + "costCacheCreation": 4.3828125, + "costCached": 22.490015624999998, + "costInput": 7.1929687499999995, + "costOutput": 16.70625, + "costUsd": 50.772046875, + "key": "1h", + "models": [ + { + "cacheCreation": 562500, + "cached": 26381250, + "costUsd": 29.3625, + "input": 843750, + "output": 337500, + "tokens": 28125000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 787500, + "cached": 36933750, + "costUsd": 10.81828125, + "input": 1181250, + "output": 472500, + "tokens": 39375000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 206250, + "cached": 9673125, + "costUsd": 6.45975, + "input": 309375, + "output": 123750, + "tokens": 10312500, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 243750, + "cached": 11431875, + "costUsd": 3.348515625, + "input": 365625, + "output": 146250, + "tokens": 12187500, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 75000, + "cached": 3517500, + "costUsd": 0.7829999999999999, + "input": 112500, + "output": 45000, + "tokens": 3750000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 472759080, + "totalCacheCreation": 1875000, + "totalCached": 87937500, + "totalInput": 2812500, + "totalOutput": 1125000, + "totalTokens": 93750000 + }, + { + "bucketMs": 150000, + "buckets": [ + 12692664, + 10296502, + 21495959, + 15053162, + 9195431, + 12863168, + 1220281, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4861617, + 10429490, + 10819712, + 13475983, + 21513944, + 18158869, + 4278136, + 7852928, + 8180580, + 5977487, + 3170661, + 4810945, + 0, + 0, + 0, + 0, + 12401967, + 494170, + 4028633, + 10814637, + 17493089, + 6782289, + 8009161, + 6689617, + 5993566, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4886891, + 0, + 0, + 2720091, + 0, + 0, + 167512, + 0, + 0, + 0, + 0, + 0, + 0, + 1194401, + 0, + 10240070, + 15103383, + 8365173, + 15461143, + 22771111, + 22880377, + 23273307, + 7443819, + 14839408, + 12683285, + 10147247, + 737461, + 0, + 715117, + 0, + 0, + 0, + 0, + 5277805, + 8467739, + 2951979, + 0, + 397983, + 8812233, + 157817, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [ + { + "cacheCreation": 4218750, + "cached": 197859375, + "costUsd": 183.02624999999998, + "input": 6328125, + "output": 2531250, + "tokens": 210937500, + "provider": "anthropic" + }, + { + "cacheCreation": 5156250, + "cached": 241828125, + "costUsd": 70.833984375, + "input": 7734375, + "output": 3093750, + "tokens": 257812500, + "provider": "openai" + } + ], + "costCacheCreation": 21.9140625, + "costCached": 112.450078125, + "costInput": 35.96484375, + "costOutput": 83.53125, + "costUsd": 253.86023437499998, + "key": "5h", + "models": [ + { + "cacheCreation": 2812500, + "cached": 131906250, + "costUsd": 146.8125, + "input": 4218750, + "output": 1687500, + "tokens": 140625000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 3937500, + "cached": 184668750, + "costUsd": 54.09140625, + "input": 5906250, + "output": 2362500, + "tokens": 196875000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 1031250, + "cached": 48365625, + "costUsd": 32.29875, + "input": 1546875, + "output": 618750, + "tokens": 51562500, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 1218750, + "cached": 57159375, + "costUsd": 16.742578125, + "input": 1828125, + "output": 731250, + "tokens": 60937500, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 375000, + "cached": 17587500, + "costUsd": 3.915, + "input": 562500, + "output": 225000, + "tokens": 18750000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 558559368, + "totalCacheCreation": 9375000, + "totalCached": 439687500, + "totalInput": 14062500, + "totalOutput": 5625000, + "totalTokens": 468750000 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 19657832, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11160187, + 28327746, + 80545778, + 33539138, + 85861572, + 6712031, + 41041339, + 47298892, + 0, + 0, + 0, + 0, + 0, + 44121034, + 0, + 34967027, + 91231202, + 104234495, + 43513649, + 100676318, + 121028139, + 45588472, + 90594457, + 68243135, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8041137, + 0, + 0, + 5577615, + 0, + 4470391, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6074699, + 36388180, + 39137835, + 76483895, + 28842912, + 102736586, + 115360313, + 101569636, + 59205399, + 55172505, + 0, + 0, + 39571743, + 0, + 29547720, + 0, + 17417636, + 43087023, + 0, + 1832552, + 92290060, + 81358193, + 55998054, + 18167447, + 48748681, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1392825, + 0, + 0, + 33173297, + 39026765, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 10984457, + 0 + ], + "byProvider": [ + { + "cacheCreation": 20250000, + "cached": 949725000, + "costUsd": 878.5260000000001, + "input": 30375000, + "output": 12150000, + "tokens": 1012500000, + "provider": "anthropic" + }, + { + "cacheCreation": 24750000, + "cached": 1160775000, + "costUsd": 340.003125, + "input": 37125000, + "output": 14850000, + "tokens": 1237500000, + "provider": "openai" + } + ], + "costCacheCreation": 105.1875, + "costCached": 539.7603750000001, + "costInput": 172.63125, + "costOutput": 400.95, + "costUsd": 1218.529125, + "key": "24h", + "models": [ + { + "cacheCreation": 13500000, + "cached": 633150000, + "costUsd": 704.7, + "input": 20250000, + "output": 8100000, + "tokens": 675000000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 18900000, + "cached": 886410000, + "costUsd": 259.63875, + "input": 28350000, + "output": 11340000, + "tokens": 945000000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 4950000, + "cached": 232155000, + "costUsd": 155.034, + "input": 7425000, + "output": 2970000, + "tokens": 247500000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 5850000, + "cached": 274365000, + "costUsd": 80.364375, + "input": 8775000, + "output": 3510000, + "tokens": 292500000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 1800000, + "cached": 84420000, + "costUsd": 18.792, + "input": 2700000, + "output": 1080000, + "tokens": 90000000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 605140695, + "totalCacheCreation": 45000000, + "totalCached": 2110500000, + "totalInput": 67500000, + "totalOutput": 27000000, + "totalTokens": 2250000000 + }, + { + "bucketMs": 5040000, + "buckets": [ + 92480736, + 0, + 0, + 0, + 0, + 0, + 10019505, + 0, + 0, + 45361491, + 505729366, + 440296842, + 285269503, + 590128972, + 424829351, + 548074907, + 625616506, + 529459031, + 264920673, + 215370993, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 183095542, + 0, + 295171412, + 32296933, + 139767502, + 150285787, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 120904791, + 0, + 162969914, + 281412445, + 71972469, + 239024630, + 102212963, + 300441947, + 95337812, + 0, + 105094635, + 128434753, + 0, + 0, + 0, + 288284698, + 393754350, + 394296798, + 292027258, + 777429509, + 325299788, + 718634053, + 381117000, + 421277710, + 209434894, + 0, + 28857279, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 215173984, + 121539976, + 425210605, + 177538530, + 735873321, + 720299122, + 620486090, + 267535903, + 78449599, + 51341740, + 110807309, + 0, + 0, + 167879465, + 160658493, + 0, + 0, + 12928159, + 295851379, + 372031575 + ], + "byProvider": [ + { + "cacheCreation": 141750000, + "cached": 6648074999, + "costUsd": 6149.681999499999, + "input": 212625000, + "output": 85050000, + "tokens": 7087499999, + "provider": "anthropic" + }, + { + "cacheCreation": 173250000, + "cached": 8125424999, + "costUsd": 2380.021874875, + "input": 259875000, + "output": 103950000, + "tokens": 8662499999, + "provider": "openai" + } + ], + "costCacheCreation": 736.3125, + "costCached": 3778.3226243749996, + "costInput": 1208.41875, + "costOutput": 2806.65, + "costUsd": 8529.703874374998, + "key": "7d", + "models": [ + { + "cacheCreation": 94500000, + "cached": 4432049999, + "costUsd": 4932.8999994999995, + "input": 141750000, + "output": 56700000, + "tokens": 4724999999, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 132300000, + "cached": 6204869999, + "costUsd": 1817.471249875, + "input": 198450000, + "output": 79380000, + "tokens": 6614999999, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 34650000, + "cached": 1625085000, + "costUsd": 1085.2379999999998, + "input": 51975000, + "output": 20790000, + "tokens": 1732500000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 40950000, + "cached": 1920555000, + "costUsd": 562.550625, + "input": 61425000, + "output": 24570000, + "tokens": 2047500000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 12600000, + "cached": 590940000, + "costUsd": 131.544, + "input": 18900000, + "output": 7560000, + "tokens": 630000000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 555306792, + "totalCacheCreation": 315000000, + "totalCached": 14773499998, + "totalInput": 472500000, + "totalOutput": 189000000, + "totalTokens": 15749999998 + }, + { + "bucketMs": 22320000, + "buckets": [ + 222246732, + 0, + 0, + 0, + 644822809, + 310932073, + 0, + 1842570663, + 2458693616, + 1160029277, + 812205993, + 524855179, + 1903300786, + 1316251807, + 38736130, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 779191712, + 0, + 1127354015, + 178082366, + 394627231, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 529902594, + 668445301, + 342969372, + 1712074619, + 2228494101, + 3711018886, + 4381272999, + 3114554524, + 2864547802, + 2823849715, + 878400923, + 1079288821, + 0, + 684862896, + 141753537, + 0, + 0, + 0, + 0, + 0, + 0, + 203400614, + 1506269158, + 1441531156, + 115122385, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 513752660, + 597972805, + 925712800, + 2267419330, + 1025891696, + 88047489, + 0, + 0, + 2011839, + 0, + 521363766, + 285349084, + 224906124, + 32420951, + 2557242505, + 426934099, + 1675895756, + 3699235623, + 1796150228, + 2125481644, + 3489960185, + 1718461511, + 1328692514, + 920408093, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 624328136, + 760701371 + ], + "byProvider": [ + { + "cacheCreation": 627750000, + "cached": 29441475000, + "costUsd": 27234.306, + "input": 941625000, + "output": 376650000, + "tokens": 31387500000, + "provider": "anthropic" + }, + { + "cacheCreation": 767250000, + "cached": 35984025000, + "costUsd": 10540.096875, + "input": 1150875000, + "output": 460350000, + "tokens": 38362500000, + "provider": "openai" + } + ], + "costCacheCreation": 3260.8125, + "costCached": 16732.571625, + "costInput": 5351.568749999999, + "costOutput": 12429.449999999999, + "costUsd": 37774.40287500001, + "key": "31d", + "models": [ + { + "cacheCreation": 418500000, + "cached": 19627650000, + "costUsd": 21845.7, + "input": 627750000, + "output": 251100000, + "tokens": 20925000000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 585900000, + "cached": 27478710000, + "costUsd": 8048.8012499999995, + "input": 878850000, + "output": 351540000, + "tokens": 29295000000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 153450000, + "cached": 7196805000, + "costUsd": 4806.054, + "input": 230175000, + "output": 92070000, + "tokens": 7672500000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 181350000, + "cached": 8505315000, + "costUsd": 2491.2956249999997, + "input": 272025000, + "output": 108810000, + "tokens": 9067500000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 55800000, + "cached": 2617020000, + "costUsd": 582.5519999999999, + "input": 83700000, + "output": 33480000, + "tokens": 2790000000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 706656935, + "totalCacheCreation": 1395000000, + "totalCached": 65425500000, + "totalInput": 2092500000, + "totalOutput": 837000000, + "totalTokens": 69750000000 + } + ] + } +} \ No newline at end of file diff --git a/gui/tests/fixtures/cruising.json b/gui/tests/fixtures/cruising.json new file mode 100644 index 0000000..1e326a8 --- /dev/null +++ b/gui/tests/fixtures/cruising.json @@ -0,0 +1,1321 @@ +{ + "snapshot": { + "accounts": [ + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "b31c07d2", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000001", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_1", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:b31c07d2" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "9f4ae815", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000002", + "identity": "ship@rubriclabs.com", + "label": "ship@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_2", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:9f4ae815" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "c8d2f6a1", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000003", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/3", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "4e7b93c5", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000004", + "identity": "research@rubriclabs.com", + "label": "research@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_5x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/4", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "apiKey", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "a25d18f4", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000005", + "identity": "anthropic api · prod", + "label": "anthropic api · prod", + "onThreshold": "switch", + "plan": null, + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/5", + "provider": "anthropic", + "secretReference": null + } + ], + "providers": [ + { + "activeAccountId": "00000000-0000-4000-8000-000000000001", + "generation": 4, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "openai", + "thresholdPercent": 90 + }, + "provider": "openai", + "switchedAt": "2026-07-15T15:06:00.000Z" + }, + { + "activeAccountId": "00000000-0000-4000-8000-000000000003", + "generation": 2, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": "2026-07-15T13:12:00.000Z" + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [ + { + "accountId": "00000000-0000-4000-8000-000000000001", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T18:36:00.000Z", + "usedPercent": 41 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T06:51:36.000Z", + "usedPercent": 29 + } + ], + "extraUsage": { + "balanceUsd": 18.5, + "enabled": true, + "exhausted": false, + "limitUsd": null, + "spentUsd": null, + "usedPercent": null + }, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": { + "applicable": 0, + "available": 3 + }, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000002", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T20:12:00.000Z", + "usedPercent": 8 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T10:13:12.000Z", + "usedPercent": 19 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000003", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T18:57:00.000Z", + "usedPercent": 25 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T04:42:00.000Z", + "usedPercent": 14 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-19T14:46:48.000Z", + "usedPercent": 15 + } + ], + "extraUsage": { + "balanceUsd": null, + "enabled": true, + "exhausted": false, + "limitUsd": 50, + "spentUsd": 12.4, + "usedPercent": 25 + }, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000004", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T20:30:00.000Z", + "usedPercent": 5 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T21:30:00.000Z", + "usedPercent": 9 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-21T07:06:00.000Z", + "usedPercent": 3 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000005", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [], + "extraUsage": null, + "measuredSpendUsd": 23.71, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + } + ] + }, + "tokens": { + "nowPerHour": 226338, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 1499, + 0, + 1819, + 1719, + 2106, + 2525, + 1078, + 1130, + 1216, + 1863, + 1014, + 0, + 923, + 417, + 0, + 1132, + 215, + 1200, + 755, + 2229, + 1860, + 2121, + 1260, + 88, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 225, + 36, + 1020, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 187, + 201, + 545, + 2493, + 2051, + 1841, + 2311, + 1977, + 1763, + 1135, + 2517, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 812, + 0, + 803, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 971, + 1431, + 1508, + 1943, + 0, + 0, + 0, + 0, + 454, + 0, + 0, + 0, + 760, + 0, + 997, + 1988, + 3152, + 1649, + 3068, + 1561, + 1359, + 2219, + 1545, + 882, + 1313, + 0, + 106 + ], + "byProvider": [ + { + "cacheCreation": 675, + "cached": 31657, + "costUsd": 0.02928555, + "input": 1013, + "output": 405, + "tokens": 33750, + "provider": "anthropic" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.011334, + "input": 1238, + "output": 495, + "tokens": 41250, + "provider": "openai" + } + ], + "costCacheCreation": 0.00350625, + "costCached": 0.017991800000000002, + "costInput": 0.0057565, + "costOutput": 0.013365, + "costUsd": 0.04061955, + "key": "1h", + "models": [ + { + "cacheCreation": 450, + "cached": 21105, + "costUsd": 0.02349, + "input": 675, + "output": 270, + "tokens": 22500, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 630, + "cached": 29547, + "costUsd": 0.008654625, + "input": 945, + "output": 378, + "tokens": 31500, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 165, + "cached": 7738, + "costUsd": 0.005169149999999999, + "input": 248, + "output": 99, + "tokens": 8250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 195, + "cached": 9145, + "costUsd": 0.0026793750000000003, + "input": 293, + "output": 117, + "tokens": 9750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 60, + "cached": 2814, + "costUsd": 0.0006264, + "input": 90, + "output": 36, + "tokens": 3000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 378240, + "totalCacheCreation": 1500, + "totalCached": 70349, + "totalInput": 2251, + "totalOutput": 900, + "totalTokens": 75000 + }, + { + "bucketMs": 150000, + "buckets": [ + 10154, + 8237, + 17197, + 12043, + 7356, + 10291, + 976, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3889, + 8344, + 8656, + 10781, + 17211, + 14527, + 3423, + 6282, + 6544, + 4782, + 2537, + 3849, + 0, + 0, + 0, + 0, + 9922, + 395, + 3223, + 8652, + 13994, + 5426, + 6407, + 5352, + 4795, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3910, + 0, + 0, + 2176, + 0, + 0, + 134, + 0, + 0, + 0, + 0, + 0, + 0, + 956, + 0, + 8192, + 12083, + 6692, + 12369, + 18217, + 18304, + 18619, + 5955, + 11872, + 10147, + 8118, + 590, + 0, + 572, + 0, + 0, + 0, + 0, + 4222, + 6774, + 2362, + 0, + 318, + 7050, + 126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [ + { + "cacheCreation": 3375, + "cached": 158288, + "costUsd": 0.14642285000000002, + "input": 5063, + "output": 2025, + "tokens": 168751, + "provider": "anthropic" + }, + { + "cacheCreation": 4125, + "cached": 193463, + "costUsd": 0.056667875, + "input": 6188, + "output": 2475, + "tokens": 206251, + "provider": "openai" + } + ], + "costCacheCreation": 0.017531249999999998, + "costCached": 0.089960475, + "costInput": 0.028773999999999997, + "costOutput": 0.066825, + "costUsd": 0.203090725, + "key": "5h", + "models": [ + { + "cacheCreation": 2250, + "cached": 105526, + "costUsd": 0.11745050000000001, + "input": 3375, + "output": 1350, + "tokens": 112501, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 3150, + "cached": 147736, + "costUsd": 0.04327325, + "input": 4725, + "output": 1890, + "tokens": 157501, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.025840349999999998, + "input": 1238, + "output": 495, + "tokens": 41250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 975, + "cached": 45727, + "costUsd": 0.013394625, + "input": 1463, + "output": 585, + "tokens": 48750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 300, + "cached": 14070, + "costUsd": 0.003132, + "input": 450, + "output": 180, + "tokens": 15000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 446856, + "totalCacheCreation": 7500, + "totalCached": 351751, + "totalInput": 11251, + "totalOutput": 4500, + "totalTokens": 375002 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 15726, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8928, + 22662, + 64437, + 26831, + 68689, + 5370, + 32833, + 37839, + 0, + 0, + 0, + 0, + 0, + 35297, + 0, + 27974, + 72985, + 83388, + 34811, + 80541, + 96823, + 36471, + 72476, + 54595, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6433, + 0, + 0, + 4462, + 0, + 3576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4860, + 29111, + 31310, + 61187, + 23074, + 82189, + 92288, + 81256, + 47364, + 44138, + 0, + 0, + 31657, + 0, + 23638, + 0, + 13934, + 34470, + 0, + 1466, + 73832, + 65087, + 44798, + 14534, + 38999, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1114, + 0, + 0, + 26539, + 31221, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8788, + 0 + ], + "byProvider": [ + { + "cacheCreation": 16200, + "cached": 759780, + "costUsd": 0.7028207999999999, + "input": 24300, + "output": 9720, + "tokens": 810000, + "provider": "anthropic" + }, + { + "cacheCreation": 19800, + "cached": 928620, + "costUsd": 0.2720025, + "input": 29700, + "output": 11880, + "tokens": 990000, + "provider": "openai" + } + ], + "costCacheCreation": 0.08415, + "costCached": 0.43180830000000003, + "costInput": 0.13810499999999998, + "costOutput": 0.32075999999999993, + "costUsd": 0.9748233, + "key": "24h", + "models": [ + { + "cacheCreation": 10800, + "cached": 506520, + "costUsd": 0.5637599999999999, + "input": 16200, + "output": 6480, + "tokens": 540000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 15120, + "cached": 709128, + "costUsd": 0.20771099999999998, + "input": 22680, + "output": 9072, + "tokens": 756000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 3960, + "cached": 185724, + "costUsd": 0.1240272, + "input": 5940, + "output": 2376, + "tokens": 198000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 4680, + "cached": 219492, + "costUsd": 0.0642915, + "input": 7020, + "output": 2808, + "tokens": 234000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 1440, + "cached": 67536, + "costUsd": 0.0150336, + "input": 2160, + "output": 864, + "tokens": 72000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 484115, + "totalCacheCreation": 36000, + "totalCached": 1688400, + "totalInput": 54000, + "totalOutput": 21600, + "totalTokens": 1800000 + }, + { + "bucketMs": 5040000, + "buckets": [ + 73985, + 0, + 0, + 0, + 0, + 0, + 8016, + 0, + 0, + 36289, + 404583, + 352237, + 228216, + 472103, + 339863, + 438460, + 500493, + 423567, + 211937, + 172297, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 146476, + 0, + 236137, + 25838, + 111814, + 120229, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96724, + 0, + 130376, + 225130, + 57578, + 191220, + 81770, + 240354, + 76270, + 0, + 84076, + 102748, + 0, + 0, + 0, + 230628, + 315003, + 315437, + 233622, + 621944, + 260240, + 574907, + 304894, + 337022, + 167548, + 0, + 23086, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 172139, + 97232, + 340168, + 142031, + 588699, + 576239, + 496389, + 214029, + 62760, + 41073, + 88646, + 0, + 0, + 134304, + 128527, + 0, + 0, + 10343, + 236681, + 297625 + ], + "byProvider": [ + { + "cacheCreation": 113400, + "cached": 5318461, + "costUsd": 4.9197461, + "input": 170100, + "output": 68040, + "tokens": 5670001, + "provider": "anthropic" + }, + { + "cacheCreation": 138600, + "cached": 6500341, + "costUsd": 1.9040176250000003, + "input": 207900, + "output": 83160, + "tokens": 6930001, + "provider": "openai" + } + ], + "costCacheCreation": 0.5890500000000001, + "costCached": 3.022658725, + "costInput": 0.966735, + "costOutput": 2.24532, + "costUsd": 6.823763725, + "key": "7d", + "models": [ + { + "cacheCreation": 75600, + "cached": 3545641, + "costUsd": 3.9463204999999997, + "input": 113400, + "output": 45360, + "tokens": 3780001, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 105840, + "cached": 4963897, + "costUsd": 1.4539771250000002, + "input": 158760, + "output": 63504, + "tokens": 5292001, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 27720, + "cached": 1300068, + "costUsd": 0.8681904, + "input": 41580, + "output": 16632, + "tokens": 1386000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 32760, + "cached": 1536444, + "costUsd": 0.4500405, + "input": 49140, + "output": 19656, + "tokens": 1638000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 10080, + "cached": 472752, + "costUsd": 0.1052352, + "input": 15120, + "output": 6048, + "tokens": 504000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 444246, + "totalCacheCreation": 252000, + "totalCached": 11818802, + "totalInput": 378000, + "totalOutput": 151200, + "totalTokens": 12600002 + }, + { + "bucketMs": 22320000, + "buckets": [ + 177797, + 0, + 0, + 0, + 515858, + 248746, + 0, + 1474057, + 1966955, + 928023, + 649765, + 419884, + 1522641, + 1053001, + 30989, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 623353, + 0, + 901883, + 142466, + 315702, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 423922, + 534756, + 274375, + 1369660, + 1782795, + 2968815, + 3505018, + 2491644, + 2291638, + 2259080, + 702721, + 863431, + 0, + 547890, + 113403, + 0, + 0, + 0, + 0, + 0, + 0, + 162720, + 1205015, + 1153225, + 92098, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 411002, + 478378, + 740570, + 1813935, + 820713, + 70438, + 0, + 0, + 1609, + 0, + 417091, + 228279, + 179925, + 25937, + 2045794, + 341547, + 1340717, + 2959388, + 1436920, + 1700385, + 2791968, + 1374769, + 1062954, + 736326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 499463, + 608561 + ], + "byProvider": [ + { + "cacheCreation": 502200, + "cached": 23553178, + "costUsd": 21.787444, + "input": 753300, + "output": 301320, + "tokens": 25109998, + "provider": "anthropic" + }, + { + "cacheCreation": 613800, + "cached": 28787217, + "costUsd": 8.432077125, + "input": 920700, + "output": 368280, + "tokens": 30689997, + "provider": "openai" + } + ], + "costCacheCreation": 2.60865, + "costCached": 13.386056125, + "costInput": 4.281255, + "costOutput": 9.94356, + "costUsd": 30.219521125, + "key": "31d", + "models": [ + { + "cacheCreation": 334800, + "cached": 15702119, + "costUsd": 17.4765595, + "input": 502200, + "output": 200880, + "tokens": 16739999, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 468720, + "cached": 21982966, + "costUsd": 6.43904075, + "input": 703080, + "output": 281232, + "tokens": 23435998, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 122760, + "cached": 5757443, + "costUsd": 3.8448428999999997, + "input": 184140, + "output": 73656, + "tokens": 6137999, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 145080, + "cached": 6804251, + "costUsd": 1.993036375, + "input": 217620, + "output": 87048, + "tokens": 7253999, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 44640, + "cached": 2093616, + "costUsd": 0.4660416, + "input": 66960, + "output": 26784, + "tokens": 2232000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 565325, + "totalCacheCreation": 1116000, + "totalCached": 52340395, + "totalInput": 1674000, + "totalOutput": 669600, + "totalTokens": 55799995 + } + ] + } +} \ No newline at end of file diff --git a/gui/tests/fixtures/onboarding.json b/gui/tests/fixtures/onboarding.json new file mode 100644 index 0000000..713b984 --- /dev/null +++ b/gui/tests/fixtures/onboarding.json @@ -0,0 +1,741 @@ +{ + "snapshot": { + "accounts": [], + "providers": [ + { + "activeAccountId": null, + "generation": 0, + "policy": { + "authorization": "notConfirmed", + "enabled": false, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "openai", + "thresholdPercent": 90 + }, + "provider": "openai", + "switchedAt": null + }, + { + "activeAccountId": null, + "generation": 0, + "policy": { + "authorization": "notConfirmed", + "enabled": false, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": null + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [] + }, + "tokens": { + "nowPerHour": 0, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [], + "costCacheCreation": 0, + "costCached": 0, + "costInput": 0, + "costOutput": 0, + "costUsd": 0, + "key": "1h", + "models": [], + "peakPerHour": 0, + "totalCacheCreation": 0, + "totalCached": 0, + "totalInput": 0, + "totalOutput": 0, + "totalTokens": 0 + }, + { + "bucketMs": 150000, + "buckets": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [], + "costCacheCreation": 0, + "costCached": 0, + "costInput": 0, + "costOutput": 0, + "costUsd": 0, + "key": "5h", + "models": [], + "peakPerHour": 0, + "totalCacheCreation": 0, + "totalCached": 0, + "totalInput": 0, + "totalOutput": 0, + "totalTokens": 0 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [], + "costCacheCreation": 0, + "costCached": 0, + "costInput": 0, + "costOutput": 0, + "costUsd": 0, + "key": "24h", + "models": [], + "peakPerHour": 0, + "totalCacheCreation": 0, + "totalCached": 0, + "totalInput": 0, + "totalOutput": 0, + "totalTokens": 0 + }, + { + "bucketMs": 5040000, + "buckets": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [], + "costCacheCreation": 0, + "costCached": 0, + "costInput": 0, + "costOutput": 0, + "costUsd": 0, + "key": "7d", + "models": [], + "peakPerHour": 0, + "totalCacheCreation": 0, + "totalCached": 0, + "totalInput": 0, + "totalOutput": 0, + "totalTokens": 0 + }, + { + "bucketMs": 22320000, + "buckets": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [], + "costCacheCreation": 0, + "costCached": 0, + "costInput": 0, + "costOutput": 0, + "costUsd": 0, + "key": "31d", + "models": [], + "peakPerHour": 0, + "totalCacheCreation": 0, + "totalCached": 0, + "totalInput": 0, + "totalOutput": 0, + "totalTokens": 0 + } + ] + } +} \ No newline at end of file diff --git a/gui/tests/fixtures/oneHot.json b/gui/tests/fixtures/oneHot.json new file mode 100644 index 0000000..7c6a506 --- /dev/null +++ b/gui/tests/fixtures/oneHot.json @@ -0,0 +1,1280 @@ +{ + "snapshot": { + "accounts": [ + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "b31c07d2", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000001", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_1", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:b31c07d2" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "9f4ae815", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000002", + "identity": "ship@rubriclabs.com", + "label": "ship@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_2", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:9f4ae815" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "c8d2f6a1", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000003", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/3", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "4e7b93c5", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000004", + "identity": "research@rubriclabs.com", + "label": "research@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_5x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/4", + "provider": "anthropic", + "secretReference": null + } + ], + "providers": [ + { + "activeAccountId": "00000000-0000-4000-8000-000000000001", + "generation": 7, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "openai", + "thresholdPercent": 90 + }, + "provider": "openai", + "switchedAt": "2026-07-15T11:54:00.000Z" + }, + { + "activeAccountId": "00000000-0000-4000-8000-000000000003", + "generation": 3, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": "2026-07-15T09:42:00.000Z" + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [ + { + "accountId": "00000000-0000-4000-8000-000000000001", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T17:21:00.000Z", + "usedPercent": 96 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-17T19:06:00.000Z", + "usedPercent": 52 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": { + "applicable": 1, + "available": 2 + }, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000002", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T20:18:00.000Z", + "usedPercent": 8 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T11:54:00.000Z", + "usedPercent": 23 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000003", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T18:42:00.000Z", + "usedPercent": 38 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T08:32:24.000Z", + "usedPercent": 29 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-16T09:30:00.000Z", + "usedPercent": 85 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000004", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T19:57:00.000Z", + "usedPercent": 12 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T13:06:00.000Z", + "usedPercent": 12 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-20T14:18:00.000Z", + "usedPercent": 6 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + } + ] + }, + "tokens": { + "nowPerHour": 226338, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 1499, + 0, + 1819, + 1719, + 2106, + 2525, + 1078, + 1130, + 1216, + 1863, + 1014, + 0, + 923, + 417, + 0, + 1132, + 215, + 1200, + 755, + 2229, + 1860, + 2121, + 1260, + 88, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 225, + 36, + 1020, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 187, + 201, + 545, + 2493, + 2051, + 1841, + 2311, + 1977, + 1763, + 1135, + 2517, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 812, + 0, + 803, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 971, + 1431, + 1508, + 1943, + 0, + 0, + 0, + 0, + 454, + 0, + 0, + 0, + 760, + 0, + 997, + 1988, + 3152, + 1649, + 3068, + 1561, + 1359, + 2219, + 1545, + 882, + 1313, + 0, + 106 + ], + "byProvider": [ + { + "cacheCreation": 675, + "cached": 31657, + "costUsd": 0.02928555, + "input": 1013, + "output": 405, + "tokens": 33750, + "provider": "anthropic" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.011334, + "input": 1238, + "output": 495, + "tokens": 41250, + "provider": "openai" + } + ], + "costCacheCreation": 0.00350625, + "costCached": 0.017991800000000002, + "costInput": 0.0057565, + "costOutput": 0.013365, + "costUsd": 0.04061955, + "key": "1h", + "models": [ + { + "cacheCreation": 450, + "cached": 21105, + "costUsd": 0.02349, + "input": 675, + "output": 270, + "tokens": 22500, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 630, + "cached": 29547, + "costUsd": 0.008654625, + "input": 945, + "output": 378, + "tokens": 31500, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 165, + "cached": 7738, + "costUsd": 0.005169149999999999, + "input": 248, + "output": 99, + "tokens": 8250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 195, + "cached": 9145, + "costUsd": 0.0026793750000000003, + "input": 293, + "output": 117, + "tokens": 9750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 60, + "cached": 2814, + "costUsd": 0.0006264, + "input": 90, + "output": 36, + "tokens": 3000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 378240, + "totalCacheCreation": 1500, + "totalCached": 70349, + "totalInput": 2251, + "totalOutput": 900, + "totalTokens": 75000 + }, + { + "bucketMs": 150000, + "buckets": [ + 10154, + 8237, + 17197, + 12043, + 7356, + 10291, + 976, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3889, + 8344, + 8656, + 10781, + 17211, + 14527, + 3423, + 6282, + 6544, + 4782, + 2537, + 3849, + 0, + 0, + 0, + 0, + 9922, + 395, + 3223, + 8652, + 13994, + 5426, + 6407, + 5352, + 4795, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3910, + 0, + 0, + 2176, + 0, + 0, + 134, + 0, + 0, + 0, + 0, + 0, + 0, + 956, + 0, + 8192, + 12083, + 6692, + 12369, + 18217, + 18304, + 18619, + 5955, + 11872, + 10147, + 8118, + 590, + 0, + 572, + 0, + 0, + 0, + 0, + 4222, + 6774, + 2362, + 0, + 318, + 7050, + 126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [ + { + "cacheCreation": 3375, + "cached": 158288, + "costUsd": 0.14642285000000002, + "input": 5063, + "output": 2025, + "tokens": 168751, + "provider": "anthropic" + }, + { + "cacheCreation": 4125, + "cached": 193463, + "costUsd": 0.056667875, + "input": 6188, + "output": 2475, + "tokens": 206251, + "provider": "openai" + } + ], + "costCacheCreation": 0.017531249999999998, + "costCached": 0.089960475, + "costInput": 0.028773999999999997, + "costOutput": 0.066825, + "costUsd": 0.203090725, + "key": "5h", + "models": [ + { + "cacheCreation": 2250, + "cached": 105526, + "costUsd": 0.11745050000000001, + "input": 3375, + "output": 1350, + "tokens": 112501, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 3150, + "cached": 147736, + "costUsd": 0.04327325, + "input": 4725, + "output": 1890, + "tokens": 157501, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.025840349999999998, + "input": 1238, + "output": 495, + "tokens": 41250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 975, + "cached": 45727, + "costUsd": 0.013394625, + "input": 1463, + "output": 585, + "tokens": 48750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 300, + "cached": 14070, + "costUsd": 0.003132, + "input": 450, + "output": 180, + "tokens": 15000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 446856, + "totalCacheCreation": 7500, + "totalCached": 351751, + "totalInput": 11251, + "totalOutput": 4500, + "totalTokens": 375002 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 15726, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8928, + 22662, + 64437, + 26831, + 68689, + 5370, + 32833, + 37839, + 0, + 0, + 0, + 0, + 0, + 35297, + 0, + 27974, + 72985, + 83388, + 34811, + 80541, + 96823, + 36471, + 72476, + 54595, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6433, + 0, + 0, + 4462, + 0, + 3576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4860, + 29111, + 31310, + 61187, + 23074, + 82189, + 92288, + 81256, + 47364, + 44138, + 0, + 0, + 31657, + 0, + 23638, + 0, + 13934, + 34470, + 0, + 1466, + 73832, + 65087, + 44798, + 14534, + 38999, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1114, + 0, + 0, + 26539, + 31221, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8788, + 0 + ], + "byProvider": [ + { + "cacheCreation": 16200, + "cached": 759780, + "costUsd": 0.7028207999999999, + "input": 24300, + "output": 9720, + "tokens": 810000, + "provider": "anthropic" + }, + { + "cacheCreation": 19800, + "cached": 928620, + "costUsd": 0.2720025, + "input": 29700, + "output": 11880, + "tokens": 990000, + "provider": "openai" + } + ], + "costCacheCreation": 0.08415, + "costCached": 0.43180830000000003, + "costInput": 0.13810499999999998, + "costOutput": 0.32075999999999993, + "costUsd": 0.9748233, + "key": "24h", + "models": [ + { + "cacheCreation": 10800, + "cached": 506520, + "costUsd": 0.5637599999999999, + "input": 16200, + "output": 6480, + "tokens": 540000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 15120, + "cached": 709128, + "costUsd": 0.20771099999999998, + "input": 22680, + "output": 9072, + "tokens": 756000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 3960, + "cached": 185724, + "costUsd": 0.1240272, + "input": 5940, + "output": 2376, + "tokens": 198000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 4680, + "cached": 219492, + "costUsd": 0.0642915, + "input": 7020, + "output": 2808, + "tokens": 234000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 1440, + "cached": 67536, + "costUsd": 0.0150336, + "input": 2160, + "output": 864, + "tokens": 72000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 484115, + "totalCacheCreation": 36000, + "totalCached": 1688400, + "totalInput": 54000, + "totalOutput": 21600, + "totalTokens": 1800000 + }, + { + "bucketMs": 5040000, + "buckets": [ + 73985, + 0, + 0, + 0, + 0, + 0, + 8016, + 0, + 0, + 36289, + 404583, + 352237, + 228216, + 472103, + 339863, + 438460, + 500493, + 423567, + 211937, + 172297, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 146476, + 0, + 236137, + 25838, + 111814, + 120229, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96724, + 0, + 130376, + 225130, + 57578, + 191220, + 81770, + 240354, + 76270, + 0, + 84076, + 102748, + 0, + 0, + 0, + 230628, + 315003, + 315437, + 233622, + 621944, + 260240, + 574907, + 304894, + 337022, + 167548, + 0, + 23086, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 172139, + 97232, + 340168, + 142031, + 588699, + 576239, + 496389, + 214029, + 62760, + 41073, + 88646, + 0, + 0, + 134304, + 128527, + 0, + 0, + 10343, + 236681, + 297625 + ], + "byProvider": [ + { + "cacheCreation": 113400, + "cached": 5318461, + "costUsd": 4.9197461, + "input": 170100, + "output": 68040, + "tokens": 5670001, + "provider": "anthropic" + }, + { + "cacheCreation": 138600, + "cached": 6500341, + "costUsd": 1.9040176250000003, + "input": 207900, + "output": 83160, + "tokens": 6930001, + "provider": "openai" + } + ], + "costCacheCreation": 0.5890500000000001, + "costCached": 3.022658725, + "costInput": 0.966735, + "costOutput": 2.24532, + "costUsd": 6.823763725, + "key": "7d", + "models": [ + { + "cacheCreation": 75600, + "cached": 3545641, + "costUsd": 3.9463204999999997, + "input": 113400, + "output": 45360, + "tokens": 3780001, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 105840, + "cached": 4963897, + "costUsd": 1.4539771250000002, + "input": 158760, + "output": 63504, + "tokens": 5292001, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 27720, + "cached": 1300068, + "costUsd": 0.8681904, + "input": 41580, + "output": 16632, + "tokens": 1386000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 32760, + "cached": 1536444, + "costUsd": 0.4500405, + "input": 49140, + "output": 19656, + "tokens": 1638000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 10080, + "cached": 472752, + "costUsd": 0.1052352, + "input": 15120, + "output": 6048, + "tokens": 504000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 444246, + "totalCacheCreation": 252000, + "totalCached": 11818802, + "totalInput": 378000, + "totalOutput": 151200, + "totalTokens": 12600002 + }, + { + "bucketMs": 22320000, + "buckets": [ + 177797, + 0, + 0, + 0, + 515858, + 248746, + 0, + 1474057, + 1966955, + 928023, + 649765, + 419884, + 1522641, + 1053001, + 30989, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 623353, + 0, + 901883, + 142466, + 315702, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 423922, + 534756, + 274375, + 1369660, + 1782795, + 2968815, + 3505018, + 2491644, + 2291638, + 2259080, + 702721, + 863431, + 0, + 547890, + 113403, + 0, + 0, + 0, + 0, + 0, + 0, + 162720, + 1205015, + 1153225, + 92098, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 411002, + 478378, + 740570, + 1813935, + 820713, + 70438, + 0, + 0, + 1609, + 0, + 417091, + 228279, + 179925, + 25937, + 2045794, + 341547, + 1340717, + 2959388, + 1436920, + 1700385, + 2791968, + 1374769, + 1062954, + 736326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 499463, + 608561 + ], + "byProvider": [ + { + "cacheCreation": 502200, + "cached": 23553178, + "costUsd": 21.787444, + "input": 753300, + "output": 301320, + "tokens": 25109998, + "provider": "anthropic" + }, + { + "cacheCreation": 613800, + "cached": 28787217, + "costUsd": 8.432077125, + "input": 920700, + "output": 368280, + "tokens": 30689997, + "provider": "openai" + } + ], + "costCacheCreation": 2.60865, + "costCached": 13.386056125, + "costInput": 4.281255, + "costOutput": 9.94356, + "costUsd": 30.219521125, + "key": "31d", + "models": [ + { + "cacheCreation": 334800, + "cached": 15702119, + "costUsd": 17.4765595, + "input": 502200, + "output": 200880, + "tokens": 16739999, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 468720, + "cached": 21982966, + "costUsd": 6.43904075, + "input": 703080, + "output": 281232, + "tokens": 23435998, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 122760, + "cached": 5757443, + "costUsd": 3.8448428999999997, + "input": 184140, + "output": 73656, + "tokens": 6137999, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 145080, + "cached": 6804251, + "costUsd": 1.993036375, + "input": 217620, + "output": 87048, + "tokens": 7253999, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 44640, + "cached": 2093616, + "costUsd": 0.4660416, + "input": 66960, + "output": 26784, + "tokens": 2232000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 565325, + "totalCacheCreation": 1116000, + "totalCached": 52340395, + "totalInput": 1674000, + "totalOutput": 669600, + "totalTokens": 55799995 + } + ] + } +} \ No newline at end of file diff --git a/gui/tests/fixtures/relay.json b/gui/tests/fixtures/relay.json new file mode 100644 index 0000000..e670f2e --- /dev/null +++ b/gui/tests/fixtures/relay.json @@ -0,0 +1,1348 @@ +{ + "snapshot": { + "accounts": [ + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "b31c07d2", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000001", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_1", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:b31c07d2" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "9f4ae815", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000002", + "identity": "ship@rubriclabs.com", + "label": "ship@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_2", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:9f4ae815" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "7c91e0b6", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000006", + "identity": "ops@rubriclabs.com", + "label": "ops@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_6", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:7c91e0b6" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "c8d2f6a1", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000003", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/3", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "4e7b93c5", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000004", + "identity": "research@rubriclabs.com", + "label": "research@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/4", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "a25d18f4", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000005", + "identity": "zero@rubriclabs.com", + "label": "zero@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/5", + "provider": "anthropic", + "secretReference": null + } + ], + "providers": [ + { + "activeAccountId": "00000000-0000-4000-8000-000000000006", + "generation": 7, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "openai", + "thresholdPercent": 90 + }, + "provider": "openai", + "switchedAt": "2026-07-15T16:30:57.142Z" + }, + { + "activeAccountId": "00000000-0000-4000-8000-000000000005", + "generation": 4, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": "2026-07-15T15:30:00.000Z" + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [ + { + "accountId": "00000000-0000-4000-8000-000000000001", + "hardLimitReached": true, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 100 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T11:54:00.000Z", + "usedPercent": 24 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000002", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 95 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T11:54:00.000Z", + "usedPercent": 27 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000006", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 51 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T11:54:00.000Z", + "usedPercent": 35 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000003", + "hardLimitReached": true, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 100 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T20:18:00.000Z", + "usedPercent": 18 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000004", + "hardLimitReached": true, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 100 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T20:18:00.000Z", + "usedPercent": 24 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000005", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "session", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 64 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T20:18:00.000Z", + "usedPercent": 27 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + } + ] + }, + "tokens": { + "nowPerHour": 226338, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 1499, + 0, + 1819, + 1719, + 2106, + 2525, + 1078, + 1130, + 1216, + 1863, + 1014, + 0, + 923, + 417, + 0, + 1132, + 215, + 1200, + 755, + 2229, + 1860, + 2121, + 1260, + 88, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 225, + 36, + 1020, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 187, + 201, + 545, + 2493, + 2051, + 1841, + 2311, + 1977, + 1763, + 1135, + 2517, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 812, + 0, + 803, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 971, + 1431, + 1508, + 1943, + 0, + 0, + 0, + 0, + 454, + 0, + 0, + 0, + 760, + 0, + 997, + 1988, + 3152, + 1649, + 3068, + 1561, + 1359, + 2219, + 1545, + 882, + 1313, + 0, + 106 + ], + "byProvider": [ + { + "cacheCreation": 675, + "cached": 31657, + "costUsd": 0.02928555, + "input": 1013, + "output": 405, + "tokens": 33750, + "provider": "anthropic" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.011334, + "input": 1238, + "output": 495, + "tokens": 41250, + "provider": "openai" + } + ], + "costCacheCreation": 0.00350625, + "costCached": 0.017991800000000002, + "costInput": 0.0057565, + "costOutput": 0.013365, + "costUsd": 0.04061955, + "key": "1h", + "models": [ + { + "cacheCreation": 450, + "cached": 21105, + "costUsd": 0.02349, + "input": 675, + "output": 270, + "tokens": 22500, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 630, + "cached": 29547, + "costUsd": 0.008654625, + "input": 945, + "output": 378, + "tokens": 31500, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 165, + "cached": 7738, + "costUsd": 0.005169149999999999, + "input": 248, + "output": 99, + "tokens": 8250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 195, + "cached": 9145, + "costUsd": 0.0026793750000000003, + "input": 293, + "output": 117, + "tokens": 9750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 60, + "cached": 2814, + "costUsd": 0.0006264, + "input": 90, + "output": 36, + "tokens": 3000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 378240, + "totalCacheCreation": 1500, + "totalCached": 70349, + "totalInput": 2251, + "totalOutput": 900, + "totalTokens": 75000 + }, + { + "bucketMs": 150000, + "buckets": [ + 10154, + 8237, + 17197, + 12043, + 7356, + 10291, + 976, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3889, + 8344, + 8656, + 10781, + 17211, + 14527, + 3423, + 6282, + 6544, + 4782, + 2537, + 3849, + 0, + 0, + 0, + 0, + 9922, + 395, + 3223, + 8652, + 13994, + 5426, + 6407, + 5352, + 4795, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3910, + 0, + 0, + 2176, + 0, + 0, + 134, + 0, + 0, + 0, + 0, + 0, + 0, + 956, + 0, + 8192, + 12083, + 6692, + 12369, + 18217, + 18304, + 18619, + 5955, + 11872, + 10147, + 8118, + 590, + 0, + 572, + 0, + 0, + 0, + 0, + 4222, + 6774, + 2362, + 0, + 318, + 7050, + 126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [ + { + "cacheCreation": 3375, + "cached": 158288, + "costUsd": 0.14642285000000002, + "input": 5063, + "output": 2025, + "tokens": 168751, + "provider": "anthropic" + }, + { + "cacheCreation": 4125, + "cached": 193463, + "costUsd": 0.056667875, + "input": 6188, + "output": 2475, + "tokens": 206251, + "provider": "openai" + } + ], + "costCacheCreation": 0.017531249999999998, + "costCached": 0.089960475, + "costInput": 0.028773999999999997, + "costOutput": 0.066825, + "costUsd": 0.203090725, + "key": "5h", + "models": [ + { + "cacheCreation": 2250, + "cached": 105526, + "costUsd": 0.11745050000000001, + "input": 3375, + "output": 1350, + "tokens": 112501, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 3150, + "cached": 147736, + "costUsd": 0.04327325, + "input": 4725, + "output": 1890, + "tokens": 157501, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.025840349999999998, + "input": 1238, + "output": 495, + "tokens": 41250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 975, + "cached": 45727, + "costUsd": 0.013394625, + "input": 1463, + "output": 585, + "tokens": 48750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 300, + "cached": 14070, + "costUsd": 0.003132, + "input": 450, + "output": 180, + "tokens": 15000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 446856, + "totalCacheCreation": 7500, + "totalCached": 351751, + "totalInput": 11251, + "totalOutput": 4500, + "totalTokens": 375002 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 15726, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8928, + 22662, + 64437, + 26831, + 68689, + 5370, + 32833, + 37839, + 0, + 0, + 0, + 0, + 0, + 35297, + 0, + 27974, + 72985, + 83388, + 34811, + 80541, + 96823, + 36471, + 72476, + 54595, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6433, + 0, + 0, + 4462, + 0, + 3576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4860, + 29111, + 31310, + 61187, + 23074, + 82189, + 92288, + 81256, + 47364, + 44138, + 0, + 0, + 31657, + 0, + 23638, + 0, + 13934, + 34470, + 0, + 1466, + 73832, + 65087, + 44798, + 14534, + 38999, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1114, + 0, + 0, + 26539, + 31221, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8788, + 0 + ], + "byProvider": [ + { + "cacheCreation": 16200, + "cached": 759780, + "costUsd": 0.7028207999999999, + "input": 24300, + "output": 9720, + "tokens": 810000, + "provider": "anthropic" + }, + { + "cacheCreation": 19800, + "cached": 928620, + "costUsd": 0.2720025, + "input": 29700, + "output": 11880, + "tokens": 990000, + "provider": "openai" + } + ], + "costCacheCreation": 0.08415, + "costCached": 0.43180830000000003, + "costInput": 0.13810499999999998, + "costOutput": 0.32075999999999993, + "costUsd": 0.9748233, + "key": "24h", + "models": [ + { + "cacheCreation": 10800, + "cached": 506520, + "costUsd": 0.5637599999999999, + "input": 16200, + "output": 6480, + "tokens": 540000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 15120, + "cached": 709128, + "costUsd": 0.20771099999999998, + "input": 22680, + "output": 9072, + "tokens": 756000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 3960, + "cached": 185724, + "costUsd": 0.1240272, + "input": 5940, + "output": 2376, + "tokens": 198000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 4680, + "cached": 219492, + "costUsd": 0.0642915, + "input": 7020, + "output": 2808, + "tokens": 234000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 1440, + "cached": 67536, + "costUsd": 0.0150336, + "input": 2160, + "output": 864, + "tokens": 72000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 484115, + "totalCacheCreation": 36000, + "totalCached": 1688400, + "totalInput": 54000, + "totalOutput": 21600, + "totalTokens": 1800000 + }, + { + "bucketMs": 5040000, + "buckets": [ + 73985, + 0, + 0, + 0, + 0, + 0, + 8016, + 0, + 0, + 36289, + 404583, + 352237, + 228216, + 472103, + 339863, + 438460, + 500493, + 423567, + 211937, + 172297, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 146476, + 0, + 236137, + 25838, + 111814, + 120229, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96724, + 0, + 130376, + 225130, + 57578, + 191220, + 81770, + 240354, + 76270, + 0, + 84076, + 102748, + 0, + 0, + 0, + 230628, + 315003, + 315437, + 233622, + 621944, + 260240, + 574907, + 304894, + 337022, + 167548, + 0, + 23086, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 172139, + 97232, + 340168, + 142031, + 588699, + 576239, + 496389, + 214029, + 62760, + 41073, + 88646, + 0, + 0, + 134304, + 128527, + 0, + 0, + 10343, + 236681, + 297625 + ], + "byProvider": [ + { + "cacheCreation": 113400, + "cached": 5318461, + "costUsd": 4.9197461, + "input": 170100, + "output": 68040, + "tokens": 5670001, + "provider": "anthropic" + }, + { + "cacheCreation": 138600, + "cached": 6500341, + "costUsd": 1.9040176250000003, + "input": 207900, + "output": 83160, + "tokens": 6930001, + "provider": "openai" + } + ], + "costCacheCreation": 0.5890500000000001, + "costCached": 3.022658725, + "costInput": 0.966735, + "costOutput": 2.24532, + "costUsd": 6.823763725, + "key": "7d", + "models": [ + { + "cacheCreation": 75600, + "cached": 3545641, + "costUsd": 3.9463204999999997, + "input": 113400, + "output": 45360, + "tokens": 3780001, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 105840, + "cached": 4963897, + "costUsd": 1.4539771250000002, + "input": 158760, + "output": 63504, + "tokens": 5292001, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 27720, + "cached": 1300068, + "costUsd": 0.8681904, + "input": 41580, + "output": 16632, + "tokens": 1386000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 32760, + "cached": 1536444, + "costUsd": 0.4500405, + "input": 49140, + "output": 19656, + "tokens": 1638000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 10080, + "cached": 472752, + "costUsd": 0.1052352, + "input": 15120, + "output": 6048, + "tokens": 504000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 444246, + "totalCacheCreation": 252000, + "totalCached": 11818802, + "totalInput": 378000, + "totalOutput": 151200, + "totalTokens": 12600002 + }, + { + "bucketMs": 22320000, + "buckets": [ + 177797, + 0, + 0, + 0, + 515858, + 248746, + 0, + 1474057, + 1966955, + 928023, + 649765, + 419884, + 1522641, + 1053001, + 30989, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 623353, + 0, + 901883, + 142466, + 315702, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 423922, + 534756, + 274375, + 1369660, + 1782795, + 2968815, + 3505018, + 2491644, + 2291638, + 2259080, + 702721, + 863431, + 0, + 547890, + 113403, + 0, + 0, + 0, + 0, + 0, + 0, + 162720, + 1205015, + 1153225, + 92098, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 411002, + 478378, + 740570, + 1813935, + 820713, + 70438, + 0, + 0, + 1609, + 0, + 417091, + 228279, + 179925, + 25937, + 2045794, + 341547, + 1340717, + 2959388, + 1436920, + 1700385, + 2791968, + 1374769, + 1062954, + 736326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 499463, + 608561 + ], + "byProvider": [ + { + "cacheCreation": 502200, + "cached": 23553178, + "costUsd": 21.787444, + "input": 753300, + "output": 301320, + "tokens": 25109998, + "provider": "anthropic" + }, + { + "cacheCreation": 613800, + "cached": 28787217, + "costUsd": 8.432077125, + "input": 920700, + "output": 368280, + "tokens": 30689997, + "provider": "openai" + } + ], + "costCacheCreation": 2.60865, + "costCached": 13.386056125, + "costInput": 4.281255, + "costOutput": 9.94356, + "costUsd": 30.219521125, + "key": "31d", + "models": [ + { + "cacheCreation": 334800, + "cached": 15702119, + "costUsd": 17.4765595, + "input": 502200, + "output": 200880, + "tokens": 16739999, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 468720, + "cached": 21982966, + "costUsd": 6.43904075, + "input": 703080, + "output": 281232, + "tokens": 23435998, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 122760, + "cached": 5757443, + "costUsd": 3.8448428999999997, + "input": 184140, + "output": 73656, + "tokens": 6137999, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 145080, + "cached": 6804251, + "costUsd": 1.993036375, + "input": 217620, + "output": 87048, + "tokens": 7253999, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 44640, + "cached": 2093616, + "costUsd": 0.4660416, + "input": 66960, + "output": 26784, + "tokens": 2232000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 565325, + "totalCacheCreation": 1116000, + "totalCached": 52340395, + "totalInput": 1674000, + "totalOutput": 669600, + "totalTokens": 55799995 + } + ] + } +} \ No newline at end of file diff --git a/gui/tests/fixtures/rotated.json b/gui/tests/fixtures/rotated.json new file mode 100644 index 0000000..34bead6 --- /dev/null +++ b/gui/tests/fixtures/rotated.json @@ -0,0 +1,1280 @@ +{ + "snapshot": { + "accounts": [ + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "9f4ae815", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000002", + "identity": "ship@rubriclabs.com", + "label": "ship@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_2", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:9f4ae815" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "b31c07d2", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000001", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_1", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:b31c07d2" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "c8d2f6a1", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000003", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/3", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "4e7b93c5", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000004", + "identity": "research@rubriclabs.com", + "label": "research@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_5x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/4", + "provider": "anthropic", + "secretReference": null + } + ], + "providers": [ + { + "activeAccountId": "00000000-0000-4000-8000-000000000002", + "generation": 8, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "openai", + "thresholdPercent": 90 + }, + "provider": "openai", + "switchedAt": "2026-07-15T16:40:00.000Z" + }, + { + "activeAccountId": "00000000-0000-4000-8000-000000000003", + "generation": 3, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": "2026-07-15T09:42:00.000Z" + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [ + { + "accountId": "00000000-0000-4000-8000-000000000002", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T20:36:00.000Z", + "usedPercent": 6 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T15:15:36.000Z", + "usedPercent": 21 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000001", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T16:57:00.000Z", + "usedPercent": 98 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-17T15:44:24.000Z", + "usedPercent": 55 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": { + "applicable": 1, + "available": 1 + }, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000003", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T19:12:00.000Z", + "usedPercent": 21 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T11:54:00.000Z", + "usedPercent": 27 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-18T20:18:00.000Z", + "usedPercent": 27 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000004", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T20:42:00.000Z", + "usedPercent": 4 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T18:08:24.000Z", + "usedPercent": 10 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-21T00:22:48.000Z", + "usedPercent": 4 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + } + ] + }, + "tokens": { + "nowPerHour": 226338, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 1499, + 0, + 1819, + 1719, + 2106, + 2525, + 1078, + 1130, + 1216, + 1863, + 1014, + 0, + 923, + 417, + 0, + 1132, + 215, + 1200, + 755, + 2229, + 1860, + 2121, + 1260, + 88, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 225, + 36, + 1020, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 187, + 201, + 545, + 2493, + 2051, + 1841, + 2311, + 1977, + 1763, + 1135, + 2517, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 812, + 0, + 803, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 971, + 1431, + 1508, + 1943, + 0, + 0, + 0, + 0, + 454, + 0, + 0, + 0, + 760, + 0, + 997, + 1988, + 3152, + 1649, + 3068, + 1561, + 1359, + 2219, + 1545, + 882, + 1313, + 0, + 106 + ], + "byProvider": [ + { + "cacheCreation": 675, + "cached": 31657, + "costUsd": 0.02928555, + "input": 1013, + "output": 405, + "tokens": 33750, + "provider": "anthropic" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.011334, + "input": 1238, + "output": 495, + "tokens": 41250, + "provider": "openai" + } + ], + "costCacheCreation": 0.00350625, + "costCached": 0.017991800000000002, + "costInput": 0.0057565, + "costOutput": 0.013365, + "costUsd": 0.04061955, + "key": "1h", + "models": [ + { + "cacheCreation": 450, + "cached": 21105, + "costUsd": 0.02349, + "input": 675, + "output": 270, + "tokens": 22500, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 630, + "cached": 29547, + "costUsd": 0.008654625, + "input": 945, + "output": 378, + "tokens": 31500, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 165, + "cached": 7738, + "costUsd": 0.005169149999999999, + "input": 248, + "output": 99, + "tokens": 8250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 195, + "cached": 9145, + "costUsd": 0.0026793750000000003, + "input": 293, + "output": 117, + "tokens": 9750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 60, + "cached": 2814, + "costUsd": 0.0006264, + "input": 90, + "output": 36, + "tokens": 3000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 378240, + "totalCacheCreation": 1500, + "totalCached": 70349, + "totalInput": 2251, + "totalOutput": 900, + "totalTokens": 75000 + }, + { + "bucketMs": 150000, + "buckets": [ + 10154, + 8237, + 17197, + 12043, + 7356, + 10291, + 976, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3889, + 8344, + 8656, + 10781, + 17211, + 14527, + 3423, + 6282, + 6544, + 4782, + 2537, + 3849, + 0, + 0, + 0, + 0, + 9922, + 395, + 3223, + 8652, + 13994, + 5426, + 6407, + 5352, + 4795, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3910, + 0, + 0, + 2176, + 0, + 0, + 134, + 0, + 0, + 0, + 0, + 0, + 0, + 956, + 0, + 8192, + 12083, + 6692, + 12369, + 18217, + 18304, + 18619, + 5955, + 11872, + 10147, + 8118, + 590, + 0, + 572, + 0, + 0, + 0, + 0, + 4222, + 6774, + 2362, + 0, + 318, + 7050, + 126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [ + { + "cacheCreation": 3375, + "cached": 158288, + "costUsd": 0.14642285000000002, + "input": 5063, + "output": 2025, + "tokens": 168751, + "provider": "anthropic" + }, + { + "cacheCreation": 4125, + "cached": 193463, + "costUsd": 0.056667875, + "input": 6188, + "output": 2475, + "tokens": 206251, + "provider": "openai" + } + ], + "costCacheCreation": 0.017531249999999998, + "costCached": 0.089960475, + "costInput": 0.028773999999999997, + "costOutput": 0.066825, + "costUsd": 0.203090725, + "key": "5h", + "models": [ + { + "cacheCreation": 2250, + "cached": 105526, + "costUsd": 0.11745050000000001, + "input": 3375, + "output": 1350, + "tokens": 112501, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 3150, + "cached": 147736, + "costUsd": 0.04327325, + "input": 4725, + "output": 1890, + "tokens": 157501, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.025840349999999998, + "input": 1238, + "output": 495, + "tokens": 41250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 975, + "cached": 45727, + "costUsd": 0.013394625, + "input": 1463, + "output": 585, + "tokens": 48750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 300, + "cached": 14070, + "costUsd": 0.003132, + "input": 450, + "output": 180, + "tokens": 15000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 446856, + "totalCacheCreation": 7500, + "totalCached": 351751, + "totalInput": 11251, + "totalOutput": 4500, + "totalTokens": 375002 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 15726, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8928, + 22662, + 64437, + 26831, + 68689, + 5370, + 32833, + 37839, + 0, + 0, + 0, + 0, + 0, + 35297, + 0, + 27974, + 72985, + 83388, + 34811, + 80541, + 96823, + 36471, + 72476, + 54595, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6433, + 0, + 0, + 4462, + 0, + 3576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4860, + 29111, + 31310, + 61187, + 23074, + 82189, + 92288, + 81256, + 47364, + 44138, + 0, + 0, + 31657, + 0, + 23638, + 0, + 13934, + 34470, + 0, + 1466, + 73832, + 65087, + 44798, + 14534, + 38999, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1114, + 0, + 0, + 26539, + 31221, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8788, + 0 + ], + "byProvider": [ + { + "cacheCreation": 16200, + "cached": 759780, + "costUsd": 0.7028207999999999, + "input": 24300, + "output": 9720, + "tokens": 810000, + "provider": "anthropic" + }, + { + "cacheCreation": 19800, + "cached": 928620, + "costUsd": 0.2720025, + "input": 29700, + "output": 11880, + "tokens": 990000, + "provider": "openai" + } + ], + "costCacheCreation": 0.08415, + "costCached": 0.43180830000000003, + "costInput": 0.13810499999999998, + "costOutput": 0.32075999999999993, + "costUsd": 0.9748233, + "key": "24h", + "models": [ + { + "cacheCreation": 10800, + "cached": 506520, + "costUsd": 0.5637599999999999, + "input": 16200, + "output": 6480, + "tokens": 540000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 15120, + "cached": 709128, + "costUsd": 0.20771099999999998, + "input": 22680, + "output": 9072, + "tokens": 756000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 3960, + "cached": 185724, + "costUsd": 0.1240272, + "input": 5940, + "output": 2376, + "tokens": 198000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 4680, + "cached": 219492, + "costUsd": 0.0642915, + "input": 7020, + "output": 2808, + "tokens": 234000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 1440, + "cached": 67536, + "costUsd": 0.0150336, + "input": 2160, + "output": 864, + "tokens": 72000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 484115, + "totalCacheCreation": 36000, + "totalCached": 1688400, + "totalInput": 54000, + "totalOutput": 21600, + "totalTokens": 1800000 + }, + { + "bucketMs": 5040000, + "buckets": [ + 73985, + 0, + 0, + 0, + 0, + 0, + 8016, + 0, + 0, + 36289, + 404583, + 352237, + 228216, + 472103, + 339863, + 438460, + 500493, + 423567, + 211937, + 172297, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 146476, + 0, + 236137, + 25838, + 111814, + 120229, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96724, + 0, + 130376, + 225130, + 57578, + 191220, + 81770, + 240354, + 76270, + 0, + 84076, + 102748, + 0, + 0, + 0, + 230628, + 315003, + 315437, + 233622, + 621944, + 260240, + 574907, + 304894, + 337022, + 167548, + 0, + 23086, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 172139, + 97232, + 340168, + 142031, + 588699, + 576239, + 496389, + 214029, + 62760, + 41073, + 88646, + 0, + 0, + 134304, + 128527, + 0, + 0, + 10343, + 236681, + 297625 + ], + "byProvider": [ + { + "cacheCreation": 113400, + "cached": 5318461, + "costUsd": 4.9197461, + "input": 170100, + "output": 68040, + "tokens": 5670001, + "provider": "anthropic" + }, + { + "cacheCreation": 138600, + "cached": 6500341, + "costUsd": 1.9040176250000003, + "input": 207900, + "output": 83160, + "tokens": 6930001, + "provider": "openai" + } + ], + "costCacheCreation": 0.5890500000000001, + "costCached": 3.022658725, + "costInput": 0.966735, + "costOutput": 2.24532, + "costUsd": 6.823763725, + "key": "7d", + "models": [ + { + "cacheCreation": 75600, + "cached": 3545641, + "costUsd": 3.9463204999999997, + "input": 113400, + "output": 45360, + "tokens": 3780001, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 105840, + "cached": 4963897, + "costUsd": 1.4539771250000002, + "input": 158760, + "output": 63504, + "tokens": 5292001, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 27720, + "cached": 1300068, + "costUsd": 0.8681904, + "input": 41580, + "output": 16632, + "tokens": 1386000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 32760, + "cached": 1536444, + "costUsd": 0.4500405, + "input": 49140, + "output": 19656, + "tokens": 1638000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 10080, + "cached": 472752, + "costUsd": 0.1052352, + "input": 15120, + "output": 6048, + "tokens": 504000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 444246, + "totalCacheCreation": 252000, + "totalCached": 11818802, + "totalInput": 378000, + "totalOutput": 151200, + "totalTokens": 12600002 + }, + { + "bucketMs": 22320000, + "buckets": [ + 177797, + 0, + 0, + 0, + 515858, + 248746, + 0, + 1474057, + 1966955, + 928023, + 649765, + 419884, + 1522641, + 1053001, + 30989, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 623353, + 0, + 901883, + 142466, + 315702, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 423922, + 534756, + 274375, + 1369660, + 1782795, + 2968815, + 3505018, + 2491644, + 2291638, + 2259080, + 702721, + 863431, + 0, + 547890, + 113403, + 0, + 0, + 0, + 0, + 0, + 0, + 162720, + 1205015, + 1153225, + 92098, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 411002, + 478378, + 740570, + 1813935, + 820713, + 70438, + 0, + 0, + 1609, + 0, + 417091, + 228279, + 179925, + 25937, + 2045794, + 341547, + 1340717, + 2959388, + 1436920, + 1700385, + 2791968, + 1374769, + 1062954, + 736326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 499463, + 608561 + ], + "byProvider": [ + { + "cacheCreation": 502200, + "cached": 23553178, + "costUsd": 21.787444, + "input": 753300, + "output": 301320, + "tokens": 25109998, + "provider": "anthropic" + }, + { + "cacheCreation": 613800, + "cached": 28787217, + "costUsd": 8.432077125, + "input": 920700, + "output": 368280, + "tokens": 30689997, + "provider": "openai" + } + ], + "costCacheCreation": 2.60865, + "costCached": 13.386056125, + "costInput": 4.281255, + "costOutput": 9.94356, + "costUsd": 30.219521125, + "key": "31d", + "models": [ + { + "cacheCreation": 334800, + "cached": 15702119, + "costUsd": 17.4765595, + "input": 502200, + "output": 200880, + "tokens": 16739999, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 468720, + "cached": 21982966, + "costUsd": 6.43904075, + "input": 703080, + "output": 281232, + "tokens": 23435998, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 122760, + "cached": 5757443, + "costUsd": 3.8448428999999997, + "input": 184140, + "output": 73656, + "tokens": 6137999, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 145080, + "cached": 6804251, + "costUsd": 1.993036375, + "input": 217620, + "output": 87048, + "tokens": 7253999, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 44640, + "cached": 2093616, + "costUsd": 0.4660416, + "input": 66960, + "output": 26784, + "tokens": 2232000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 565325, + "totalCacheCreation": 1116000, + "totalCached": 52340395, + "totalInput": 1674000, + "totalOutput": 669600, + "totalTokens": 55799995 + } + ] + } +} \ No newline at end of file diff --git a/gui/tests/fixtures/tuned.json b/gui/tests/fixtures/tuned.json new file mode 100644 index 0000000..55bad06 --- /dev/null +++ b/gui/tests/fixtures/tuned.json @@ -0,0 +1,1321 @@ +{ + "snapshot": { + "accounts": [ + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "b31c07d2", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000001", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_1", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:b31c07d2" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "9f4ae815", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000002", + "identity": "ship@rubriclabs.com", + "label": "ship@rubriclabs.com", + "onThreshold": "switch", + "plan": "pro", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": "user_2", + "profilePath": null, + "provider": "openai", + "secretReference": "codex:9f4ae815" + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "c8d2f6a1", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000003", + "identity": "dexter@rubriclabs.com", + "label": "dexter@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_20x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/3", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "oauth", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "4e7b93c5", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000004", + "identity": "research@rubriclabs.com", + "label": "research@rubriclabs.com", + "onThreshold": "switch", + "plan": "claude_max_5x", + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/4", + "provider": "anthropic", + "secretReference": null + }, + { + "auth": "apiKey", + "createdAt": "2026-06-11T16:42:00.000Z", + "enabled": true, + "externalAccountId": "a25d18f4", + "health": "ready", + "id": "00000000-0000-4000-8000-000000000005", + "identity": "anthropic api · prod", + "label": "anthropic api · prod", + "onThreshold": "switch", + "plan": null, + "updatedAt": "2026-07-15T16:40:00.000Z", + "externalUserId": null, + "profilePath": "/tmp/tokenmaxx/claude/5", + "provider": "anthropic", + "secretReference": null + } + ], + "providers": [ + { + "activeAccountId": "00000000-0000-4000-8000-000000000001", + "generation": 4, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 480000, + "provider": "openai", + "thresholdPercent": 85 + }, + "provider": "openai", + "switchedAt": "2026-07-15T15:06:00.000Z" + }, + { + "activeAccountId": "00000000-0000-4000-8000-000000000003", + "generation": 2, + "policy": { + "authorization": "confirmed", + "enabled": true, + "hiddenWindowIds": [], + "hysteresisPercent": 5, + "maximumSnapshotAgeMilliseconds": 420000, + "minimumDwellMilliseconds": 300000, + "provider": "anthropic", + "thresholdPercent": 90 + }, + "provider": "anthropic", + "switchedAt": "2026-07-15T13:12:00.000Z" + } + ], + "sampledAt": "2026-07-15T16:41:48.000Z", + "usage": [ + { + "accountId": "00000000-0000-4000-8000-000000000001", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T18:36:00.000Z", + "usedPercent": 41 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T06:51:36.000Z", + "usedPercent": 29 + } + ], + "extraUsage": { + "balanceUsd": 18.5, + "enabled": true, + "exhausted": false, + "limitUsd": null, + "spentUsd": null, + "usedPercent": null + }, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": { + "applicable": 0, + "available": 3 + }, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000002", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5 hour", + "resetAt": "2026-07-15T20:12:00.000Z", + "usedPercent": 8 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-18T10:13:12.000Z", + "usedPercent": 19 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "openai", + "resetCredits": null, + "source": "codexUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000003", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T18:57:00.000Z", + "usedPercent": 25 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T04:42:00.000Z", + "usedPercent": 14 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-19T14:46:48.000Z", + "usedPercent": 15 + } + ], + "extraUsage": { + "balanceUsd": null, + "enabled": true, + "exhausted": false, + "limitUsd": 50, + "spentUsd": 12.4, + "usedPercent": 25 + }, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000004", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [ + { + "id": "five-hour", + "kind": "hard", + "label": "5h session", + "resetAt": "2026-07-15T20:30:00.000Z", + "usedPercent": 5 + }, + { + "id": "weekly", + "kind": "hard", + "label": "7 day · all models", + "resetAt": "2026-07-19T21:30:00.000Z", + "usedPercent": 9 + }, + { + "id": "weekly_scoped:fable", + "kind": "hard", + "label": "7 day · Fable", + "resetAt": "2026-07-21T07:06:00.000Z", + "usedPercent": 3 + } + ], + "extraUsage": null, + "measuredSpendUsd": null, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + }, + { + "accountId": "00000000-0000-4000-8000-000000000005", + "hardLimitReached": false, + "observedAt": "2026-07-15T16:41:49.402Z", + "windows": [], + "extraUsage": null, + "measuredSpendUsd": 23.71, + "provider": "anthropic", + "source": "claudeUsageEndpoint" + } + ] + }, + "tokens": { + "nowPerHour": 226338, + "timeframes": [ + { + "bucketMs": 30000, + "buckets": [ + 0, + 1499, + 0, + 1819, + 1719, + 2106, + 2525, + 1078, + 1130, + 1216, + 1863, + 1014, + 0, + 923, + 417, + 0, + 1132, + 215, + 1200, + 755, + 2229, + 1860, + 2121, + 1260, + 88, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 225, + 36, + 1020, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 187, + 201, + 545, + 2493, + 2051, + 1841, + 2311, + 1977, + 1763, + 1135, + 2517, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 812, + 0, + 803, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 971, + 1431, + 1508, + 1943, + 0, + 0, + 0, + 0, + 454, + 0, + 0, + 0, + 760, + 0, + 997, + 1988, + 3152, + 1649, + 3068, + 1561, + 1359, + 2219, + 1545, + 882, + 1313, + 0, + 106 + ], + "byProvider": [ + { + "cacheCreation": 675, + "cached": 31657, + "costUsd": 0.02928555, + "input": 1013, + "output": 405, + "tokens": 33750, + "provider": "anthropic" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.011334, + "input": 1238, + "output": 495, + "tokens": 41250, + "provider": "openai" + } + ], + "costCacheCreation": 0.00350625, + "costCached": 0.017991800000000002, + "costInput": 0.0057565, + "costOutput": 0.013365, + "costUsd": 0.04061955, + "key": "1h", + "models": [ + { + "cacheCreation": 450, + "cached": 21105, + "costUsd": 0.02349, + "input": 675, + "output": 270, + "tokens": 22500, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 630, + "cached": 29547, + "costUsd": 0.008654625, + "input": 945, + "output": 378, + "tokens": 31500, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 165, + "cached": 7738, + "costUsd": 0.005169149999999999, + "input": 248, + "output": 99, + "tokens": 8250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 195, + "cached": 9145, + "costUsd": 0.0026793750000000003, + "input": 293, + "output": 117, + "tokens": 9750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 60, + "cached": 2814, + "costUsd": 0.0006264, + "input": 90, + "output": 36, + "tokens": 3000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 378240, + "totalCacheCreation": 1500, + "totalCached": 70349, + "totalInput": 2251, + "totalOutput": 900, + "totalTokens": 75000 + }, + { + "bucketMs": 150000, + "buckets": [ + 10154, + 8237, + 17197, + 12043, + 7356, + 10291, + 976, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3889, + 8344, + 8656, + 10781, + 17211, + 14527, + 3423, + 6282, + 6544, + 4782, + 2537, + 3849, + 0, + 0, + 0, + 0, + 9922, + 395, + 3223, + 8652, + 13994, + 5426, + 6407, + 5352, + 4795, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3910, + 0, + 0, + 2176, + 0, + 0, + 134, + 0, + 0, + 0, + 0, + 0, + 0, + 956, + 0, + 8192, + 12083, + 6692, + 12369, + 18217, + 18304, + 18619, + 5955, + 11872, + 10147, + 8118, + 590, + 0, + 572, + 0, + 0, + 0, + 0, + 4222, + 6774, + 2362, + 0, + 318, + 7050, + 126, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "byProvider": [ + { + "cacheCreation": 3375, + "cached": 158288, + "costUsd": 0.14642285000000002, + "input": 5063, + "output": 2025, + "tokens": 168751, + "provider": "anthropic" + }, + { + "cacheCreation": 4125, + "cached": 193463, + "costUsd": 0.056667875, + "input": 6188, + "output": 2475, + "tokens": 206251, + "provider": "openai" + } + ], + "costCacheCreation": 0.017531249999999998, + "costCached": 0.089960475, + "costInput": 0.028773999999999997, + "costOutput": 0.066825, + "costUsd": 0.203090725, + "key": "5h", + "models": [ + { + "cacheCreation": 2250, + "cached": 105526, + "costUsd": 0.11745050000000001, + "input": 3375, + "output": 1350, + "tokens": 112501, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 3150, + "cached": 147736, + "costUsd": 0.04327325, + "input": 4725, + "output": 1890, + "tokens": 157501, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 825, + "cached": 38692, + "costUsd": 0.025840349999999998, + "input": 1238, + "output": 495, + "tokens": 41250, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 975, + "cached": 45727, + "costUsd": 0.013394625, + "input": 1463, + "output": 585, + "tokens": 48750, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 300, + "cached": 14070, + "costUsd": 0.003132, + "input": 450, + "output": 180, + "tokens": 15000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 446856, + "totalCacheCreation": 7500, + "totalCached": 351751, + "totalInput": 11251, + "totalOutput": 4500, + "totalTokens": 375002 + }, + { + "bucketMs": 720000, + "buckets": [ + 0, + 15726, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8928, + 22662, + 64437, + 26831, + 68689, + 5370, + 32833, + 37839, + 0, + 0, + 0, + 0, + 0, + 35297, + 0, + 27974, + 72985, + 83388, + 34811, + 80541, + 96823, + 36471, + 72476, + 54595, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 6433, + 0, + 0, + 4462, + 0, + 3576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4860, + 29111, + 31310, + 61187, + 23074, + 82189, + 92288, + 81256, + 47364, + 44138, + 0, + 0, + 31657, + 0, + 23638, + 0, + 13934, + 34470, + 0, + 1466, + 73832, + 65087, + 44798, + 14534, + 38999, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1114, + 0, + 0, + 26539, + 31221, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8788, + 0 + ], + "byProvider": [ + { + "cacheCreation": 16200, + "cached": 759780, + "costUsd": 0.7028207999999999, + "input": 24300, + "output": 9720, + "tokens": 810000, + "provider": "anthropic" + }, + { + "cacheCreation": 19800, + "cached": 928620, + "costUsd": 0.2720025, + "input": 29700, + "output": 11880, + "tokens": 990000, + "provider": "openai" + } + ], + "costCacheCreation": 0.08415, + "costCached": 0.43180830000000003, + "costInput": 0.13810499999999998, + "costOutput": 0.32075999999999993, + "costUsd": 0.9748233, + "key": "24h", + "models": [ + { + "cacheCreation": 10800, + "cached": 506520, + "costUsd": 0.5637599999999999, + "input": 16200, + "output": 6480, + "tokens": 540000, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 15120, + "cached": 709128, + "costUsd": 0.20771099999999998, + "input": 22680, + "output": 9072, + "tokens": 756000, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 3960, + "cached": 185724, + "costUsd": 0.1240272, + "input": 5940, + "output": 2376, + "tokens": 198000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 4680, + "cached": 219492, + "costUsd": 0.0642915, + "input": 7020, + "output": 2808, + "tokens": 234000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 1440, + "cached": 67536, + "costUsd": 0.0150336, + "input": 2160, + "output": 864, + "tokens": 72000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 484115, + "totalCacheCreation": 36000, + "totalCached": 1688400, + "totalInput": 54000, + "totalOutput": 21600, + "totalTokens": 1800000 + }, + { + "bucketMs": 5040000, + "buckets": [ + 73985, + 0, + 0, + 0, + 0, + 0, + 8016, + 0, + 0, + 36289, + 404583, + 352237, + 228216, + 472103, + 339863, + 438460, + 500493, + 423567, + 211937, + 172297, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 146476, + 0, + 236137, + 25838, + 111814, + 120229, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96724, + 0, + 130376, + 225130, + 57578, + 191220, + 81770, + 240354, + 76270, + 0, + 84076, + 102748, + 0, + 0, + 0, + 230628, + 315003, + 315437, + 233622, + 621944, + 260240, + 574907, + 304894, + 337022, + 167548, + 0, + 23086, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 172139, + 97232, + 340168, + 142031, + 588699, + 576239, + 496389, + 214029, + 62760, + 41073, + 88646, + 0, + 0, + 134304, + 128527, + 0, + 0, + 10343, + 236681, + 297625 + ], + "byProvider": [ + { + "cacheCreation": 113400, + "cached": 5318461, + "costUsd": 4.9197461, + "input": 170100, + "output": 68040, + "tokens": 5670001, + "provider": "anthropic" + }, + { + "cacheCreation": 138600, + "cached": 6500341, + "costUsd": 1.9040176250000003, + "input": 207900, + "output": 83160, + "tokens": 6930001, + "provider": "openai" + } + ], + "costCacheCreation": 0.5890500000000001, + "costCached": 3.022658725, + "costInput": 0.966735, + "costOutput": 2.24532, + "costUsd": 6.823763725, + "key": "7d", + "models": [ + { + "cacheCreation": 75600, + "cached": 3545641, + "costUsd": 3.9463204999999997, + "input": 113400, + "output": 45360, + "tokens": 3780001, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 105840, + "cached": 4963897, + "costUsd": 1.4539771250000002, + "input": 158760, + "output": 63504, + "tokens": 5292001, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 27720, + "cached": 1300068, + "costUsd": 0.8681904, + "input": 41580, + "output": 16632, + "tokens": 1386000, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 32760, + "cached": 1536444, + "costUsd": 0.4500405, + "input": 49140, + "output": 19656, + "tokens": 1638000, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 10080, + "cached": 472752, + "costUsd": 0.1052352, + "input": 15120, + "output": 6048, + "tokens": 504000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 444246, + "totalCacheCreation": 252000, + "totalCached": 11818802, + "totalInput": 378000, + "totalOutput": 151200, + "totalTokens": 12600002 + }, + { + "bucketMs": 22320000, + "buckets": [ + 177797, + 0, + 0, + 0, + 515858, + 248746, + 0, + 1474057, + 1966955, + 928023, + 649765, + 419884, + 1522641, + 1053001, + 30989, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 623353, + 0, + 901883, + 142466, + 315702, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 423922, + 534756, + 274375, + 1369660, + 1782795, + 2968815, + 3505018, + 2491644, + 2291638, + 2259080, + 702721, + 863431, + 0, + 547890, + 113403, + 0, + 0, + 0, + 0, + 0, + 0, + 162720, + 1205015, + 1153225, + 92098, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 411002, + 478378, + 740570, + 1813935, + 820713, + 70438, + 0, + 0, + 1609, + 0, + 417091, + 228279, + 179925, + 25937, + 2045794, + 341547, + 1340717, + 2959388, + 1436920, + 1700385, + 2791968, + 1374769, + 1062954, + 736326, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 499463, + 608561 + ], + "byProvider": [ + { + "cacheCreation": 502200, + "cached": 23553178, + "costUsd": 21.787444, + "input": 753300, + "output": 301320, + "tokens": 25109998, + "provider": "anthropic" + }, + { + "cacheCreation": 613800, + "cached": 28787217, + "costUsd": 8.432077125, + "input": 920700, + "output": 368280, + "tokens": 30689997, + "provider": "openai" + } + ], + "costCacheCreation": 2.60865, + "costCached": 13.386056125, + "costInput": 4.281255, + "costOutput": 9.94356, + "costUsd": 30.219521125, + "key": "31d", + "models": [ + { + "cacheCreation": 334800, + "cached": 15702119, + "costUsd": 17.4765595, + "input": 502200, + "output": 200880, + "tokens": 16739999, + "model": "claude-opus-4-8", + "provider": "anthropic" + }, + { + "cacheCreation": 468720, + "cached": 21982966, + "costUsd": 6.43904075, + "input": 703080, + "output": 281232, + "tokens": 23435998, + "model": "gpt-5.6-sol", + "provider": "openai" + }, + { + "cacheCreation": 122760, + "cached": 5757443, + "costUsd": 3.8448428999999997, + "input": 184140, + "output": 73656, + "tokens": 6137999, + "model": "claude-sonnet-4-6", + "provider": "anthropic" + }, + { + "cacheCreation": 145080, + "cached": 6804251, + "costUsd": 1.993036375, + "input": 217620, + "output": 87048, + "tokens": 7253999, + "model": "gpt-5.6-codex", + "provider": "openai" + }, + { + "cacheCreation": 44640, + "cached": 2093616, + "costUsd": 0.4660416, + "input": 66960, + "output": 26784, + "tokens": 2232000, + "model": "claude-haiku-4-5", + "provider": "anthropic" + } + ], + "peakPerHour": 565325, + "totalCacheCreation": 1116000, + "totalCached": 52340395, + "totalInput": 1674000, + "totalOutput": 669600, + "totalTokens": 55799995 + } + ] + } +} \ No newline at end of file diff --git a/package.json b/package.json index 7ec8ed3..a07aabf 100644 --- a/package.json +++ b/package.json @@ -48,10 +48,13 @@ "scripts": { "bleed": "bun x npm-check-updates -u", "build": "bun run clean && bun build ./src/index.ts --target bun --outdir dist --external '@opentui/*' && cp bin/launcher.cjs dist/launcher.cjs && chmod 755 dist/index.js dist/launcher.cjs", + "build:bin": "bun build --compile ./src/index.ts --outfile gui/target/bin/tokenmaxx", "check": "bun run typecheck && bun x biome check . && bun test", "clean": "rm -rf dist", "dev": "bun run src/index.ts", "format": "bun x biome check --write .", + "gui:bundle": "gui/scripts/bundle.sh", + "gui:dev": "bun run build:bin && TOKENMAXX_BIN=gui/target/bin/tokenmaxx cargo run --manifest-path gui/Cargo.toml", "prepare": "bun x @rubriclab/package prepare", "typecheck": "tsc --noEmit" }, @@ -59,5 +62,5 @@ "post-commit": "bun x @rubriclab/package post-commit" }, "type": "module", - "version": "0.0.66" + "version": "0.0.67" } diff --git a/src/cli.ts b/src/cli.ts index 356b520..f025f96 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,8 +5,8 @@ import { homedir } from 'node:os' import { join } from 'node:path' import { createInterface } from 'node:readline/promises' import { z } from 'zod' -import { registerClaudeAccount, registerClaudeApiKeyAccount } from './claude.ts' -import { registerCodexAccount, registerOpenAiApiKeyAccount } from './codex.ts' +import { registerClaudeAccount } from './claude.ts' +import { registerCodexAccount } from './codex.ts' import { healInstalledConfigs, installClaudeConfig, @@ -26,8 +26,11 @@ import { managerVersion, readDashboard, readProxyPort, + readRouting, requestAccountRemove, requestAccountSave, + requestAddApiKey, + requestRouting, requestSwitch, startManagerServer } from './ipc.ts' @@ -37,7 +40,7 @@ import { proxyIdentity } from './proxy.ts' import { createStateStore, type StateStore } from './storage.ts' import { renderDashboard } from './ui.ts' import { createMacOsKeychainVault } from './vault.ts' -import { availableUpdate, installedVersion, VERSION } from './version.ts' +import { availableUpdate, compiledBinary, installedVersion, VERSION } from './version.ts' const DaemonLockSchema = z .object({ @@ -351,20 +354,28 @@ async function runDaemon(context: ApplicationContext): Promise { } } +function daemonCommandArguments(): string[] { + if (compiledBinary) { + return ['daemon', 'run'] + } + const entrypoint = process.argv[1] + if (entrypoint === undefined) { + throw new ApplicationError('ENTRYPOINT_MISSING', 'Cannot locate the CLI entrypoint') + } + return [entrypoint, 'daemon', 'run'] +} + async function startDaemon(context: ApplicationContext): Promise { if (await managerAvailable(context.paths.managerSocket)) { return } await replacePortOccupant(context.paths.proxyPort) await mkdir(context.paths.runtime, { mode: 0o700, recursive: true }) - const entrypoint = process.argv[1] - if (entrypoint === undefined) { - throw new ApplicationError('ENTRYPOINT_MISSING', 'Cannot locate the CLI entrypoint') - } + const daemonArguments = daemonCommandArguments() const logDescriptor = openSync(join(context.paths.runtime, 'daemon.log'), 'a', 0o600) try { for (let attempt = 0; attempt < 3; attempt += 1) { - const child = spawn(process.execPath, [entrypoint, 'daemon', 'run'], { + const child = spawn(process.execPath, daemonArguments, { detached: true, env: process.env, stdio: ['ignore', logDescriptor, logDescriptor] @@ -543,10 +554,9 @@ export function stripTerminalNoise(line: string): string { return clean.trim() } -async function registerApiKeyAccount( - provider: 'openai' | 'anthropic', +async function promptApiKey( keyArgument: string | undefined -): Promise { +): Promise<{ key: string; label: string }> { if (process.stdin.isTTY !== true && keyArgument === undefined) { throw new ApplicationError( 'USAGE', @@ -555,41 +565,19 @@ async function registerApiKeyAccount( } await handTerminalBack() const readline = createInterface({ input: process.stdin, output: process.stdout }) - let key: string - let label: string try { - key = keyArgument ?? stripTerminalNoise(await readline.question('Paste the API key: ')) - label = stripTerminalNoise( + const key = keyArgument ?? stripTerminalNoise(await readline.question('Paste the API key: ')) + const label = stripTerminalNoise( await readline.question('Name this account (shown in the dashboard): ') ) + return { key, label } } finally { readline.close() } - if (label.trim().length === 0) { - throw new ApplicationError('USAGE', 'The account needs a name') - } - const vault = createMacOsKeychainVault() - return provider === 'openai' - ? registerOpenAiApiKeyAccount({ key, label: label.trim(), vault }) - : registerClaudeApiKeyAccount({ key, label: label.trim(), vault }) } -async function login( - context: ApplicationContext, - providerArgument: string | undefined, - options: { apiKey: boolean; apiKeyValue?: string } = { apiKey: false } -): Promise { - if (providerArgument === undefined) { - throw new ApplicationError('USAGE', 'Usage: tokenmaxx login [--api-key [key]]') - } - const provider = providerFromCli(providerArgument) - if (!options.apiKey) { - assertCliInstalled(provider) - } - await ensureDaemon(context) - const authenticated = options.apiKey - ? await registerApiKeyAccount(provider, options.apiKeyValue) - : await registerIsolatedAccount(provider) +async function signInOauth(context: ApplicationContext, provider: ProviderId): Promise { + const authenticated = await registerIsolatedAccount(provider) const existing = context.store .listAccounts(provider) .find( @@ -612,7 +600,9 @@ async function login( secretReference: existing?.secretReference ?? null }) } catch (error) { - await removeUnstoredAccount(authenticated) + if (authenticated.secretReference !== null) { + await createMacOsKeychainVault().remove(authenticated.secretReference) + } throw error } process.stdout.write( @@ -620,21 +610,38 @@ async function login( ? `Signed in ${account.label}.\n` : `Re-authenticated ${account.label}; live sessions pick it up on their next request.\n` ) - if (existing === undefined) { - const status = await installStatus() - const alreadyRouted = provider === 'openai' ? status.codexRouted : status.claudeRouted - if (!alreadyRouted) { - await setRouting(context, provider, true).catch(() => undefined) - process.stdout.write( - `tokenmaxx is on for ${providerArgument} — run ${providerArgument} as usual.\n` - ) - } - } + return existing === undefined } -async function removeUnstoredAccount(account: Account): Promise { - if (account.secretReference !== null) { - await createMacOsKeychainVault().remove(account.secretReference) +async function login( + context: ApplicationContext, + providerArgument: string | undefined, + options: { apiKey: boolean; apiKeyValue?: string } = { apiKey: false } +): Promise { + if (providerArgument === undefined) { + throw new ApplicationError('USAGE', 'Usage: tokenmaxx login [--api-key [key]]') + } + const provider = providerFromCli(providerArgument) + if (!options.apiKey) { + assertCliInstalled(provider) + } + await ensureDaemon(context) + const socket = context.paths.managerSocket + if (options.apiKey) { + const account = await requestAddApiKey(socket, { + provider, + ...(await promptApiKey(options.apiKeyValue)) + }) + process.stdout.write(`Signed in ${account.label}.\n`) + } else if (!(await signInOauth(context, provider))) { + return + } + const routing = await readRouting(socket) + if (!routing.routed[provider]) { + await requestRouting(socket, provider, true).catch(() => undefined) + process.stdout.write( + `tokenmaxx is on for ${providerArgument} — run ${providerArgument} as usual.\n` + ) } } @@ -840,18 +847,6 @@ async function uninstallConfig(targetArgument?: string): Promise { ) } -async function setRouting( - context: ApplicationContext, - provider: ProviderId, - enable: boolean -): Promise { - if (provider === 'openai') { - await (enable ? installCodexConfig(context.paths) : uninstallCodexConfig()) - } else { - await (enable ? installClaudeConfig(context.paths) : uninstallClaudeConfig()) - } -} - async function doctor(context: ApplicationContext): Promise { const tools = [ ['bun', '1.2+'], @@ -884,7 +879,7 @@ async function doctor(context: ApplicationContext): Promise { process.stdout.write( update === null ? `ok version ${VERSION} (latest)\n` - : `note version ${VERSION} — v${update} is out: bun add -g tokenmaxx\n` + : `note version ${VERSION} — v${update} is out: ${compiledBinary ? 'update the tokenmaxx app' : 'bun add -g tokenmaxx'}\n` ) if (running) { const port = await readProxyPort(context.paths.managerSocket).catch(() => null) @@ -954,9 +949,9 @@ export async function runCli(rawArguments: readonly string[]): Promise { fixture: { name: fixtureName, now, + routed, timewarp: Number.isFinite(timewarp) && timewarp > 0 ? timewarp : 0 - }, - routing: { anthropic: routed, openai: routed } + } }) context.store.close() process.exit(0) @@ -964,16 +959,9 @@ export async function runCli(rawArguments: readonly string[]): Promise { await ensureDaemon(context) if (process.stdout.isTTY) { const { runTuiDashboard } = await import('./tui/dashboard.ts') - const readRouting = async (): Promise> => { - const status = await installStatus() - return { anthropic: status.claudeRouted, openai: status.codexRouted } - } let alert = '' for (;;) { - const action = await runTuiDashboard(context.paths.managerSocket, { - alert, - routing: await readRouting() - }) + const action = await runTuiDashboard(context.paths.managerSocket, { alert }) alert = '' await handTerminalBack() if (action === undefined) { @@ -995,13 +983,13 @@ export async function runCli(rawArguments: readonly string[]): Promise { }) continue } - if (action.kind === 'routing') { - await setRouting(context, action.provider, action.enable).catch(error => { - alert = errorMessage(error) - }) - continue - } if (action.kind === 'update') { + if (compiledBinary) { + process.stdout.write( + `v${action.version} is out — update the tokenmaxx app from https://tokenmaxx.sh\n` + ) + break + } process.stdout.write(`Updating tokenmaxx to v${action.version}…\n`) const bun = Bun.which('bun') ?? 'bun' const result = Bun.spawnSync([bun, 'add', '-g', `tokenmaxx@${action.version}`], { @@ -1022,6 +1010,11 @@ export async function runCli(rawArguments: readonly string[]): Promise { process.stdout.write(`${renderDashboard(await readDashboard(context.paths.managerSocket))}\n`) return 0 } + case 'version': + case '--version': + case '-v': + process.stdout.write(`${VERSION}\n`) + return 0 case 'help': case '--help': case '-h': @@ -1075,7 +1068,7 @@ export async function runCli(rawArguments: readonly string[]): Promise { await runDaemon(context) return 0 case 'start': - await startDaemon(context) + await ensureDaemon(context) process.stdout.write('Manager daemon is running.\n') return 0 case 'stop': diff --git a/src/config-install.test.ts b/src/config-install.test.ts index a7da7c3..317dc28 100644 --- a/src/config-install.test.ts +++ b/src/config-install.test.ts @@ -222,6 +222,21 @@ describe('healInstalledConfigs', () => { expect((await installStatus()).codexRouted).toBe(false) }) + test('leaves configs routed to another instance alone', async () => { + await installCodexConfig(paths()) + await installClaudeConfig(paths()) + const elsewhere = applicationPaths({ + ...process.env, + TOKENMAXX_HOME: join(home, 'elsewhere'), + TOKENMAXX_PROXY_PORT: '18459' + }) + expect(await healInstalledConfigs(elsewhere)).toEqual([]) + expect((await readClaudeSettings()).env?.ANTHROPIC_BASE_URL).toBe( + 'http://127.0.0.1:8459/anthropic' + ) + expect(await readCodexConfig()).toContain('http://127.0.0.1:8459/openai') + }) + test('runs once per version, not on every start', async () => { await healInstalledConfigs(paths()) await writeClaudeSettings({ diff --git a/src/config-install.ts b/src/config-install.ts index fb10149..1ee9e87 100644 --- a/src/config-install.ts +++ b/src/config-install.ts @@ -182,11 +182,14 @@ interface InstallStatus { codexRouted: boolean claudeRouted: boolean codexStale: boolean + codexBaseUrl: string | null + claudeBaseUrl: string | null } export async function installStatus(): Promise { const codexRaw = await readFileOrEmpty(codexConfigPath()) let codexRouted = false + let codexBaseUrl: string | null = null try { const parsed = Bun.TOML.parse(codexRaw) as { model_provider?: unknown @@ -194,7 +197,8 @@ export async function installStatus(): Promise { } const selected = typeof parsed.model_provider === 'string' ? parsed.model_provider : null const baseUrl = selected === null ? undefined : parsed.model_providers?.[selected]?.base_url - codexRouted = typeof baseUrl === 'string' && baseUrl.includes('127.0.0.1') + codexBaseUrl = typeof baseUrl === 'string' ? baseUrl : null + codexRouted = codexBaseUrl?.includes('127.0.0.1') ?? false } catch { codexRouted = false } @@ -203,31 +207,33 @@ export async function installStatus(): Promise { ([...legacyBeginMarkers, tableBeginMarker].some(marker => codexRaw.includes(marker)) || codexRaw.match(bareProviderTable) !== null) - let claudeRouted = false + let claudeBaseUrl: string | null = null try { const settings = JSON.parse(await readFileOrEmpty(claudeSettingsPath())) as ClaudeSettings - claudeRouted = settings.env?.ANTHROPIC_BASE_URL?.includes('127.0.0.1') ?? false + claudeBaseUrl = settings.env?.ANTHROPIC_BASE_URL ?? null } catch { - claudeRouted = false + claudeBaseUrl = null } - return { claudeRouted, codexRouted, codexStale } + const claudeRouted = claudeBaseUrl?.includes('127.0.0.1') ?? false + return { claudeBaseUrl, claudeRouted, codexBaseUrl, codexRouted, codexStale } } // Configs written by an older version stay stale after an update (#17): re-apply // install for whatever is currently routed, once per version change. Never adds -// routing — a harness the user uninstalled or never installed stays untouched. +// routing — a harness the user uninstalled or never installed stays untouched, and a +// config routed to another tokenmaxx instance's port belongs to that instance. export async function healInstalledConfigs(paths: ApplicationPaths): Promise { const stampPath = join(paths.root, 'healed-version') if ((await readFileOrEmpty(stampPath)).trim() === VERSION) { return [] } - const { claudeRouted, codexRouted } = await installStatus() + const { claudeBaseUrl, codexBaseUrl } = await installStatus() const healed: string[] = [] - if (codexRouted) { + if (codexBaseUrl === proxyBaseUrl(paths, 'openai')) { await installCodexConfig(paths) healed.push('codex') } - if (claudeRouted) { + if (claudeBaseUrl === proxyBaseUrl(paths, 'anthropic')) { await installClaudeConfig(paths) healed.push('claude') } diff --git a/src/ipc.test.ts b/src/ipc.test.ts new file mode 100644 index 0000000..03cf1e7 --- /dev/null +++ b/src/ipc.test.ts @@ -0,0 +1,122 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { mkdtempSync, rmSync } from 'node:fs' +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { ApplicationError } from './errors.ts' +import { + readDashboard, + readRouting, + requestAddApiKey, + requestRouting, + startManagerServer +} from './ipc.ts' +import { AccountManager } from './manager.ts' +import { applicationPaths, ensureApplicationPaths } from './paths.ts' +import { createStateStore } from './storage.ts' +import type { CredentialVault } from './vault.ts' + +const secrets = new Map() +const vault: CredentialVault = { + async read(reference) { + return secrets.get(reference) ?? null + }, + async remove(reference) { + secrets.delete(reference) + }, + async write(reference, value) { + secrets.set(reference, value) + } +} + +const acceptingFetch = async () => new Response('{}', { status: 200 }) + +let home = '' +let socketPath = '' +let close: () => Promise = async () => undefined + +beforeEach(async () => { + home = mkdtempSync(join(tmpdir(), 'tokenmaxx-ipc-')) + process.env.CODEX_HOME = join(home, 'codex') + process.env.CLAUDE_CONFIG_DIR = join(home, 'claude') + process.env.PI_CODING_AGENT_DIR = join(home, 'pi') + await mkdir(process.env.CODEX_HOME, { recursive: true }) + await mkdir(process.env.CLAUDE_CONFIG_DIR, { recursive: true }) + const paths = applicationPaths({ ...process.env, TOKENMAXX_HOME: join(home, 'state') }) + await ensureApplicationPaths(paths) + const manager = new AccountManager({ + dependencies: { fetchImplementation: acceptingFetch }, + paths, + store: createStateStore(paths.database), + vault + }) + socketPath = paths.managerSocket + const server = await startManagerServer({ manager, onStop: () => undefined, socketPath }) + close = server.close +}) + +afterEach(async () => { + await close() + delete process.env.CODEX_HOME + delete process.env.CLAUDE_CONFIG_DIR + delete process.env.PI_CODING_AGENT_DIR + secrets.clear() + rmSync(home, { force: true, recursive: true }) +}) + +describe('routing over ipc', () => { + test('reads and toggles codex and claude routing', async () => { + expect((await readRouting(socketPath)).routed).toEqual({ anthropic: false, openai: false }) + + const on = await requestRouting(socketPath, 'anthropic', true) + expect(on.routed.anthropic).toBe(true) + const settings = await readFile(join(home, 'claude', 'settings.json'), 'utf8') + expect(settings).toContain('127.0.0.1') + + const off = await requestRouting(socketPath, 'anthropic', false) + expect(off.routed.anthropic).toBe(false) + }) + + test('routes pi and refuses to touch a models.json it cannot parse', async () => { + await mkdir(join(home, 'pi'), { recursive: true }) + expect((await requestRouting(socketPath, 'pi', true)).pi).toEqual({ + present: true, + routed: true + }) + + await writeFile(join(home, 'pi', 'models.json'), '{ not json') + const failure = await requestRouting(socketPath, 'pi', true).catch(error => error) + expect(failure).toBeInstanceOf(ApplicationError) + expect((failure as ApplicationError).code).toBe('MANUAL_EDIT_REQUIRED') + }) +}) + +describe('api key accounts over ipc', () => { + test('stores the key in the vault and activates the first account', async () => { + const account = await requestAddApiKey(socketPath, { + key: 'sk-ant-test', + label: 'team key', + provider: 'anthropic' + }) + expect(account.auth).toBe('apiKey') + expect(account.secretReference === null ? null : secrets.get(account.secretReference)).toBe( + 'sk-ant-test' + ) + + const dashboard = await readDashboard(socketPath) + expect(dashboard.accounts.map(candidate => candidate.label)).toEqual(['team key']) + expect(dashboard.providers.find(state => state.provider === 'anthropic')?.activeAccountId).toBe( + account.id + ) + }) + + test('rejects an empty name', async () => { + const failure = await requestAddApiKey(socketPath, { + key: 'sk-test', + label: ' ', + provider: 'openai' + }).catch(error => error) + expect((failure as ApplicationError).code).toBe('USAGE') + expect(secrets.size).toBe(0) + }) +}) diff --git a/src/ipc.ts b/src/ipc.ts index 1d84d1b..5dbf4bd 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -19,7 +19,15 @@ import { } from './domain.ts' import { ApplicationError, errorMessage } from './errors.ts' import type { AccountManager } from './manager.ts' -import { VERSION } from './version.ts' +import { + type RoutingStatus, + RoutingStatusSchema, + type RoutingTarget, + RoutingTargetSchema, + routingStatus, + setRouting +} from './routing.ts' +import { availableUpdate, VERSION } from './version.ts' const RpcRequestSchema = z .object({ @@ -62,6 +70,14 @@ const PolicyParamsSchema = z const ResetParamsSchema = z.object({ accountId: z.uuid() }).strict() +const RoutingParamsSchema = z.object({ enable: z.boolean(), target: RoutingTargetSchema }).strict() + +const AddApiKeyParamsSchema = z + .object({ key: z.string().min(1), label: z.string().min(1), provider: ProviderIdSchema }) + .strict() + +const LatestVersionSchema = z.object({ latest: z.string().nullable() }).strict() + const ReplaceCredentialParamsSchema = z .object({ account: AccountSchema, @@ -119,6 +135,19 @@ async function dispatch( return manager.codexResetCredits(ResetParamsSchema.parse(params).accountId) case 'codex/consumeReset': return manager.consumeCodexReset(ResetParamsSchema.parse(params).accountId) + case 'account/addApiKey': { + const account = await manager.addApiKeyAccount(AddApiKeyParamsSchema.parse(params)) + return { account } + } + case 'routing/read': + return routingStatus() + case 'routing/set': { + const parsed = RoutingParamsSchema.parse(params) + await setRouting(manager.paths, parsed.target, parsed.enable) + return routingStatus() + } + case 'version/latest': + return { latest: await availableUpdate() } default: throw new ApplicationError('METHOD_NOT_FOUND', `Unknown manager method ${method}`) } @@ -395,3 +424,48 @@ export function requestAccountSave( timeoutMilliseconds: 15_000 }).then(() => undefined) } + +export function requestAddApiKey( + socketPath: string, + input: { provider: ProviderId; key: string; label: string } +): Promise { + return managerRequest({ + method: 'account/addApiKey', + params: input, + schema: z.object({ account: AccountSchema }).transform(result => result.account), + socketPath, + timeoutMilliseconds: 30_000 + }) +} + +export function readRouting(socketPath: string): Promise { + return managerRequest({ + method: 'routing/read', + schema: RoutingStatusSchema, + socketPath, + timeoutMilliseconds: 15_000 + }) +} + +export function requestRouting( + socketPath: string, + target: RoutingTarget, + enable: boolean +): Promise { + return managerRequest({ + method: 'routing/set', + params: { enable, target }, + schema: RoutingStatusSchema, + socketPath, + timeoutMilliseconds: 15_000 + }) +} + +export function readLatestVersion(socketPath: string): Promise { + return managerRequest({ + method: 'version/latest', + schema: LatestVersionSchema, + socketPath, + timeoutMilliseconds: 5_000 + }).then(result => result.latest) +} diff --git a/src/manager.ts b/src/manager.ts index 3079ce5..1dddd08 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -1,9 +1,16 @@ -import { claudeUpstream, migrateClaudeAccount, probeClaude, removeClaudeProfile } from './claude.ts' +import { + claudeUpstream, + migrateClaudeAccount, + probeClaude, + registerClaudeApiKeyAccount, + removeClaudeProfile +} from './claude.ts' import { codexUpstream, probeCodex, probeCodexResetCredits, - redeemCodexResetCredit + redeemCodexResetCredit, + registerOpenAiApiKeyAccount } from './codex.ts' import { type Account, @@ -165,6 +172,10 @@ export class AccountManager { return this.#proxy?.port ?? null } + public get paths(): ApplicationPaths { + return this.#paths + } + private async upstreamInjection( provider: ProviderId, forceRefresh: boolean @@ -318,6 +329,27 @@ export class AccountManager { }) } + public async addApiKeyAccount(input: { + provider: ProviderId + key: string + label: string + }): Promise { + const label = input.label.trim() + if (label.length === 0) { + throw new ApplicationError('USAGE', 'The account needs a name') + } + const register = + input.provider === 'openai' ? registerOpenAiApiKeyAccount : registerClaudeApiKeyAccount + const account = await register({ + fetchImplementation: this.#dependencies.fetchImplementation, + key: input.key, + label, + vault: this.#vault + }) + await this.saveAccount({ account, removePrevious: { profilePath: null, secretReference: null } }) + return account + } + public setAutomationPolicy(input: { provider: ProviderId enabled?: boolean diff --git a/src/routing.ts b/src/routing.ts new file mode 100644 index 0000000..650aa9f --- /dev/null +++ b/src/routing.ts @@ -0,0 +1,65 @@ +import { z } from 'zod' +import { + installClaudeConfig, + installCodexConfig, + installPiConfig, + installStatus, + piStatus, + uninstallClaudeConfig, + uninstallCodexConfig, + uninstallPiConfig +} from './config-install.ts' +import { ProviderIdSchema } from './domain.ts' +import { ApplicationError } from './errors.ts' +import type { ApplicationPaths } from './paths.ts' + +const ProviderFlagsSchema = z.record(ProviderIdSchema, z.boolean()) + +export const RoutingTargetSchema = z.enum(['openai', 'anthropic', 'pi']) +export type RoutingTarget = z.infer + +export const RoutingStatusSchema = z + .object({ + clis: ProviderFlagsSchema, + codexStale: z.boolean(), + pi: z.object({ present: z.boolean(), routed: z.boolean() }).strict(), + routed: ProviderFlagsSchema + }) + .strict() +export type RoutingStatus = z.infer + +export async function routingStatus( + which: (binary: string) => string | null = Bun.which +): Promise { + const [install, pi] = await Promise.all([installStatus(), piStatus(which)]) + return { + clis: { anthropic: which('claude') !== null, openai: which('codex') !== null }, + codexStale: install.codexStale, + pi, + routed: { anthropic: install.claudeRouted, openai: install.codexRouted } + } +} + +export async function setRouting( + paths: ApplicationPaths, + target: RoutingTarget, + enable: boolean +): Promise { + switch (target) { + case 'openai': + await (enable ? installCodexConfig(paths) : uninstallCodexConfig()) + return + case 'anthropic': + await (enable ? installClaudeConfig(paths) : uninstallClaudeConfig()) + return + case 'pi': { + const result = await (enable ? installPiConfig(paths) : uninstallPiConfig()) + if (result.manual !== null) { + throw new ApplicationError( + 'MANUAL_EDIT_REQUIRED', + `${result.path} needs a manual edit — run: tokenmaxx ${enable ? 'install' : 'uninstall'} pi` + ) + } + } + } +} diff --git a/src/tui/dashboard.ts b/src/tui/dashboard.ts index 61209ad..ba61d53 100644 --- a/src/tui/dashboard.ts +++ b/src/tui/dashboard.ts @@ -1,5 +1,4 @@ import { Box, createCliRenderer, parseColor, type RGBA, Text } from '@opentui/core' -import { installPiConfig, type PiStatus, piStatus, uninstallPiConfig } from '../config-install.ts' import type { Account, AnalyticsSnapshot, @@ -14,15 +13,17 @@ import type { } from '../domain.ts' import { readAnalytics, + readRouting, refreshUsage, requestAccountSave, requestConsumeReset, requestPolicy, requestResetCredits, + requestRouting, requestSwitch } from '../ipc.ts' -import { applicationPaths } from '../paths.ts' import { readPreferences, writePreferences } from '../preferences.ts' +import type { RoutingStatus } from '../routing.ts' import { availableUpdate, installedVersion, VERSION } from '../version.ts' import { buildScenario } from './fixtures.ts' import { @@ -95,7 +96,7 @@ interface Ctx { themePreference: ThemePreference themePinnedByEnvironment: boolean themeFromTerminalActive: boolean - pi: PiStatus + pi: RoutingStatus['pi'] } function labelWidth(ctx: Ctx): number { @@ -1172,7 +1173,6 @@ type DashboardAction = | { kind: 'relogin'; provider: ProviderId } | { kind: 'login'; provider: ProviderId } | { kind: 'loginApiKey'; provider: ProviderId } - | { kind: 'routing'; provider: ProviderId; enable: boolean } | { kind: 'update'; version: string } function view(ctx: Ctx, analytics: AnalyticsSnapshot, rows: Row[], state: ViewState) { @@ -1308,21 +1308,28 @@ function view(ctx: Ctx, analytics: AnalyticsSnapshot, rows: Row[], state: ViewSt interface FixtureOptions { name: string now: number + routed: boolean timewarp: number } export async function runTuiDashboard( socketPath: string, - options: { routing: Record; fixture?: FixtureOptions; alert?: string } + options: { fixture?: FixtureOptions; alert?: string } ): Promise { const fixture = options.fixture const live = fixture === undefined try { process.stdin.setRawMode?.(true) } catch {} - const cliPresent: Record = live - ? { anthropic: Bun.which('claude') !== null, openai: Bun.which('codex') !== null } - : { anthropic: true, openai: true } + let routing: RoutingStatus = + fixture === undefined + ? await readRouting(socketPath) + : { + clis: { anthropic: true, openai: true }, + codexStale: false, + pi: { present: true, routed: true }, + routed: { anthropic: fixture.routed, openai: fixture.routed } + } const renderer = await createCliRenderer({ exitOnCtrlC: false, targetFps: 30 }) await renderer.waitForThemeMode(400).catch(() => null) const themeEnvironmentOverride = themeOverride(process.env) @@ -1346,7 +1353,6 @@ export async function runTuiDashboard( ? await readAnalytics(socketPath) : buildScenario(fixture.name, simulatedNow) let rows = orderedRows(analytics.snapshot) - let pi: PiStatus = live ? await piStatus() : { present: true, routed: true } const state: ViewState = { addConfirm: null, alert: options.alert ?? '', @@ -1383,11 +1389,11 @@ export async function runTuiDashboard( try { next = view( { - cliPresent, + cliPresent: routing.clis, columns, now: live ? Date.now() : simulatedNow, - pi, - routing: options.routing, + pi: routing.pi, + routing: routing.routed, rows: process.stdout.rows ?? 24, switchFlagMs: fixture !== undefined && fixture.timewarp > 0 ? 24 * 60_000 : 120_000, theme: currentTheme(), @@ -1435,7 +1441,7 @@ export async function runTuiDashboard( } analytics = await readAnalytics(socketPath) rows = orderedRows(analytics.snapshot) - pi = await piStatus() + routing = await readRouting(socketPath) clampSelection() }) @@ -1455,7 +1461,7 @@ export async function runTuiDashboard( } if (row.accountId === ADD_ROW) { state.addConfirm = { - choice: cliPresent[row.provider] ? 'oauth' : 'apiKey', + choice: routing.clis[row.provider] ? 'oauth' : 'apiKey', provider: row.provider } paint() @@ -1572,8 +1578,19 @@ export async function runTuiDashboard( applyPolicy(provider, { hiddenWindowIds: next }, 'rate-limit view…') } - const toggleRouting = (provider: ProviderId) => { - finish({ enable: !options.routing[provider], kind: 'routing', provider }) + const toggleTarget = (target: ProviderId | 'pi', routed: boolean) => { + if (!live) { + routing = + target === 'pi' + ? { ...routing, pi: { ...routing.pi, routed: !routed } } + : { ...routing, routed: { ...routing.routed, [target]: !routed } } + paint() + return + } + const name = target === 'pi' ? 'pi' : providerCli[target] + void withBusy(`${routed ? 'unrouting' : 'routing'} ${name}…`, async () => { + routing = await requestRouting(socketPath, target, !routed) + }) } const adjustSetting = (delta: number) => { @@ -1582,27 +1599,12 @@ export async function runTuiDashboard( return } if (row.scope === 'harness') { - if (!pi.present) { + if (!routing.pi.present) { state.note = 'pi is not installed' paint() return } - if (!live) { - pi = { ...pi, routed: !pi.routed } - paint() - return - } - void withBusy(pi.routed ? 'unrouting pi…' : 'routing pi…', async () => { - if (pi.routed) { - await uninstallPiConfig() - } else { - const result = await installPiConfig(applicationPaths()) - if (result.manual !== null) { - throw new Error('models.json needs a manual edit — run: tokenmaxx install pi') - } - } - pi = await piStatus() - }) + toggleTarget('pi', routing.pi.routed) return } if (row.scope === 'display') { @@ -1623,7 +1625,7 @@ export async function runTuiDashboard( } const policy = currentPolicy(row.provider) if (row.key === 'routing') { - toggleRouting(row.provider) + toggleTarget(row.provider, routing.routed[row.provider]) return } if (row.key === 'auto') { @@ -1810,7 +1812,7 @@ export async function runTuiDashboard( const row = rows[state.selected] if (row !== undefined && row.accountId === ADD_ROW) { state.addConfirm = { - choice: cliPresent[row.provider] ? 'oauth' : 'apiKey', + choice: routing.clis[row.provider] ? 'oauth' : 'apiKey', provider: row.provider } paint() diff --git a/src/version.ts b/src/version.ts index b069241..142f901 100644 --- a/src/version.ts +++ b/src/version.ts @@ -4,6 +4,9 @@ import packageJson from '../package.json' export const VERSION: string = packageJson.version +// `bun build --compile` mounts the bundle under /$bunfs/, which is how the app-bundled binary runs. +export const compiledBinary = Bun.main.startsWith('/$bunfs/') + // The version on disk, which an update may have moved past this running process. export async function installedVersion(): Promise { try {