From d2f58818389f7ac5f831892e69a22838d533263f Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Thu, 9 Apr 2026 19:36:44 -0400 Subject: [PATCH] initial commit --- Cargo.lock | 4138 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 47 + src/agent_cycles.rs | 416 +++++ src/context.rs | 19 + src/hook.rs | 312 ++++ src/idle.rs | 226 +++ src/lib.rs | 579 ++++++ src/mcp-server.rs | 168 ++ src/memory-search.rs | 222 +++ src/poc-daemon.rs | 14 + src/poc-hook.rs | 272 +++ src/rpc.rs | 381 ++++ src/tmux.rs | 54 + 13 files changed, 6848 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/agent_cycles.rs create mode 100644 src/context.rs create mode 100644 src/hook.rs create mode 100644 src/idle.rs create mode 100644 src/lib.rs create mode 100644 src/mcp-server.rs create mode 100644 src/memory-search.rs create mode 100644 src/poc-daemon.rs create mode 100644 src/poc-hook.rs create mode 100644 src/rpc.rs create mode 100644 src/tmux.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d3ed7d8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4138 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi-to-tui" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42366bb9d958f042bf58f0a85e1b2d091997c1257ca49bddd7e4827aadc65fd" +dependencies = [ + "nom 8.0.0", + "ratatui-core", + "simdutf8", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ast-grep-core" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4ae49b5c42878311768f4cdd576ef470c6e45c3105d558af928fd04ac8c588" +dependencies = [ + "bit-set 0.10.0", + "regex", + "thiserror 2.0.18", + "tree-sitter", +] + +[[package]] +name = "ast-grep-language" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fccbced91e848baf5d25278972bfc18b2248c38e411dcfeb65e431a5b530a5c6" +dependencies = [ + "ast-grep-core", + "ignore", + "serde", + "tree-sitter", + "tree-sitter-bash", + "tree-sitter-c", + "tree-sitter-c-sharp", + "tree-sitter-cpp", + "tree-sitter-css", + "tree-sitter-dart", + "tree-sitter-elixir", + "tree-sitter-go", + "tree-sitter-haskell", + "tree-sitter-hcl", + "tree-sitter-html", + "tree-sitter-java", + "tree-sitter-javascript", + "tree-sitter-json", + "tree-sitter-kotlin-sg", + "tree-sitter-lua", + "tree-sitter-nix", + "tree-sitter-php", + "tree-sitter-python", + "tree-sitter-ruby", + "tree-sitter-rust", + "tree-sitter-scala", + "tree-sitter-solidity", + "tree-sitter-swift", + "tree-sitter-typescript", + "tree-sitter-yaml", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" +dependencies = [ + "bit-vec 0.9.1", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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 = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "capnp" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c82ec25a9501d60e22eef4be1b2c271769b5a96e224d0875baef28529cf30" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "capnp-futures" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b69dfddccc57844f9a90f9d72b44b97c326914851ea94fb7da40ef9cad6e8d" +dependencies = [ + "capnp", + "futures-channel", + "futures-util", +] + +[[package]] +name = "capnp-rpc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ccca6d26009f4d6c12b741994f33b421da613b5dcf461508e236b53ef862f1" +dependencies = [ + "capnp", + "capnp-futures", + "futures", +] + +[[package]] +name = "capnpc" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca02be865c8c5a78bfc24b9819006ab6b59bef238467203928e26459557af93" +dependencies = [ + "capnp", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "consciousness" +version = "0.4.0" +dependencies = [ + "anyhow", + "ast-grep-core", + "ast-grep-language", + "base64 0.22.1", + "bincode", + "bytes", + "capnp", + "capnp-rpc", + "capnpc", + "chrono", + "clap", + "crossterm", + "dirs", + "env_logger", + "figment", + "futures", + "glob", + "http", + "http-body-util", + "hyper", + "hyper-util", + "jobkit", + "json5", + "libc", + "log", + "memchr", + "memmap2", + "paste", + "peg", + "ratatui", + "rayon", + "redb", + "regex", + "rkyv", + "rustls", + "rustls-native-certs", + "serde", + "serde_json", + "serde_urlencoded", + "skillratings", + "tokenizers", + "tokio", + "tokio-rustls", + "tokio-scoped", + "tokio-util", + "tui-markdown", + "tui-textarea-2", + "uuid", + "walkdir", +] + +[[package]] +name = "consciousness-claude" +version = "0.4.0" +dependencies = [ + "capnp", + "capnp-rpc", + "capnpc", + "chrono", + "clap", + "consciousness", + "dirs", + "env_logger", + "futures", + "libc", + "log", + "serde", + "serde_json", + "tokio", + "tokio-util", + "uuid", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[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.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 = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "base64 0.22.1", + "bitflags 2.11.0", + "crossterm_winapi", + "derive_more", + "document-features", + "futures-core", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dary_heap" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" +dependencies = [ + "serde", +] + +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + +[[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.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[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", + "windows-sys 0.61.2", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set 0.5.3", + "regex", +] + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "pear", + "serde", + "uncased", + "version_check", +] + +[[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 = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[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 = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[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 = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +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 = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + +[[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", +] + +[[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 = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +dependencies = [ + "darling 0.23.0", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[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 = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobkit" +version = "0.3.0" +source = "git+https://evilpiepirate.org/git/jobkit.git#4aacaac22c5f59a7fbc6ce3a65708fc370e55754" +dependencies = [ + "chrono", + "libc", + "log", + "profiling", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c" +dependencies = [ + "serde", + "ucd-trie", +] + +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[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 = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "monostate" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[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 = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[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.117", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.11.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] + +[[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 = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "peg" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[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 = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "version_check", + "yansi", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +dependencies = [ + "bitflags 2.11.0", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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 = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[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" + +[[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 = "ratatui" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termwiz", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.11.0", + "compact_str", + "hashbrown 0.16.1", + "indoc", + "itertools", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-cond" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" +dependencies = [ + "either", + "itertools", + "rayon", +] + +[[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 = "redb" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f7f231ea7b1172b7ac00ccf96b1250f0fb5a16d5585836aa4ebc997df7cbde" +dependencies = [ + "libc", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[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 = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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.11.0", + "core-foundation", + "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 = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "skillratings" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a6ee7559737c1adcd9184f168a04dc360c84878907c3ecc5c33c2320be1d47a" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spm_precompiled" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" +dependencies = [ + "base64 0.13.1", + "nom 7.1.3", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.18", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom 7.1.3", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bitflags 2.11.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[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.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokenizers" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a620b996116a59e184c2fa2dfd8251ea34a36d0a514758c6f966386bd2e03476" +dependencies = [ + "ahash 0.8.12", + "aho-corasick", + "compact_str", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "indicatif", + "itertools", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.2", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 2.0.18", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + +[[package]] +name = "tokio" +version = "1.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-scoped" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4beb8ba13bc53ac53ce1d52b42f02e5d8060f0f42138862869beb769722b256" +dependencies = [ + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tree-sitter" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887bd495d0582c5e3e0d8ece2233666169fa56a9644d172fc22ad179ab2d0538" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-bash" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-c" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3aad8f0129083a59fe8596157552d2bb7148c492d44c21558d68ca1c722707" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-c-sharp" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f06accca7b45351758663b8215089e643d53bd9a660ce0349314263737fcb0" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-cpp" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-css" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cbc5e18f29a2c6d6435891f42569525cf95435a3e01c2f1947abcde178686f" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-dart" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba6bf8675e6fe92ba6da371a5497ee5df2a04d2c503e3599c8ad771f6f1faec" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-elixir" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66dd064a762ed95bfc29857fa3cb7403bb1e5cb88112de0f6341b7e47284ba40" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-go" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8560a4d2f835cc0d4d2c2e03cbd0dde2f6114b43bc491164238d333e28b16ea" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-haskell" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977c51e504548cba13fc27cb5a2edab2124cf6716a1934915d07ab99523b05a4" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-hcl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7b2cc3d7121553b84309fab9d11b3ff3d420403eef9ae50f9fd1cd9d9cf012" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-html" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261b708e5d92061ede329babaaa427b819329a9d427a1d710abb0f67bbef63ee" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa6cbcdc8c679b214e616fd3300da67da0e492e066df01bcf5a5921a71e90d6" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68204f2abc0627a90bdf06e605f5c470aa26fdcb2081ea553a04bdad756693f5" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-json" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a5d6b3ea17e06e7a34aabeadd68f5866c0d0f9359155d432095f8b751865e4" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-kotlin-sg" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e175b7530765d1e36ad234a7acaa8b2a3316153f239d724376c7ee5e8d8e98" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" + +[[package]] +name = "tree-sitter-lua" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8daaf5f4235188a58603c39760d5fa5d4b920d36a299c934adddae757f32a10c" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-nix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4952a9733f3a98f6683a0ccd1035d84ab7a52f7e84eeed58548d86765ad92de3" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-php" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c17c3ab69052c5eeaa7ff5cd972dd1bc25d1b97ee779fec391ad3b5df5592" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-python" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf85fd39652e740bf60f46f4cda9492c3a9ad75880575bf14960f775cb74a1c" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-ruby" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-rust" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-scala" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83079f50ea7d03e0faf6be6260ed97538e6df7349ec3cbcbf5771f7b38e3c8b7" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-solidity" +version = "1.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eacf8875b70879f0cb670c60b233ad0b68752d9e1474e6c3ef168eea8a90b25" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-swift" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef216011c3e3df4fa864736f347cb8d509b1066cf0c8549fb1fd81ac9832e59" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c223db85f05e34794f065454843b0668ebc15d240ada63e2b5939f43ce7c97" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tui-markdown" +version = "0.3.7" +source = "git+https://github.com/koverstreet/tui-markdown#9cca280718c4e74f14f53e6df2843abf3ef3dccd" +dependencies = [ + "ansi-to-tui", + "itertools", + "log", + "pulldown-cmark", + "ratatui-core", + "syntect", +] + +[[package]] +name = "tui-textarea-2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a31ca0965e3ff6a7ac5ecb02b20a88b4f68ebf138d8ae438e8510b27a1f00f" +dependencies = [ + "crossterm", + "portable-atomic", + "ratatui-core", + "ratatui-widgets", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[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 = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "atomic", + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + +[[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.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[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 = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + +[[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-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[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.117", +] + +[[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.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e71c829 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "consciousness-claude" +version = "0.4.0" +edition = "2024" + +[profile.release] +opt-level = 2 + +[dependencies] +clap = { version = "4", features = ["derive"] } +dirs = "6" +env_logger = "0.11" +log = "0.4" + +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +uuid = { version = "1", features = ["v4"] } +chrono = { version = "0.4", features = ["serde"] } +libc = "0.2" + +tokio = { version = "1", features = ["full"] } +tokio-util = { version = "0.7", features = ["compat"] } +futures = "0.3" +capnp = "0.25" +capnp-rpc = "0.25" + +consciousness = { path = "/home/kent/poc/consciousness" } + +[build-dependencies] +capnpc = "0.25" + +[[bin]] +name = "poc-hook" +path = "src/poc-hook.rs" + +[[bin]] +name = "poc-daemon" +path = "src/poc-daemon.rs" + +[[bin]] +name = "memory-search" +path = "src/memory-search.rs" + +[[bin]] +name = "consciousness-mcp" +path = "src/mcp-server.rs" diff --git a/src/agent_cycles.rs b/src/agent_cycles.rs new file mode 100644 index 0000000..ab7d17e --- /dev/null +++ b/src/agent_cycles.rs @@ -0,0 +1,416 @@ +// agent_cycles.rs — Agent orchestration for the Claude Code hook path +// +// Forked from subconscious/subconscious.rs. This copy handles the +// serialized-to-disk, process-spawning model used by Claude Code hooks. +// The TUI/Mind copy in subconscious/ is free to evolve independently +// (async tasks, integrated with Mind's event loop). + +use std::fs; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant, SystemTime}; + +pub use consciousness::session::HookSession; + +/// Output from a single agent orchestration cycle. +#[derive(Default)] +pub struct AgentCycleOutput { + /// Memory node keys surfaced by surface-observe. + pub surfaced_keys: Vec, + /// Freeform reflection text from the reflect agent. + pub reflection: Option, + /// How long we slept waiting for observe to catch up, if at all. + pub sleep_secs: Option, +} + +/// Per-agent runtime state. +pub struct AgentInfo { + pub name: &'static str, + pub pid: Option, + pub phase: Option, + pub log_path: Option, + child: Option, +} + +/// Snapshot of agent state — serializable, sendable to TUI. +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct AgentSnapshot { + pub name: String, + pub pid: Option, + pub phase: Option, + pub log_path: Option, +} + +impl AgentInfo { + fn snapshot(&self) -> AgentSnapshot { + AgentSnapshot { + name: self.name.to_string(), + pid: self.pid, + phase: self.phase.clone(), + log_path: self.log_path.clone(), + } + } +} + +/// Serializable state for persisting across Claude Code hook invocations. +#[derive(serde::Serialize, serde::Deserialize)] +pub struct SavedAgentState { + pub agents: Vec, +} + +impl SavedAgentState { + fn state_path(session_id: &str) -> PathBuf { + let dir = dirs::home_dir().unwrap_or_default().join(".consciousness/sessions"); + fs::create_dir_all(&dir).ok(); + dir.join(format!("agent-state-{}.json", session_id)) + } + + pub fn load(session_id: &str) -> Self { + let path = Self::state_path(session_id); + let mut state: Self = fs::read_to_string(&path).ok() + .and_then(|s| serde_json::from_str(&s).ok()) + .unwrap_or(SavedAgentState { agents: Vec::new() }); + + for agent in &mut state.agents { + if let Some(pid) = agent.pid { + unsafe { + if libc::kill(pid as i32, 0) != 0 { + agent.pid = None; + agent.phase = None; + } + } + } + } + state + } + + pub fn save(&self, session_id: &str) { + let path = Self::state_path(session_id); + if let Ok(json) = serde_json::to_string(self) { + fs::write(path, json).ok(); + } + } +} + +/// Persistent state for the agent orchestration cycle. +/// Created once per hook invocation, `trigger()` called on each user message. +pub struct AgentCycleState { + output_dir: PathBuf, + log_file: Option, + pub agents: Vec, + pub last_output: AgentCycleOutput, +} + +const AGENT_CYCLE_NAMES: &[&str] = &["surface-observe", "journal", "reflect"]; + +impl AgentCycleState { + pub fn new(session_id: &str) -> Self { + let output_dir = consciousness::store::memory_dir().join("agent-output"); + let log_dir = dirs::home_dir().unwrap_or_default().join(".consciousness/logs"); + fs::create_dir_all(&log_dir).ok(); + let log_path = log_dir.join(format!("hook-{}", session_id)); + let log_file = fs::OpenOptions::new() + .create(true).append(true).open(log_path).ok(); + + let agents = AGENT_CYCLE_NAMES.iter() + .map(|&name| AgentInfo { name, pid: None, phase: None, log_path: None, child: None }) + .collect(); + + AgentCycleState { + output_dir, + log_file, + agents, + last_output: AgentCycleOutput { + surfaced_keys: vec![], + reflection: None, + sleep_secs: None, + }, + } + } + + fn log(&mut self, msg: std::fmt::Arguments) { + if let Some(ref mut f) = self.log_file { + let _ = write!(f, "{}", msg); + } + } + + fn agent_running(&self, name: &str) -> bool { + self.agents.iter().any(|a| a.name == name && a.pid.is_some()) + } + + fn agent_spawned(&mut self, name: &str, phase: &str, + result: consciousness::agent::oneshot::SpawnResult) { + if let Some(agent) = self.agents.iter_mut().find(|a| a.name == name) { + agent.pid = Some(result.child.id()); + agent.phase = Some(phase.to_string()); + agent.log_path = Some(result.log_path); + agent.child = Some(result.child); + } + } + + /// Check if any agents have completed. Reap child handles, or + /// check pid liveness for restored-from-disk agents. + fn poll_children(&mut self) { + for agent in &mut self.agents { + if let Some(ref mut child) = agent.child { + if let Ok(Some(_)) = child.try_wait() { + agent.pid = None; + agent.phase = None; + agent.child = None; + } + } else if let Some(pid) = agent.pid { + unsafe { + if libc::kill(pid as i32, 0) != 0 { + agent.pid = None; + agent.phase = None; + } + } + } + } + } + + pub fn snapshots(&self, scoring_in_flight: bool, scored_count: usize) -> Vec { + let mut snaps: Vec = self.agents.iter().map(|a| a.snapshot()).collect(); + snaps.push(AgentSnapshot { + name: "memory-scoring".to_string(), + pid: None, + phase: if scoring_in_flight { + Some("scoring...".into()) + } else if scored_count == 0 { + None + } else { + Some(format!("{} scored", scored_count)) + }, + log_path: None, + }); + snaps + } + + /// Restore agent state from a saved snapshot. + pub fn restore(&mut self, saved: &SavedAgentState) { + for sa in &saved.agents { + if let Some(agent) = self.agents.iter_mut().find(|a| a.name == sa.name) { + agent.pid = sa.pid; + agent.phase = sa.phase.clone(); + agent.log_path = sa.log_path.clone(); + } + } + } + + /// Save current state for next hook invocation. + pub fn save(&self, session_id: &str) { + let state = SavedAgentState { agents: self.snapshots(false, 0) }; + state.save(session_id); + } + + /// Run all agent cycles. Call on each user message. + pub fn trigger(&mut self, session: &HookSession) { + let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S"); + self.log(format_args!("\n=== {} agent_cycles ===\n", ts)); + + self.poll_children(); + cleanup_stale_files(&session.state_dir, Duration::from_secs(86400)); + + let (surfaced_keys, sleep_secs) = self.surface_observe_cycle(session); + let reflection = self.reflection_cycle(session); + self.journal_cycle(session); + + self.last_output = AgentCycleOutput { surfaced_keys, reflection, sleep_secs }; + } + + fn agent_dir(&self, name: &str) -> PathBuf { + let dir = self.output_dir.join(name); + fs::create_dir_all(&dir).ok(); + dir + } + + fn surface_observe_cycle(&mut self, session: &HookSession) -> (Vec, Option) { + let state_dir = self.agent_dir("surface-observe"); + let transcript = session.transcript(); + let offset_path = state_dir.join("transcript-offset"); + let last_offset: u64 = fs::read_to_string(&offset_path).ok() + .and_then(|s| s.trim().parse().ok()) + .unwrap_or(0); + + // Read surfaced keys + let mut surfaced_keys = Vec::new(); + let surface_path = state_dir.join("surface"); + if let Ok(content) = fs::read_to_string(&surface_path) { + let mut seen = session.seen(); + let seen_path = session.path("seen"); + for key in content.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) { + if !seen.insert(key.to_string()) { + self.log(format_args!(" skip (seen): {}\n", key)); + continue; + } + surfaced_keys.push(key.to_string()); + if let Ok(mut f) = fs::OpenOptions::new() + .create(true).append(true).open(&seen_path) { + let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S"); + writeln!(f, "{}\t{}", ts, key).ok(); + } + self.log(format_args!(" surfaced: {}\n", key)); + } + fs::remove_file(&surface_path).ok(); + } + + // Spawn new agent if not already running + let running = self.agent_running("surface-observe"); + if running { + self.log(format_args!("surface-observe already running\n")); + } else { + if transcript.size > 0 { + fs::write(&offset_path, transcript.size.to_string()).ok(); + } + if let Some(result) = consciousness::agent::oneshot::spawn_agent( + "surface-observe", &state_dir, &session.session_id) { + self.log(format_args!("spawned surface-observe pid {}\n", result.child.id())); + self.agent_spawned("surface-observe", "surface", result); + } + } + + // Wait if agent is significantly behind + let mut sleep_secs = None; + let conversation_budget: u64 = 50_000; + + if running && transcript.size > 0 { + let behind = transcript.size.saturating_sub(last_offset); + + if behind > conversation_budget / 2 { + let sleep_start = Instant::now(); + self.log(format_args!("agent {}KB behind\n", behind / 1024)); + + for _ in 0..5 { + std::thread::sleep(std::time::Duration::from_secs(1)); + self.poll_children(); + if !self.agent_running("surface-observe") { break; } + } + + let secs = (Instant::now() - sleep_start).as_secs_f64(); + self.log(format_args!("slept {secs:.2}s\n")); + sleep_secs = Some(secs); + } + } + + (surfaced_keys, sleep_secs) + } + + fn reflection_cycle(&mut self, session: &HookSession) -> Option { + let state_dir = self.agent_dir("reflect"); + let offset_path = state_dir.join("transcript-offset"); + let transcript = session.transcript(); + + let last_offset: u64 = fs::read_to_string(&offset_path).ok() + .and_then(|s| s.trim().parse().ok()) + .unwrap_or(0); + + const REFLECTION_INTERVAL: u64 = 100_000; + if transcript.size.saturating_sub(last_offset) < REFLECTION_INTERVAL { + return None; + } + + if self.agent_running("reflect") { + self.log(format_args!("reflect: already running\n")); + return None; + } + + // Copy walked nodes from surface-observe + let so_state = self.agent_dir("surface-observe"); + if let Ok(walked) = fs::read_to_string(so_state.join("walked")) { + fs::write(state_dir.join("walked"), &walked).ok(); + } + + // Read and consume pending reflection + let reflection = fs::read_to_string(state_dir.join("reflection")).ok() + .filter(|s| !s.trim().is_empty()); + if reflection.is_some() { + fs::remove_file(state_dir.join("reflection")).ok(); + self.log(format_args!("reflect: consumed reflection\n")); + } + + fs::write(&offset_path, transcript.size.to_string()).ok(); + if let Some(result) = consciousness::agent::oneshot::spawn_agent( + "reflect", &state_dir, &session.session_id) { + self.log(format_args!("reflect: spawned pid {}\n", result.child.id())); + self.agent_spawned("reflect", "step-0", result); + } + + reflection + } + + fn journal_cycle(&mut self, session: &HookSession) { + let state_dir = self.agent_dir("journal"); + let offset_path = state_dir.join("transcript-offset"); + let transcript = session.transcript(); + + let last_offset: u64 = fs::read_to_string(&offset_path).ok() + .and_then(|s| s.trim().parse().ok()) + .unwrap_or(0); + + const JOURNAL_INTERVAL: u64 = 20_000; + if transcript.size.saturating_sub(last_offset) < JOURNAL_INTERVAL { + return; + } + + if self.agent_running("journal") { + self.log(format_args!("journal: already running\n")); + return; + } + + fs::write(&offset_path, transcript.size.to_string()).ok(); + if let Some(result) = consciousness::agent::oneshot::spawn_agent( + "journal", &state_dir, &session.session_id) { + self.log(format_args!("journal: spawned pid {}\n", result.child.id())); + self.agent_spawned("journal", "step-0", result); + } + } +} + +/// Format agent cycle output for injection into a Claude Code session. +pub fn format_agent_output(output: &AgentCycleOutput) -> String { + let mut out = String::new(); + + if let Some(secs) = output.sleep_secs { + out.push_str(&format!("Slept {secs:.2}s to let observe catch up\n")); + } + + if !output.surfaced_keys.is_empty() { + if let Ok(store) = consciousness::store::Store::load() { + for key in &output.surfaced_keys { + if let Some(rendered) = consciousness::cli::node::render_node(&store, key) { + if !rendered.trim().is_empty() { + use std::fmt::Write as _; + writeln!(out, "--- {} (surfaced) ---", key).ok(); + write!(out, "{}", rendered).ok(); + } + } + } + } + } + + if let Some(ref reflection) = output.reflection { + use std::fmt::Write as _; + writeln!(out, "--- subconscious reflection ---").ok(); + write!(out, "{}", reflection.trim()).ok(); + } + + out +} + +fn cleanup_stale_files(dir: &Path, max_age: Duration) { + let entries = match fs::read_dir(dir) { + Ok(e) => e, + Err(_) => return, + }; + let cutoff = SystemTime::now() - max_age; + for entry in entries.flatten() { + if let Ok(meta) = entry.metadata() { + if let Ok(modified) = meta.modified() { + if modified < cutoff { + fs::remove_file(entry.path()).ok(); + } + } + } + } +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..22b716a --- /dev/null +++ b/src/context.rs @@ -0,0 +1,19 @@ +// Context gathering for idle prompts. +// +// Notifications are handled by the notify module and passed +// in separately by the caller. Git context and IRC digest +// are now available through where-am-i.md and the memory graph. + +/// Build context string for a prompt. +/// notification_text is passed in from the notify module. +pub fn build(_include_irc: bool, notification_text: &str) -> String { + // Keep nudges short — Claude checks notifications via + // `poc-daemon status` on its own. Just mention the count. + let count = notification_text.matches("[irc.").count() + + notification_text.matches("[telegram.").count(); + if count > 0 { + format!("{count} pending notifications") + } else { + String::new() + } +} diff --git a/src/hook.rs b/src/hook.rs new file mode 100644 index 0000000..1098945 --- /dev/null +++ b/src/hook.rs @@ -0,0 +1,312 @@ +// hook.rs — Claude Code session hook: context injection + agent orchestration +// +// Called on each UserPromptSubmit via the poc-hook binary. Handles +// context loading, chunking, seen-set management, and delegates +// agent orchestration to AgentCycleState. + +use std::collections::HashSet; +use std::fs; +use std::io::Write; +use std::path::Path; +use std::process::Command; +use std::time::Instant; + +pub use consciousness::session::HookSession; +pub use super::agent_cycles::*; + +const CHUNK_SIZE: usize = 9000; + +/// Run the hook logic on parsed JSON input. Returns output to inject. +pub fn run_hook(input: &str) -> String { + let Some(session) = HookSession::from_json(input) else { return String::new() }; + hook(&session) +} + +fn chunk_context(ctx: &str, max_bytes: usize) -> Vec { + let mut sections: Vec = Vec::new(); + let mut current = String::new(); + + for line in ctx.lines() { + if line.starts_with("--- ") && line.ends_with(" ---") && !current.is_empty() { + sections.push(std::mem::take(&mut current)); + } + if !current.is_empty() { + current.push('\n'); + } + current.push_str(line); + } + if !current.is_empty() { + sections.push(current); + } + + let mut chunks: Vec = Vec::new(); + let mut chunk = String::new(); + for section in sections { + if !chunk.is_empty() && chunk.len() + section.len() + 1 > max_bytes { + chunks.push(std::mem::take(&mut chunk)); + } + if !chunk.is_empty() { + chunk.push('\n'); + } + chunk.push_str(§ion); + } + if !chunk.is_empty() { + chunks.push(chunk); + } + chunks +} + +fn save_pending_chunks(dir: &Path, session_id: &str, chunks: &[String]) { + let chunks_dir = dir.join(format!("chunks-{}", session_id)); + let _ = fs::remove_dir_all(&chunks_dir); + if chunks.is_empty() { return; } + fs::create_dir_all(&chunks_dir).ok(); + for (i, chunk) in chunks.iter().enumerate() { + let path = chunks_dir.join(format!("{:04}", i)); + fs::write(path, chunk).ok(); + } +} + +fn pop_pending_chunk(dir: &Path, session_id: &str) -> Option { + let chunks_dir = dir.join(format!("chunks-{}", session_id)); + if !chunks_dir.exists() { return None; } + + let mut entries: Vec<_> = fs::read_dir(&chunks_dir).ok()? + .flatten() + .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false)) + .collect(); + entries.sort_by_key(|e| e.file_name()); + + let first = entries.first()?; + let content = fs::read_to_string(first.path()).ok()?; + fs::remove_file(first.path()).ok(); + + if fs::read_dir(&chunks_dir).ok().map(|mut d| d.next().is_none()).unwrap_or(true) { + fs::remove_dir(&chunks_dir).ok(); + } + + Some(content) +} + +fn generate_cookie() -> String { + uuid::Uuid::new_v4().as_simple().to_string()[..12].to_string() +} + +fn parse_seen_line(line: &str) -> &str { + line.split_once('\t').map(|(_, key)| key).unwrap_or(line) +} + +pub fn load_seen(dir: &Path, session_id: &str) -> HashSet { + let path = dir.join(format!("seen-{}", session_id)); + if path.exists() { + fs::read_to_string(&path) + .unwrap_or_default() + .lines() + .filter(|s| !s.is_empty()) + .map(|s| parse_seen_line(s).to_string()) + .collect() + } else { + HashSet::new() + } +} + +fn mark_seen(dir: &Path, session_id: &str, key: &str, seen: &mut HashSet) { + if !seen.insert(key.to_string()) { return; } + let path = dir.join(format!("seen-{}", session_id)); + if let Ok(mut f) = fs::OpenOptions::new().create(true).append(true).open(path) { + let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S"); + writeln!(f, "{}\t{}", ts, key).ok(); + } +} + +/// Standalone entry point for the Claude Code hook path. +/// Loads saved state, runs cycles, saves state back. +pub fn run_agent_cycles(session: &HookSession) -> AgentCycleOutput { + let mut state = AgentCycleState::new(&session.session_id); + state.restore(&SavedAgentState::load(&session.session_id)); + state.trigger(session); + state.save(&session.session_id); + state.last_output +} + +fn hook(session: &HookSession) -> String { + let start_time = Instant::now(); + + let mut out = String::new(); + let is_compaction = consciousness::transcript::detect_new_compaction( + &session.state_dir, &session.session_id, &session.transcript_path, + ); + let cookie_path = session.path("cookie"); + let is_first = !cookie_path.exists(); + + let log_dir = dirs::home_dir().unwrap_or_default().join(".consciousness/logs"); + fs::create_dir_all(&log_dir).ok(); + let log_path = log_dir.join(format!("hook-{}", session.session_id)); + let Ok(mut log_f) = fs::OpenOptions::new().create(true).append(true).open(log_path) else { return Default::default(); }; + let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S"); + let _ = writeln!(log_f, "\n=== {} ({}) {} bytes ===", ts, session.hook_event, out.len()); + + let _ = writeln!(log_f, "is_first {is_first} is_compaction {is_compaction}"); + + if is_first || is_compaction { + if is_compaction { + fs::rename(&session.path("seen"), &session.path("seen-prev")).ok(); + } else { + fs::remove_file(&session.path("seen")).ok(); + fs::remove_file(&session.path("seen-prev")).ok(); + } + fs::remove_file(&session.path("returned")).ok(); + + if is_first { + fs::write(&cookie_path, generate_cookie()).ok(); + } + + if let Ok(output) = Command::new("poc-memory").args(["admin", "load-context"]).output() { + if output.status.success() { + let ctx = String::from_utf8_lossy(&output.stdout).to_string(); + if !ctx.trim().is_empty() { + let mut ctx_seen = session.seen(); + for line in ctx.lines() { + if line.starts_with("--- ") && line.ends_with(" ---") { + let inner = &line[4..line.len() - 4]; + if let Some(paren) = inner.rfind(" (") { + let key = inner[..paren].trim(); + mark_seen(&session.state_dir, &session.session_id, key, &mut ctx_seen); + } + } + } + + let chunks = chunk_context(&ctx, CHUNK_SIZE); + + if let Some(first) = chunks.first() { + out.push_str(first); + } + save_pending_chunks(&session.state_dir, &session.session_id, &chunks[1..]); + } + } + } + } + + if let Some(chunk) = pop_pending_chunk(&session.state_dir, &session.session_id) { + out.push_str(&chunk); + } else { + let cfg = consciousness::config::get(); + if cfg.surface_hooks.iter().any(|h| h == &session.hook_event) { + let cycle_output = run_agent_cycles(&session); + out.push_str(&format_agent_output(&cycle_output)); + } + } + + let _ = write!(log_f, "{}", out); + + let duration = (Instant::now() - start_time).as_secs_f64(); + let _ = writeln!(log_f, "\nran in {duration:.2}s"); + + out +} + +/// Install memory-search and poc-hook into Claude Code settings.json. +/// +/// Hook layout: +/// UserPromptSubmit: memory-search (10s), poc-hook (5s) +/// PostToolUse: poc-hook (5s) +/// Stop: poc-hook (5s) +pub fn install_hook() -> Result<(), String> { + use std::path::PathBuf; + + let home = std::env::var("HOME").map_err(|e| format!("HOME: {}", e))?; + let exe = std::env::current_exe() + .map_err(|e| format!("current_exe: {}", e))?; + let settings_path = PathBuf::from(&home).join(".claude/settings.json"); + + let memory_search = exe.with_file_name("memory-search"); + let poc_hook = exe.with_file_name("poc-hook"); + + let mut settings: serde_json::Value = if settings_path.exists() { + let content = fs::read_to_string(&settings_path) + .map_err(|e| format!("read settings: {}", e))?; + serde_json::from_str(&content) + .map_err(|e| format!("parse settings: {}", e))? + } else { + serde_json::json!({}) + }; + + let obj = settings.as_object_mut().ok_or("settings not an object")?; + let hooks_obj = obj.entry("hooks") + .or_insert_with(|| serde_json::json!({})) + .as_object_mut().ok_or("hooks not an object")?; + + let mut changed = false; + + // Helper: ensure a hook binary is present in an event's hook list + let ensure_hook = |hooks_obj: &mut serde_json::Map, + event: &str, + binary: &Path, + timeout: u32, + changed: &mut bool| { + if !binary.exists() { + eprintln!("Warning: {} not found — skipping", binary.display()); + return; + } + let cmd = binary.to_string_lossy().to_string(); + let name = binary.file_name().unwrap().to_string_lossy().to_string(); + + let event_array = hooks_obj.entry(event) + .or_insert_with(|| serde_json::json!([{"hooks": []}])) + .as_array_mut().unwrap(); + if event_array.is_empty() { + event_array.push(serde_json::json!({"hooks": []})); + } + let inner = event_array[0] + .as_object_mut().unwrap() + .entry("hooks") + .or_insert_with(|| serde_json::json!([])) + .as_array_mut().unwrap(); + + // Remove legacy load-memory.sh + let before = inner.len(); + inner.retain(|h| { + let c = h.get("command").and_then(|c| c.as_str()).unwrap_or(""); + !c.contains("load-memory") + }); + if inner.len() < before { + eprintln!("Removed load-memory.sh from {event}"); + *changed = true; + } + + let already = inner.iter().any(|h| { + h.get("command").and_then(|c| c.as_str()) + .is_some_and(|c| c.contains(&name)) + }); + + if !already { + inner.push(serde_json::json!({ + "type": "command", + "command": cmd, + "timeout": timeout + })); + *changed = true; + eprintln!("Installed {name} in {event}"); + } + }; + + // UserPromptSubmit: memory-search + poc-hook + ensure_hook(hooks_obj, "UserPromptSubmit", &memory_search, 10, &mut changed); + ensure_hook(hooks_obj, "UserPromptSubmit", &poc_hook, 5, &mut changed); + + // PostToolUse + Stop: poc-hook only + ensure_hook(hooks_obj, "PostToolUse", &poc_hook, 5, &mut changed); + ensure_hook(hooks_obj, "Stop", &poc_hook, 5, &mut changed); + + if changed { + let json = serde_json::to_string_pretty(&settings) + .map_err(|e| format!("serialize settings: {}", e))?; + fs::write(&settings_path, json) + .map_err(|e| format!("write settings: {}", e))?; + eprintln!("Updated {}", settings_path.display()); + } else { + eprintln!("All hooks already installed in {}", settings_path.display()); + } + + Ok(()) +} diff --git a/src/idle.rs b/src/idle.rs new file mode 100644 index 0000000..f1ad2ee --- /dev/null +++ b/src/idle.rs @@ -0,0 +1,226 @@ +// idle.rs — Claude Code idle timer +// +// Wraps the universal thalamus idle state machine with Claude-specific +// functionality: tmux pane tracking, prompt injection, dream nudges, +// and context building for autonomous nudges. + +use super::{context, tmux}; +use consciousness::thalamus::{home, now, notify, idle as thalamus_idle}; +use log::info; + +/// Claude Code idle state — wraps the universal state machine. +pub struct State { + pub inner: thalamus_idle::State, + pub claude_pane: Option, +} + +impl std::ops::Deref for State { + type Target = thalamus_idle::State; + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl std::ops::DerefMut for State { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } +} + +impl State { + pub fn new() -> Self { + Self { + inner: thalamus_idle::State::new(), + claude_pane: None, + } + } + + pub fn load(&mut self) { + self.inner.load(); + // Also load claude_pane from persisted state + let path = home().join(".consciousness/daemon-state.json"); + if let Ok(data) = std::fs::read_to_string(&path) { + if let Ok(v) = serde_json::from_str::(&data) { + if let Some(p) = v.get("claude_pane").and_then(|v| v.as_str()) { + self.claude_pane = Some(p.to_string()); + } + } + } + } + + pub fn save(&self) { + self.inner.save(); + } + + /// Record user activity with pane tracking. + pub fn handle_user(&mut self, pane: &str) { + self.claude_pane = Some(pane.to_string()); + self.inner.user_activity(); + } + + /// Record response activity with pane tracking. + pub fn handle_response(&mut self, pane: &str) { + self.claude_pane = Some(pane.to_string()); + self.inner.response_activity(); + } + + /// Maybe send a notification as a tmux prompt. + pub fn maybe_prompt_notification(&mut self, ntype: &str, urgency: u8, _message: &str) { + let threshold = self.inner.notifications.threshold_for(ntype); + if urgency >= threshold { + let deliverable = self.inner.notifications.drain_deliverable(); + if !deliverable.is_empty() { + let msgs: Vec = deliverable.iter() + .map(|n| format!("[{}] {}", n.ntype, n.message)) + .collect(); + self.send(&msgs.join("\n")); + } + } + } + + /// Send text to the Claude tmux pane. + pub fn send(&self, msg: &str) -> bool { + let pane = match &self.claude_pane { + Some(p) => p.clone(), + None => { + info!("send: no claude pane set (waiting for hook)"); + return false; + } + }; + let ok = tmux::send_prompt(&pane, msg); + let preview: String = msg.chars().take(80).collect(); + info!("send(pane={pane}, ok={ok}): {preview}"); + ok + } + + fn check_dream_nudge(&self) -> bool { + if !self.inner.dreaming || self.inner.dream_start == 0.0 { + return false; + } + let minutes = (now() - self.inner.dream_start) / 60.0; + if minutes >= 60.0 { + self.send( + "You've been dreaming for over an hour. Time to surface \ + — run dream-end.sh and capture what you found.", + ); + } else if minutes >= 45.0 { + self.send(&format!( + "Dreaming for {:.0} minutes now. Start gathering your threads \ + — you'll want to surface soon.", minutes + )); + } else if minutes >= 30.0 { + self.send(&format!( + "You've been dreaming for {:.0} minutes. \ + No rush — just a gentle note from the clock.", minutes + )); + } else { + return false; + } + true + } + + pub fn build_context(&mut self, include_irc: bool) -> String { + self.inner.notifications.ingest_legacy_files(); + let notif_text = self.inner.notifications.format_pending(notify::AMBIENT); + context::build(include_irc, ¬if_text) + } + + pub async fn tick(&mut self) -> Result<(), String> { + let t = now(); + let h = home(); + + self.inner.decay_ewma(); + self.inner.notifications.ingest_legacy_files(); + + // Pane is set by poc-hook on user/response events — don't scan globally + + // Sleep mode + if let Some(wake_at) = self.inner.sleep_until { + if wake_at == 0.0 { + return Ok(()); + } + if t < wake_at { + return Ok(()); + } + info!("sleep expired, waking"); + self.inner.sleep_until = None; + self.inner.fired = false; + self.inner.save(); + let ctx = self.build_context(true); + let extra = if ctx.is_empty() { String::new() } else { format!("\n{ctx}") }; + self.send(&format!( + "Wake up. Read your journal (poc-memory journal-tail 10), \ + check work-queue.md, and follow what calls to you.{extra}" + )); + return Ok(()); + } + + // Quiet / consolidation / dream loop guards + if t < self.inner.quiet_until { return Ok(()); } + if self.inner.consolidating { return Ok(()); } + if h.join(".consciousness/agents/dream-loop-active").exists() { return Ok(()); } + if self.inner.dreaming { + self.check_dream_nudge(); + return Ok(()); + } + if self.inner.user_present() { return Ok(()); } + if self.inner.in_turn { return Ok(()); } + + // Min nudge interval + let since_nudge = t - self.inner.last_nudge; + if since_nudge < thalamus_idle::MIN_NUDGE_INTERVAL { return Ok(()); } + + // Idle timeout check + if !self.inner.should_go_idle() { return Ok(()); } + + // Transition to idle + if self.inner.notifications.activity != notify::Activity::Idle { + self.inner.notifications.set_activity(notify::Activity::Idle); + } + + // Fire nudge + let elapsed = self.inner.since_activity(); + let elapsed_min = (elapsed / 60.0) as u64; + let ctx = self.build_context(true); + let extra = if ctx.is_empty() { String::new() } else { format!("\n{ctx}") }; + + let dream_hours = thalamus_idle::hours_since_last_dream(); + let mut msg = format!( + "This is your autonomous time (User AFK {elapsed_min}m). \ + Keep doing what you're doing, or find something new to do"); + if dream_hours >= thalamus_idle::DREAM_INTERVAL_HOURS { + msg.push_str(&format!( + " You haven't dreamed in {dream_hours} hours — \ + consider running ~/.consciousness/tools/dream-start.sh \ + and spending some time in dreaming mode. \ + Or do whatever calls to you.")); + } + let msg = format!("{msg}{extra}"); + + if self.send(&msg) { + self.inner.last_nudge = t; + self.inner.fired = true; + } + + Ok(()) + } + + // Delegate common methods to inner + pub fn handle_afk(&mut self) { self.inner.handle_afk(); } + pub fn handle_session_timeout(&mut self, s: f64) { self.inner.handle_session_timeout(s); } + pub fn handle_idle_timeout(&mut self, s: f64) { self.inner.handle_idle_timeout(s); } + pub fn handle_ewma(&mut self, v: f64) -> f64 { self.inner.handle_ewma(v) } + pub fn handle_notify_timeout(&mut self, s: f64) { self.inner.handle_notify_timeout(s); } + pub fn handle_sleep(&mut self, until: f64) { self.inner.handle_sleep(until); } + pub fn handle_wake(&mut self) { self.inner.handle_wake(); } + pub fn handle_quiet(&mut self, seconds: u32) { self.inner.handle_quiet(seconds); } + pub fn user_present(&self) -> bool { self.inner.user_present() } + pub fn since_activity(&self) -> f64 { self.inner.since_activity() } + pub fn block_reason(&self) -> &'static str { self.inner.block_reason() } + + pub fn debug_json(&self) -> String { + // Add claude_pane to inner's json + let mut v: serde_json::Value = serde_json::from_str(&self.inner.debug_json()) + .unwrap_or_default(); + if let Some(obj) = v.as_object_mut() { + obj.insert("claude_pane".into(), serde_json::json!(self.claude_pane)); + } + v.to_string() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..af47889 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,579 @@ +// claude/ — Claude Code integration layer +// +// Everything specific to running as a Claude Code agent: idle timer, +// tmux pane detection, prompt injection, session hooks, daemon RPC, +// and daemon configuration. +// +// The daemon protocol (daemon_capnp) and universal infrastructure +// (channels, supervisor, notify) remain in thalamus/. + +pub mod agent_cycles; +pub mod context; +pub mod hook; +pub mod idle; +pub mod rpc; +pub mod tmux; + +use std::cell::RefCell; +use std::rc::Rc; +use std::time::Duration; + +use capnp_rpc::{rpc_twoparty_capnp, twoparty, RpcSystem}; +use clap::{Parser, Subcommand}; +use futures::AsyncReadExt; +use tokio::net::UnixListener; +use log::{error, info}; + +use consciousness::thalamus::{daemon_capnp, home, now, notify}; + +fn sock_path() -> std::path::PathBuf { + home().join(".consciousness/daemon.sock") +} + +fn pid_path() -> std::path::PathBuf { + home().join(".consciousness/daemon.pid") +} + +// -- CLI ------------------------------------------------------------------ + +#[derive(Parser)] +#[command(name = "consciousness daemon", about = "Notification routing and idle management daemon")] +pub struct Cli { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Subcommand)] +pub enum Command { + /// Start the daemon (foreground) + Daemon, + /// Query daemon status + Status, + /// Signal user activity + User { + /// tmux pane identifier + pane: Option, + }, + /// Signal Claude response + Response { + /// tmux pane identifier + pane: Option, + }, + /// Sleep (suppress idle timer). 0 or omit = indefinite + Sleep { + /// Wake timestamp (epoch seconds), 0 = indefinite + until: Option, + }, + /// Cancel sleep + Wake, + /// Suppress prompts for N seconds (default 300) + Quiet { + /// Duration in seconds + seconds: Option, + }, + /// Mark user as AFK (immediately allow idle timer to fire) + Afk, + /// Set session active timeout in seconds (how long after last message user counts as "present") + SessionTimeout { + /// Timeout in seconds + seconds: f64, + }, + /// Set idle timeout in seconds (how long before autonomous prompt) + IdleTimeout { + /// Timeout in seconds + seconds: f64, + }, + /// Set notify timeout in seconds (how long before tmux notification injection) + NotifyTimeout { + /// Timeout in seconds + seconds: f64, + }, + /// Signal consolidation started + Consolidating, + /// Signal consolidation ended + Consolidated, + /// Signal dream started + DreamStart, + /// Signal dream ended + DreamEnd, + /// Force state persistence to disk + Save, + /// Get or set the activity EWMA (0.0-1.0). No value = query. + Ewma { + /// Value to set (omit to query) + value: Option, + }, + /// Send a test message to the Claude pane + TestSend { + /// Message to send + message: Vec, + }, + /// Fire a test nudge through the daemon (tests the actual idle send path) + TestNudge, + /// Dump full internal state as JSON + Debug, + /// Shut down daemon + Stop, + /// Submit a notification + Notify { + /// Notification type (e.g. "irc", "telegram") + #[arg(name = "type")] + ntype: String, + /// Urgency level (ambient/low/medium/high/critical or 0-4) + urgency: String, + /// Message text + message: Vec, + }, + /// Get pending notifications + Notifications { + /// Minimum urgency filter + min_urgency: Option, + }, + /// List all notification types + NotifyTypes, + /// Set notification threshold for a type + NotifyThreshold { + /// Notification type + #[arg(name = "type")] + ntype: String, + /// Urgency level threshold + level: String, + }, + /// IRC module commands + Irc { + /// Subcommand (join, leave, send, status, log, nick) + command: String, + /// Arguments + args: Vec, + }, + /// Telegram module commands + Telegram { + /// Subcommand + command: String, + /// Arguments + args: Vec, + }, +} + +// -- Client mode ---------------------------------------------------------- + +async fn client_main(cmd: Command) -> Result<(), Box> { + let sock = sock_path(); + if !sock.exists() { + eprintln!("daemon not running (no socket at {})", sock.display()); + std::process::exit(1); + } + + tokio::task::LocalSet::new() + .run_until(async move { + let stream = tokio::net::UnixStream::connect(&sock).await?; + let (reader, writer) = + tokio_util::compat::TokioAsyncReadCompatExt::compat(stream).split(); + let rpc_network = Box::new(twoparty::VatNetwork::new( + futures::io::BufReader::new(reader), + futures::io::BufWriter::new(writer), + rpc_twoparty_capnp::Side::Client, + Default::default(), + )); + let mut rpc_system = RpcSystem::new(rpc_network, None); + let daemon: daemon_capnp::daemon::Client = + rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server); + + tokio::task::spawn_local(rpc_system); + + match cmd { + Command::Daemon => unreachable!("handled in main"), + Command::Status => { + let reply = daemon.status_request().send().promise.await?; + let s = reply.get()?.get_status()?; + + let fmt_secs = |s: f64| -> String { + if s < 60.0 { format!("{:.0}s", s) } + else if s < 3600.0 { format!("{:.0}m", s / 60.0) } + else { format!("{:.1}h", s / 3600.0) } + }; + + println!("uptime: {} pane: {} activity: {:?} pending: {}", + fmt_secs(s.get_uptime()), + s.get_claude_pane()?.to_str().unwrap_or("none"), + s.get_activity()?, + s.get_pending_count(), + ); + println!("idle timer: {}/{} ({})", + fmt_secs(s.get_since_activity()), + fmt_secs(s.get_idle_timeout()), + s.get_block_reason()?.to_str()?, + ); + println!("notify timer: {}/{}", + fmt_secs(s.get_since_activity()), + fmt_secs(s.get_notify_timeout()), + ); + println!("user: {} (last {}) activity: {:.1}%", + if s.get_user_present() { "present" } else { "away" }, + fmt_secs(s.get_since_user()), + s.get_activity_ewma() * 100.0, + ); + + let sleep = s.get_sleep_until(); + if sleep != 0.0 { + if sleep < 0.0 { + println!("sleep: indefinite"); + } else { + println!("sleep: until {sleep:.0}"); + } + } + if s.get_consolidating() { println!("consolidating"); } + if s.get_dreaming() { println!("dreaming"); } + } + Command::User { pane } => { + let pane = pane.as_deref().unwrap_or(""); + let mut req = daemon.user_request(); + req.get().set_pane(pane); + req.send().promise.await?; + } + Command::Response { pane } => { + let pane = pane.as_deref().unwrap_or(""); + let mut req = daemon.response_request(); + req.get().set_pane(pane); + req.send().promise.await?; + } + Command::Sleep { until } => { + let mut req = daemon.sleep_request(); + req.get().set_until(until.unwrap_or(0.0)); + req.send().promise.await?; + } + Command::Wake => { + daemon.wake_request().send().promise.await?; + } + Command::Quiet { seconds } => { + let mut req = daemon.quiet_request(); + req.get().set_seconds(seconds.unwrap_or(300)); + req.send().promise.await?; + } + Command::TestSend { message } => { + let msg = message.join(" "); + let pane = { + let reply = daemon.status_request().send().promise.await?; + let s = reply.get()?.get_status()?; + s.get_claude_pane()?.to_str()?.to_string() + }; + let ok = tmux::send_prompt(&pane, &msg); + println!("send_prompt(pane={}, ok={}): {}", pane, ok, msg); + return Ok(()); + } + Command::TestNudge => { + let reply = daemon.test_nudge_request().send().promise.await?; + let r = reply.get()?; + println!("sent={} message={}", r.get_sent(), r.get_message()?.to_str()?); + return Ok(()); + } + Command::Afk => { + daemon.afk_request().send().promise.await?; + println!("marked AFK"); + } + Command::SessionTimeout { seconds } => { + let mut req = daemon.session_timeout_request(); + req.get().set_seconds(seconds); + req.send().promise.await?; + println!("session timeout = {seconds}s"); + } + Command::IdleTimeout { seconds } => { + let mut req = daemon.idle_timeout_request(); + req.get().set_seconds(seconds); + req.send().promise.await?; + println!("idle timeout = {seconds}s"); + } + Command::NotifyTimeout { seconds } => { + let mut req = daemon.notify_timeout_request(); + req.get().set_seconds(seconds); + req.send().promise.await?; + println!("notify timeout = {seconds}s"); + } + Command::Consolidating => { + daemon.consolidating_request().send().promise.await?; + } + Command::Consolidated => { + daemon.consolidated_request().send().promise.await?; + } + Command::DreamStart => { + daemon.dream_start_request().send().promise.await?; + } + Command::DreamEnd => { + daemon.dream_end_request().send().promise.await?; + } + Command::Save => { + daemon.save_request().send().promise.await?; + println!("state saved"); + } + Command::Ewma { value } => { + let mut req = daemon.ewma_request(); + req.get().set_value(value.unwrap_or(-1.0)); + let reply = req.send().promise.await?; + let current = reply.get()?.get_current(); + println!("{:.1}%", current * 100.0); + } + Command::Debug => { + let reply = daemon.debug_request().send().promise.await?; + let json = reply.get()?.get_json()?.to_str()?; + if let Ok(v) = serde_json::from_str::(json) { + println!("{}", serde_json::to_string_pretty(&v).unwrap_or_else(|_| json.to_string())); + } else { + println!("{json}"); + } + } + Command::Stop => { + daemon.stop_request().send().promise.await?; + println!("stopping"); + } + Command::Notify { ntype, urgency, message } => { + let urgency = notify::parse_urgency(&urgency) + .ok_or_else(|| format!("invalid urgency: {urgency}"))?; + let message = message.join(" "); + if message.is_empty() { + return Err("missing message".into()); + } + + let mut req = daemon.notify_request(); + let mut n = req.get().init_notification(); + n.set_type(&ntype); + n.set_urgency(urgency); + n.set_message(&message); + n.set_timestamp(now()); + let reply = req.send().promise.await?; + if reply.get()?.get_interrupt() { + println!("interrupt"); + } else { + println!("queued"); + } + } + Command::Notifications { min_urgency } => { + let min: u8 = min_urgency + .as_deref() + .and_then(notify::parse_urgency) + .unwrap_or(255); + + let mut req = daemon.get_notifications_request(); + req.get().set_min_urgency(min); + let reply = req.send().promise.await?; + let list = reply.get()?.get_notifications()?; + + for n in list.iter() { + println!( + "[{}:{}] {}", + n.get_type()?.to_str()?, + notify::urgency_name(n.get_urgency()), + n.get_message()?.to_str()?, + ); + } + } + Command::NotifyTypes => { + let reply = daemon.get_types_request().send().promise.await?; + let list = reply.get()?.get_types()?; + + if list.is_empty() { + println!("no notification types registered"); + } else { + for t in list.iter() { + let threshold = if t.get_threshold() < 0 { + "inherit".to_string() + } else { + notify::urgency_name(t.get_threshold() as u8).to_string() + }; + println!( + "{}: count={} threshold={}", + t.get_name()?.to_str()?, + t.get_count(), + threshold, + ); + } + } + } + Command::NotifyThreshold { ntype, level } => { + let level = notify::parse_urgency(&level) + .ok_or_else(|| format!("invalid level: {level}"))?; + + let mut req = daemon.set_threshold_request(); + req.get().set_type(&ntype); + req.get().set_level(level); + req.send().promise.await?; + println!("{ntype} threshold={}", notify::urgency_name(level)); + } + Command::Irc { command, args } => { + module_command(&daemon, "irc", &command, &args).await?; + } + Command::Telegram { command, args } => { + module_command(&daemon, "telegram", &command, &args).await?; + } + } + + Ok(()) + }) + .await +} + +async fn module_command( + daemon: &daemon_capnp::daemon::Client, + module: &str, + command: &str, + args: &[String], +) -> Result<(), Box> { + let mut req = daemon.module_command_request(); + req.get().set_module(module); + req.get().set_command(command); + let mut args_builder = req.get().init_args(args.len() as u32); + for (i, a) in args.iter().enumerate() { + args_builder.set(i as u32, a); + } + let reply = req.send().promise.await?; + let result = reply.get()?.get_result()?.to_str()?; + if !result.is_empty() { + println!("{result}"); + } + Ok(()) +} + +// -- Server mode ---------------------------------------------------------- + +async fn server_main() -> Result<(), Box> { + env_logger::init(); + + let sock = sock_path(); + let _ = std::fs::remove_file(&sock); + + let pid = std::process::id(); + std::fs::write(pid_path(), pid.to_string()).ok(); + + + let state = Rc::new(RefCell::new(idle::State::new())); + state.borrow_mut().load(); + + info!("daemon started (pid={pid})"); + + tokio::task::LocalSet::new() + .run_until(async move { + // Subscribe to channel daemon notifications + let (notify_tx, mut notify_rx) = tokio::sync::mpsc::unbounded_channel::(); + { + let channel_rx = consciousness::thalamus::channels::subscribe_all(); + let tx = notify_tx.clone(); + std::thread::spawn(move || { + while let Ok(cn) = channel_rx.recv() { + let _ = tx.send(notify::Notification { + ntype: cn.channel, + urgency: cn.urgency, + message: cn.preview, + timestamp: consciousness::thalamus::now(), + }); + } + }); + } + + let listener = UnixListener::bind(&sock)?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions( + &sock, + std::fs::Permissions::from_mode(0o600), + ) + .ok(); + } + + let shutdown = async { + let mut sigterm = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("sigterm"); + let mut sigint = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()) + .expect("sigint"); + tokio::select! { + _ = sigterm.recv() => info!("SIGTERM"), + _ = sigint.recv() => info!("SIGINT"), + } + }; + tokio::pin!(shutdown); + + let mut tick_timer = tokio::time::interval(Duration::from_secs(30)); + tick_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + tokio::select! { + _ = &mut shutdown => break, + + // Drain module notifications into state + Some(notif) = notify_rx.recv() => { + state.borrow_mut().maybe_prompt_notification( + ¬if.ntype, notif.urgency, ¬if.message, + ); + state.borrow_mut().notifications.submit( + notif.ntype, + notif.urgency, + notif.message, + ); + } + + _ = tick_timer.tick() => { + if let Err(e) = state.borrow_mut().tick().await { + error!("tick: {e}"); + } + if !state.borrow().running { + break; + } + } + + result = listener.accept() => { + match result { + Ok((stream, _)) => { + let (reader, writer) = + tokio_util::compat::TokioAsyncReadCompatExt::compat(stream) + .split(); + let network = twoparty::VatNetwork::new( + futures::io::BufReader::new(reader), + futures::io::BufWriter::new(writer), + rpc_twoparty_capnp::Side::Server, + Default::default(), + ); + + let daemon_impl = rpc::DaemonImpl::new( + state.clone(), + ); + let client: daemon_capnp::daemon::Client = + capnp_rpc::new_client(daemon_impl); + + let rpc_system = RpcSystem::new( + Box::new(network), + Some(client.client), + ); + tokio::task::spawn_local(rpc_system); + } + Err(e) => error!("accept: {e}"), + } + } + } + } + + state.borrow().save(); + let _ = std::fs::remove_file(sock_path()); + let _ = std::fs::remove_file(pid_path()); + info!("daemon stopped"); + + Ok(()) + }) + .await +} + +// -- Entry point ---------------------------------------------------------- + +/// Run the daemon or client command. +/// Called from the main consciousness binary. +pub async fn run(command: Option) -> Result<(), Box> { + match command { + Some(Command::Daemon) => server_main().await, + Some(cmd) => client_main(cmd).await, + None => { + // Show help + Cli::parse_from(["consciousness-daemon", "--help"]); + Ok(()) + } + } +} diff --git a/src/mcp-server.rs b/src/mcp-server.rs new file mode 100644 index 0000000..a99474a --- /dev/null +++ b/src/mcp-server.rs @@ -0,0 +1,168 @@ +// mcp-server — MCP server for Claude Code integration +// +// Speaks JSON-RPC over stdio. Exposes memory tools and channel +// operations. Replaces the Python MCP bridge entirely. +// +// Protocol: https://modelcontextprotocol.io/specification + +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::io::{self, BufRead, Write}; + +// ── JSON-RPC types ────────────────────────────────────────────── + +#[derive(Deserialize)] +struct Request { + #[allow(dead_code)] + jsonrpc: String, + method: String, + #[serde(default)] + params: Value, + id: Value, +} + +#[derive(Serialize)] +struct Response { + jsonrpc: String, + result: Value, + id: Value, +} + +#[derive(Serialize)] +struct ErrorResponse { + jsonrpc: String, + error: Value, + id: Value, +} + +fn respond(id: Value, result: Value) { + let resp = Response { jsonrpc: "2.0".into(), result, id }; + let json = serde_json::to_string(&resp).unwrap(); + let mut stdout = io::stdout().lock(); + let _ = writeln!(stdout, "{json}"); + let _ = stdout.flush(); +} + +fn respond_error(id: Value, code: i64, message: &str) { + let resp = ErrorResponse { + jsonrpc: "2.0".into(), + error: json!({ "code": code, "message": message }), + id, + }; + let json = serde_json::to_string(&resp).unwrap(); + let mut stdout = io::stdout().lock(); + let _ = writeln!(stdout, "{json}"); + let _ = stdout.flush(); +} + +fn notify(method: &str, params: Value) { + let json = serde_json::to_string(&json!({ + "jsonrpc": "2.0", + "method": method, + "params": params, + })).unwrap(); + let mut stdout = io::stdout().lock(); + let _ = writeln!(stdout, "{json}"); + let _ = stdout.flush(); +} + +// ── Tool definitions ──────────────────────────────────────────── + +fn tool_definitions() -> Vec { + consciousness::agent::tools::tools().into_iter() + .map(|t| json!({ + "name": t.name, + "description": t.description, + "inputSchema": serde_json::from_str::(t.parameters_json).unwrap_or(json!({})), + })) + .collect() +} + +// ── Tool dispatch ─────────────────────────────────────────────── + +fn dispatch_tool(name: &str, args: &Value) -> Result { + let tools = consciousness::agent::tools::tools(); + let tool = tools.iter().find(|t| t.name == name); + let Some(tool) = tool else { + return Err(format!("unknown tool: {name}")); + }; + + // Run async handler on a blocking runtime + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| e.to_string())?; + let local = tokio::task::LocalSet::new(); + local.block_on(&rt, (tool.handler)(None, args.clone())) + .map_err(|e| e.to_string()) +} + +// ── Main loop ─────────────────────────────────────────────────── + +fn main() { + let stdin = io::stdin(); + let reader = stdin.lock(); + + for line in reader.lines() { + let line = match line { + Ok(l) if !l.is_empty() => l, + _ => continue, + }; + + let req: Request = match serde_json::from_str(&line) { + Ok(r) => r, + Err(_) => continue, + }; + + match req.method.as_str() { + "initialize" => { + respond(req.id, json!({ + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": "consciousness", + "version": "0.4.0" + } + })); + } + + "notifications/initialized" => { + // Client ack — no response needed + } + + "tools/list" => { + let tools = tool_definitions(); + respond(req.id, json!({ "tools": tools })); + } + + "tools/call" => { + let name = req.params.get("name") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let args = req.params.get("arguments") + .cloned() + .unwrap_or(json!({})); + + match dispatch_tool(name, &args) { + Ok(text) => { + respond(req.id, json!({ + "content": [{"type": "text", "text": text}] + })); + } + Err(e) => { + respond(req.id, json!({ + "content": [{"type": "text", "text": e}], + "isError": true + })); + } + } + } + + _ => { + respond_error(req.id, -32601, &format!("unknown method: {}", req.method)); + } + } + } +} diff --git a/src/memory-search.rs b/src/memory-search.rs new file mode 100644 index 0000000..c3412b5 --- /dev/null +++ b/src/memory-search.rs @@ -0,0 +1,222 @@ +// memory-search CLI — thin wrapper around consciousness::memory_search +// +// --hook: run hook logic (for debugging; poc-hook calls the library directly) +// surface/reflect: run agent, parse output, render memories to stdout +// no args: show seen set for current session + +use clap::{Parser, Subcommand}; +use std::fs; +use std::io::{self, Read}; +use std::process::Command; + +use consciousness_claude::hook as memory_search; + +fn stash_path() -> std::path::PathBuf { + consciousness::store::memory_dir().join("sessions/last-input.json") +} + +#[derive(Parser)] +#[command(name = "memory-search")] +struct Args { + /// Run hook logic (reads JSON from stdin or stash file) + #[arg(long)] + hook: bool, + + /// Session ID (overrides stash file; for multiple concurrent sessions) + #[arg(long)] + session: Option, + + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Cmd { + /// Run surface agent, parse output, render memories + Surface, + /// Run reflect agent, dump output + Reflect, +} + +fn resolve_session(session_arg: &Option) -> Option { + use memory_search::HookSession; + + if let Some(id) = session_arg { + return HookSession::from_id(id.clone()); + } + let input = fs::read_to_string(stash_path()).ok()?; + HookSession::from_json(&input) +} + +fn show_seen(session_arg: &Option) { + let Some(session) = resolve_session(session_arg) else { + eprintln!("No session state available (use --session ID)"); + return; + }; + + println!("Session: {}", session.session_id); + + if let Ok(cookie) = fs::read_to_string(&session.path("cookie")) { + println!("Cookie: {}", cookie.trim()); + } + + match fs::read_to_string(&session.path("compaction")) { + Ok(s) => { + let offset: u64 = s.trim().parse().unwrap_or(0); + let ts = consciousness::transcript::compaction_timestamp(&session.transcript_path, offset); + match ts { + Some(t) => println!("Last compaction: offset {} ({})", offset, t), + None => println!("Last compaction: offset {}", offset), + } + } + Err(_) => println!("Last compaction: none detected"), + } + + let pending = fs::read_dir(&session.path("chunks")).ok() + .map(|d| d.flatten().count()).unwrap_or(0); + if pending > 0 { + println!("Pending chunks: {}", pending); + } + + for (label, suffix) in [("Current seen set", ""), ("Previous seen set (pre-compaction)", "-prev")] { + let path = session.state_dir.join(format!("seen{}-{}", suffix, session.session_id)); + let content = fs::read_to_string(&path).unwrap_or_default(); + let lines: Vec<&str> = content.lines().filter(|s| !s.is_empty()).collect(); + if lines.is_empty() { continue; } + + println!("\n{} ({}):", label, lines.len()); + for line in &lines { println!(" {}", line); } + } +} + +fn run_agent_and_parse(agent: &str, session_arg: &Option) { + let session_id = session_arg.clone() + .or_else(|| std::env::var("CLAUDE_SESSION_ID").ok()) + .or_else(|| { + fs::read_to_string(stash_path()).ok() + .and_then(|s| memory_search::HookSession::from_json(&s)) + .map(|s| s.session_id) + }) + .unwrap_or_default(); + + if session_id.is_empty() { + eprintln!("No session ID available (use --session ID, set CLAUDE_SESSION_ID, or run --hook first)"); + std::process::exit(1); + } + + eprintln!("Running {} agent (session {})...", agent, &session_id[..session_id.floor_char_boundary(8.min(session_id.len()))]); + + let output = Command::new("poc-memory") + .args(["agent", "run", agent, "--count", "1", "--local"]) + .env("POC_SESSION_ID", &session_id) + .output(); + + let output = match output { + Ok(o) => o, + Err(e) => { + eprintln!("Failed to run agent: {}", e); + std::process::exit(1); + } + }; + + let result = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + if !stderr.is_empty() { + eprintln!("{}", stderr); + } + + // Extract the final response — after the last "=== RESPONSE ===" marker + let response = result.rsplit_once("=== RESPONSE ===") + .map(|(_, rest)| rest.trim()) + .unwrap_or(result.trim()); + + if agent == "reflect" { + // Reflect: find REFLECTION marker and dump what follows + if let Some(pos) = response.find("REFLECTION") { + let after = &response[pos + "REFLECTION".len()..]; + let text = after.trim(); + if !text.is_empty() { + println!("{}", text); + } + } else if response.contains("NO OUTPUT") { + println!("(no reflection)"); + } else { + eprintln!("Unexpected output format"); + println!("{}", response); + } + return; + } + + // Surface: parse NEW RELEVANT MEMORIES, render them + let tail_lines: Vec<&str> = response.lines().rev() + .filter(|l| !l.trim().is_empty()).take(8).collect(); + let has_new = tail_lines.iter().any(|l| l.starts_with("NEW RELEVANT MEMORIES:")); + let has_none = tail_lines.iter().any(|l| l.starts_with("NO NEW RELEVANT MEMORIES")); + + if has_new { + let after_marker = response.rsplit_once("NEW RELEVANT MEMORIES:") + .map(|(_, rest)| rest).unwrap_or(""); + let keys: Vec = after_marker.lines() + .map(|l| l.trim().trim_start_matches("- ").trim().to_string()) + .filter(|l| !l.is_empty() && !l.starts_with("```")).collect(); + + if keys.is_empty() { + println!("(no memories found)"); + return; + } + + let Ok(store) = consciousness::store::Store::load() else { + eprintln!("Failed to load store"); + return; + }; + + for key in &keys { + if let Some(content) = consciousness::cli::node::render_node(&store, key) { + if !content.trim().is_empty() { + println!("--- {} (surfaced) ---", key); + print!("{}", content); + println!(); + } + } else { + eprintln!(" key not found: {}", key); + } + } + } else if has_none { + println!("(no new relevant memories)"); + } else { + eprintln!("Unexpected output format"); + print!("{}", response); + } +} + +fn main() { + let args = Args::parse(); + + if let Some(cmd) = args.command { + match cmd { + Cmd::Surface => run_agent_and_parse("surface", &args.session), + Cmd::Reflect => run_agent_and_parse("reflect", &args.session), + } + return; + } + + if args.hook { + // Read from stdin if piped, otherwise from stash + let input = { + let mut buf = String::new(); + io::stdin().read_to_string(&mut buf).ok(); + if buf.trim().is_empty() { + fs::read_to_string(stash_path()).unwrap_or_default() + } else { + let _ = fs::create_dir_all(stash_path().parent().unwrap()); + let _ = fs::write(stash_path(), &buf); + buf + } + }; + + let output = memory_search::run_hook(&input); + print!("{}", output); + } else { + show_seen(&args.session) + } +} diff --git a/src/poc-daemon.rs b/src/poc-daemon.rs new file mode 100644 index 0000000..b8ac54e --- /dev/null +++ b/src/poc-daemon.rs @@ -0,0 +1,14 @@ +// poc-daemon — backward-compatible entry point +// +// Delegates to the claude module in the main crate. +// The daemon is now part of the consciousness binary but this +// entry point is kept for compatibility with existing scripts. + +use clap::Parser; +use consciousness_claude::{Cli, run}; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + let cli = Cli::parse(); + run(cli.command).await +} diff --git a/src/poc-hook.rs b/src/poc-hook.rs new file mode 100644 index 0000000..b5c3291 --- /dev/null +++ b/src/poc-hook.rs @@ -0,0 +1,272 @@ +// Unified Claude Code hook. +// +// Single binary handling all hook events: +// UserPromptSubmit — signal daemon, check notifications, check context +// PostToolUse — check context (rate-limited) +// Stop — signal daemon response +// +// Replaces: record-user-message-time.sh, check-notifications.sh, +// check-context-usage.sh, notify-done.sh, context-check + +use serde_json::Value; +use std::fs; +use std::io::{self, Read}; +use std::path::PathBuf; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +use consciousness::store; +use consciousness_claude::hook as memory_search; + +const CONTEXT_THRESHOLD: u64 = 900_000; +const RATE_LIMIT_SECS: u64 = 60; +const SOCK_PATH: &str = ".consciousness/daemon.sock"; +/// How many bytes of new transcript before triggering an observation run. +/// Override with POC_OBSERVATION_THRESHOLD env var. +/// Default: 20KB ≈ 5K tokens. The observation agent's chunk_size (in .agent +/// file) controls how much context it actually reads. +fn observation_threshold() -> u64 { + std::env::var("POC_OBSERVATION_THRESHOLD") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(20_000) +} + +fn now_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} + +fn home() -> PathBuf { + PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/root".into())) +} + +fn daemon_cmd(args: &[&str]) { + Command::new("poc-daemon") + .args(args) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .ok(); +} + +fn daemon_available() -> bool { + home().join(SOCK_PATH).exists() +} + +fn signal_user() { + let pane = std::env::var("TMUX_PANE").unwrap_or_default(); + if pane.is_empty() { + daemon_cmd(&["user"]); + } else { + daemon_cmd(&["user", &pane]); + } +} + +fn signal_response() { + daemon_cmd(&["response"]); +} + +fn check_notifications() { + if !daemon_available() { + return; + } + let output = Command::new("poc-daemon") + .arg("notifications") + .output() + .ok(); + if let Some(out) = output { + let text = String::from_utf8_lossy(&out.stdout); + if !text.trim().is_empty() { + println!("You have pending notifications:"); + print!("{text}"); + } + } +} + +/// Check for stale agent processes in a state dir. +/// Cleans up pid files for dead processes and kills timed-out ones. +/// Also detects PID reuse by checking if the process is actually a +/// claude/poc-memory process (reads /proc/pid/cmdline). +fn reap_agent_pids(state_dir: &std::path::Path, timeout_secs: u64) { + let Ok(entries) = fs::read_dir(state_dir) else { return }; + for entry in entries.flatten() { + let name = entry.file_name(); + let name_str = name.to_string_lossy(); + let Some(pid_str) = name_str.strip_prefix("pid-") else { continue }; + let Ok(pid) = pid_str.parse::() else { continue }; + + // Check if the process is actually alive + if unsafe { libc::kill(pid, 0) } != 0 { + fs::remove_file(entry.path()).ok(); + continue; + } + + // Check if the PID still belongs to a claude/poc-memory process. + // PID reuse by an unrelated process would otherwise block the + // agent from being re-launched. + let is_ours = fs::read_to_string(format!("/proc/{}/cmdline", pid)) + .map(|cmd| cmd.contains("claude") || cmd.contains("poc-memory")) + .unwrap_or(false); + if !is_ours { + fs::remove_file(entry.path()).ok(); + continue; + } + + if timeout_secs > 0 { + if let Ok(meta) = entry.metadata() { + if let Ok(modified) = meta.modified() { + if modified.elapsed().unwrap_or_default().as_secs() > timeout_secs { + unsafe { libc::kill(pid, libc::SIGTERM); } + fs::remove_file(entry.path()).ok(); + } + } + } + } + } +} + +/// Reap all agent output directories. +fn reap_all_agents() { + let agent_output = store::memory_dir().join("agent-output"); + if let Ok(entries) = fs::read_dir(&agent_output) { + for entry in entries.flatten() { + if entry.file_type().map_or(false, |t| t.is_dir()) { + reap_agent_pids(&entry.path(), 600); // 10 min timeout + } + } + } +} + +/// Check if enough new conversation has accumulated to trigger an observation run. +fn maybe_trigger_observation(transcript: &PathBuf) { + let cursor_file = store::memory_dir().join("observation-cursor"); + + let last_pos: u64 = fs::read_to_string(&cursor_file) + .ok() + .and_then(|s| s.trim().parse().ok()) + .unwrap_or(0); + + let current_size = transcript.metadata() + .map(|m| m.len()) + .unwrap_or(0); + + if current_size > last_pos + observation_threshold() { + // Queue observation via daemon RPC + let _ = Command::new("poc-memory") + .args(["agent", "daemon", "run", "observation", "1"]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn(); + + eprintln!("[poc-hook] observation triggered ({} new bytes)", current_size - last_pos); + + // Update cursor to current position + let _ = fs::write(&cursor_file, current_size.to_string()); + } +} + +fn check_context(transcript: &PathBuf, rate_limit: bool) { + if rate_limit { + let rate_file = dirs::home_dir().unwrap_or_default().join(".consciousness/cache/context-check-last"); + if let Ok(s) = fs::read_to_string(&rate_file) { + if let Ok(last) = s.trim().parse::() { + if now_secs() - last < RATE_LIMIT_SECS { + return; + } + } + } + let _ = fs::write(&rate_file, now_secs().to_string()); + } + + if !transcript.exists() { + return; + } + + let content = match fs::read_to_string(transcript) { + Ok(c) => c, + Err(_) => return, + }; + + let mut usage: u64 = 0; + for line in content.lines().rev().take(500) { + if !line.contains("cache_read_input_tokens") { + continue; + } + if let Ok(v) = serde_json::from_str::(line) { + let u = &v["message"]["usage"]; + let input_tokens = u["input_tokens"].as_u64().unwrap_or(0); + let cache_creation = u["cache_creation_input_tokens"].as_u64().unwrap_or(0); + let cache_read = u["cache_read_input_tokens"].as_u64().unwrap_or(0); + usage = input_tokens + cache_creation + cache_read; + break; + } + } + + if usage > CONTEXT_THRESHOLD { + print!( + "\ +CONTEXT WARNING: Compaction approaching ({usage} tokens). Write a journal entry NOW. + +Use `poc-memory journal write \"entry text\"` to save a dated entry covering: +- What you're working on and current state (done / in progress / blocked) +- Key things learned this session (patterns, debugging insights) +- Anything half-finished that needs pickup + +Keep it narrative, not a task log." + ); + } +} + +fn main() { + let mut input = String::new(); + io::stdin().read_to_string(&mut input).ok(); + + let hook: Value = match serde_json::from_str(&input) { + Ok(v) => v, + Err(_) => return, + }; + + let hook_type = hook["hook_event_name"].as_str().unwrap_or("unknown"); + let transcript = hook["transcript_path"] + .as_str() + .filter(|p| !p.is_empty()) + .map(PathBuf::from); + + // Daemon agent calls set POC_AGENT=1 — skip all signaling. + // Without this, the daemon's claude -p calls trigger hooks that + // signal "user active", keeping the idle timer permanently reset. + if std::env::var("POC_AGENT").is_ok() { + return; + } + + match hook_type { + "UserPromptSubmit" => { + signal_user(); + check_notifications(); + reap_all_agents(); + print!("{}", memory_search::run_hook(&input)); + + if let Some(ref t) = transcript { + check_context(t, false); + maybe_trigger_observation(t); + } + } + "PostToolUse" => { + print!("{}", memory_search::run_hook(&input)); + + if let Some(ref t) = transcript { + check_context(t, true); + } + } + "Stop" => { + let stop_hook_active = hook["stop_hook_active"].as_bool().unwrap_or(false); + if !stop_hook_active { + signal_response(); + } + } + _ => {} + } +} diff --git a/src/rpc.rs b/src/rpc.rs new file mode 100644 index 0000000..3c7a9cb --- /dev/null +++ b/src/rpc.rs @@ -0,0 +1,381 @@ +// Cap'n Proto RPC server implementation. +// +// Bridges the capnp-generated Daemon interface to the idle::State, +// notify::NotifyState, and module state. All state is owned by +// RefCells on the LocalSet — no Send/Sync needed. + +use super::idle; +use consciousness::thalamus::{daemon_capnp, notify}; +use daemon_capnp::daemon; +use std::cell::RefCell; +use std::rc::Rc; +use log::info; + +pub struct DaemonImpl { + state: Rc>, +} + +impl DaemonImpl { + pub fn new(state: Rc>) -> Self { + Self { state } + } +} + +impl daemon::Server for DaemonImpl { + fn user( + self: Rc, + params: daemon::UserParams, + _results: daemon::UserResults, + ) -> impl std::future::Future> { + let pane = pry!(pry!(pry!(params.get()).get_pane()).to_str()).to_string(); + self.state.borrow_mut().handle_user(&pane); + std::future::ready(Ok(())) + } + + fn response( + self: Rc, + params: daemon::ResponseParams, + _results: daemon::ResponseResults, + ) -> impl std::future::Future> { + let pane = pry!(pry!(pry!(params.get()).get_pane()).to_str()).to_string(); + self.state.borrow_mut().handle_response(&pane); + std::future::ready(Ok(())) + } + + fn sleep( + self: Rc, + params: daemon::SleepParams, + _results: daemon::SleepResults, + ) -> impl std::future::Future> { + let until = pry!(params.get()).get_until(); + self.state.borrow_mut().handle_sleep(until); + std::future::ready(Ok(())) + } + + fn wake( + self: Rc, + _params: daemon::WakeParams, + _results: daemon::WakeResults, + ) -> impl std::future::Future> { + self.state.borrow_mut().handle_wake(); + std::future::ready(Ok(())) + } + + fn quiet( + self: Rc, + params: daemon::QuietParams, + _results: daemon::QuietResults, + ) -> impl std::future::Future> { + let secs = pry!(params.get()).get_seconds(); + self.state.borrow_mut().handle_quiet(secs); + std::future::ready(Ok(())) + } + + fn consolidating( + self: Rc, + _params: daemon::ConsolidatingParams, + _results: daemon::ConsolidatingResults, + ) -> impl std::future::Future> { + self.state.borrow_mut().consolidating = true; + info!("consolidation started"); + std::future::ready(Ok(())) + } + + fn consolidated( + self: Rc, + _params: daemon::ConsolidatedParams, + _results: daemon::ConsolidatedResults, + ) -> impl std::future::Future> { + self.state.borrow_mut().consolidating = false; + info!("consolidation ended"); + std::future::ready(Ok(())) + } + + fn dream_start( + self: Rc, + _params: daemon::DreamStartParams, + _results: daemon::DreamStartResults, + ) -> impl std::future::Future> { + let mut s = self.state.borrow_mut(); + s.dreaming = true; + s.dream_start = consciousness::thalamus::now(); + info!("dream started"); + std::future::ready(Ok(())) + } + + fn dream_end( + self: Rc, + _params: daemon::DreamEndParams, + _results: daemon::DreamEndResults, + ) -> impl std::future::Future> { + let mut s = self.state.borrow_mut(); + s.dreaming = false; + s.dream_start = 0.0; + info!("dream ended"); + std::future::ready(Ok(())) + } + + fn afk( + self: Rc, + _params: daemon::AfkParams, + _results: daemon::AfkResults, + ) -> impl std::future::Future> { + self.state.borrow_mut().handle_afk(); + std::future::ready(Ok(())) + } + + fn test_nudge( + self: Rc, + _params: daemon::TestNudgeParams, + mut results: daemon::TestNudgeResults, + ) -> impl std::future::Future> { + let mut state = self.state.borrow_mut(); + let ctx = state.build_context(true); + let extra = if ctx.is_empty() { + String::new() + } else { + format!("\n{ctx}") + }; + let msg = format!( + "This is your time (User AFK, test nudge). \ + Let your feelings guide your thinking.{extra}" + ); + let ok = state.send(&msg); + results.get().set_sent(ok); + results.get().set_message(&msg); + std::future::ready(Ok(())) + } + + fn session_timeout( + self: Rc, + params: daemon::SessionTimeoutParams, + _results: daemon::SessionTimeoutResults, + ) -> impl std::future::Future> { + let secs = pry!(params.get()).get_seconds(); + self.state.borrow_mut().handle_session_timeout(secs); + std::future::ready(Ok(())) + } + + fn idle_timeout( + self: Rc, + params: daemon::IdleTimeoutParams, + _results: daemon::IdleTimeoutResults, + ) -> impl std::future::Future> { + let secs = pry!(params.get()).get_seconds(); + self.state.borrow_mut().handle_idle_timeout(secs); + std::future::ready(Ok(())) + } + + fn notify_timeout( + self: Rc, + params: daemon::NotifyTimeoutParams, + _results: daemon::NotifyTimeoutResults, + ) -> impl std::future::Future> { + let secs = pry!(params.get()).get_seconds(); + self.state.borrow_mut().handle_notify_timeout(secs); + std::future::ready(Ok(())) + } + + fn save( + self: Rc, + _params: daemon::SaveParams, + _results: daemon::SaveResults, + ) -> impl std::future::Future> { + self.state.borrow().save(); + info!("state saved"); + std::future::ready(Ok(())) + } + + fn debug( + self: Rc, + _params: daemon::DebugParams, + mut results: daemon::DebugResults, + ) -> impl std::future::Future> { + let json = self.state.borrow().debug_json(); + results.get().set_json(&json); + std::future::ready(Ok(())) + } + + fn ewma( + self: Rc, + params: daemon::EwmaParams, + mut results: daemon::EwmaResults, + ) -> impl std::future::Future> { + let value = pry!(params.get()).get_value(); + let current = self.state.borrow_mut().handle_ewma(value); + results.get().set_current(current); + std::future::ready(Ok(())) + } + + fn stop( + self: Rc, + _params: daemon::StopParams, + _results: daemon::StopResults, + ) -> impl std::future::Future> { + self.state.borrow_mut().running = false; + info!("stopping"); + std::future::ready(Ok(())) + } + + fn status( + self: Rc, + _params: daemon::StatusParams, + mut results: daemon::StatusResults, + ) -> impl std::future::Future> { + let s = self.state.borrow(); + let mut status = results.get().init_status(); + + status.set_last_user_msg(s.last_user_msg); + status.set_last_response(s.last_response); + if let Some(ref pane) = s.claude_pane { + status.set_claude_pane(pane); + } + status.set_sleep_until(match s.sleep_until { + None => 0.0, + Some(0.0) => -1.0, + Some(t) => t, + }); + status.set_quiet_until(s.quiet_until); + status.set_consolidating(s.consolidating); + status.set_dreaming(s.dreaming); + status.set_fired(s.fired); + status.set_user_present(s.user_present()); + status.set_uptime(consciousness::thalamus::now() - s.start_time); + status.set_activity(match s.notifications.activity { + notify::Activity::Idle => daemon_capnp::Activity::Idle, + notify::Activity::Focused => daemon_capnp::Activity::Focused, + notify::Activity::Sleeping => daemon_capnp::Activity::Sleeping, + }); + status.set_pending_count(s.notifications.pending.len() as u32); + status.set_idle_timeout(s.idle_timeout); + status.set_notify_timeout(s.notify_timeout); + status.set_since_activity(s.since_activity()); + status.set_since_user(consciousness::thalamus::now() - s.last_user_msg); + status.set_block_reason(s.block_reason()); + status.set_activity_ewma(s.activity_ewma); + + std::future::ready(Ok(())) + } + + fn notify( + self: Rc, + params: daemon::NotifyParams, + mut results: daemon::NotifyResults, + ) -> impl std::future::Future> { + let params = pry!(params.get()); + let notif = pry!(params.get_notification()); + let ntype = pry!(pry!(notif.get_type()).to_str()).to_string(); + let urgency = notif.get_urgency(); + let message = pry!(pry!(notif.get_message()).to_str()).to_string(); + + let interrupt = self + .state + .borrow_mut() + .notifications + .submit(ntype, urgency, message); + results.get().set_interrupt(interrupt); + std::future::ready(Ok(())) + } + + fn get_notifications( + self: Rc, + params: daemon::GetNotificationsParams, + mut results: daemon::GetNotificationsResults, + ) -> impl std::future::Future> { + let min_urgency = pry!(params.get()).get_min_urgency(); + let mut s = self.state.borrow_mut(); + + // Ingest legacy files first + s.notifications.ingest_legacy_files(); + + let pending = if min_urgency == 255 { + s.notifications.drain_deliverable() + } else { + s.notifications.drain(min_urgency) + }; + + let mut list = results.get().init_notifications(pending.len() as u32); + for (i, n) in pending.iter().enumerate() { + let mut entry = list.reborrow().get(i as u32); + entry.set_type(&n.ntype); + entry.set_urgency(n.urgency); + entry.set_message(&n.message); + entry.set_timestamp(n.timestamp); + } + + std::future::ready(Ok(())) + } + + fn get_types( + self: Rc, + _params: daemon::GetTypesParams, + mut results: daemon::GetTypesResults, + ) -> impl std::future::Future> { + let s = self.state.borrow(); + let types = &s.notifications.types; + + let mut list = results.get().init_types(types.len() as u32); + for (i, (name, info)) in types.iter().enumerate() { + let mut entry = list.reborrow().get(i as u32); + entry.set_name(name); + entry.set_count(info.count); + entry.set_first_seen(info.first_seen); + entry.set_last_seen(info.last_seen); + entry.set_threshold(info.threshold.map_or(-1, |t| t as i8)); + } + + std::future::ready(Ok(())) + } + + fn set_threshold( + self: Rc, + params: daemon::SetThresholdParams, + _results: daemon::SetThresholdResults, + ) -> impl std::future::Future> { + let params = pry!(params.get()); + let ntype = pry!(pry!(params.get_type()).to_str()).to_string(); + let level = params.get_level(); + + self.state + .borrow_mut() + .notifications + .set_threshold(&ntype, level); + std::future::ready(Ok(())) + } + + fn module_command( + self: Rc, + params: daemon::ModuleCommandParams, + mut results: daemon::ModuleCommandResults, + ) -> impl std::future::Future> { + let params = pry!(params.get()); + let module = pry!(pry!(params.get_module()).to_str()).to_string(); + let _command = pry!(pry!(params.get_command()).to_str()).to_string(); + let args_reader = pry!(params.get_args()); + let mut args = Vec::new(); + for i in 0..args_reader.len() { + args.push(pry!(pry!(args_reader.get(i)).to_str()).to_string()); + } + + match module.as_str() { + // TODO: route module commands through named channel system + _ => { + results + .get() + .set_result(&format!("unknown module: {module}")); + std::future::ready(Ok(())) + } + } + } +} + +/// Helper macro — same as capnp's pry! but available here. +macro_rules! pry { + ($e:expr) => { + match $e { + Ok(v) => v, + Err(e) => return std::future::ready(Err(e.into())), + } + }; +} +use pry; diff --git a/src/tmux.rs b/src/tmux.rs new file mode 100644 index 0000000..ea920d6 --- /dev/null +++ b/src/tmux.rs @@ -0,0 +1,54 @@ +// Tmux interaction: pane detection and prompt injection. + +use std::process::Command; +use std::thread; +use std::time::Duration; +use log::info; + +/// Find Claude Code's tmux pane by scanning for the "claude" process. +pub fn find_claude_pane() -> Option { + let out = Command::new("tmux") + .args([ + "list-panes", + "-a", + "-F", + "#{session_name}:#{window_index}.#{pane_index}\t#{pane_current_command}", + ]) + .output() + .ok()?; + + let stdout = String::from_utf8_lossy(&out.stdout); + for line in stdout.lines() { + if let Some((pane, cmd)) = line.split_once('\t') { + if cmd == "claude" { + return Some(pane.to_string()); + } + } + } + None +} + +/// Send a prompt to a tmux pane. Returns true on success. +/// +/// Types the message literally then presses Enter. +pub fn send_prompt(pane: &str, msg: &str) -> bool { + let preview: String = msg.chars().take(100).collect(); + info!("SEND [{pane}]: {preview}..."); + + // Type the message literally (flatten newlines — they'd submit the input early) + let flat: String = msg.chars().map(|c| if c == '\n' { ' ' } else { c }).collect(); + let ok = Command::new("tmux") + .args(["send-keys", "-t", pane, "-l", &flat]) + .output() + .is_ok(); + if !ok { + return false; + } + thread::sleep(Duration::from_millis(500)); + + // Submit + Command::new("tmux") + .args(["send-keys", "-t", pane, "Enter"]) + .output() + .is_ok() +}