diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a4ce60..371e161d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,3 +33,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - ZEPH_SQLITE_PATH env var override for database location - Provider factory in main.rs selecting Ollama or Claude from config - Memory integration into Agent with optional SqliteStore +- Channel trait abstraction for agent I/O (recv, send, send_typing) +- CliChannel implementation reading stdin/stdout via tokio::task::spawn_blocking +- TelegramChannel adapter using teloxide with mpsc-based message routing +- Telegram user whitelist via `telegram.allowed_users` config +- ZEPH_TELEGRAM_TOKEN env var for Telegram bot activation +- Bot commands: /start (welcome), /reset, /skills forwarded as ChannelMessage +- AnyChannel enum dispatch for runtime channel selection +- zeph-channels crate with teloxide 0.17 dependency +- TelegramConfig in config.rs with TOML and env var support + +### Changed + +- Agent is now generic over both LlmProvider and Channel (`Agent`) +- Agent::new() accepts a Channel parameter instead of reading stdin directly +- Agent::run() uses channel.recv()/send() instead of direct I/O +- Agent calls channel.send_typing() before each LLM request diff --git a/Cargo.lock b/Cargo.lock index 9305648a..b79685d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,44 @@ 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 = "anyhow" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -57,6 +89,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.22.1" @@ -93,6 +147,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -112,15 +172,69 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -146,6 +260,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -201,6 +325,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.10" @@ -212,6 +371,37 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -241,6 +431,22 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dptree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db96968fcf52fe063a98c75df1d1f2b1fba304e7ae29b72fdc81c1165b7e2fd0" +dependencies = [ + "colored", + "futures", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -271,6 +477,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erasable" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437cfb75878119ed8265685c41a115724eae43fb7cc5a0bf0e4ecc3b803af1c4" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "errno" version = "0.3.14" @@ -362,6 +578,27 @@ 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 = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -406,6 +643,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -424,8 +672,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -451,8 +701,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -462,9 +714,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -479,13 +733,19 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.5" @@ -669,6 +929,30 @@ dependencies = [ "windows-registry", ] +[[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 = "icu_collections" version = "2.1.1" @@ -750,6 +1034,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -771,6 +1061,36 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -779,6 +1099,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -797,12 +1119,53 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[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.85" @@ -893,6 +1256,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "md-5" version = "0.10.6" @@ -915,6 +1284,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "mio" version = "1.1.1" @@ -935,10 +1314,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -963,11 +1342,17 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + [[package]] name = "num-integer" version = "0.1.46" @@ -998,6 +1383,15 @@ dependencies = [ "libm", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "ollama-rs" version = "0.3.3" @@ -1006,12 +1400,12 @@ checksum = "db28711700d24a94bf78ce6a9df3bf55dc07c1bf68376574706ce2653ee3f5fc" dependencies = [ "async-stream", "log", - "reqwest", - "schemars", + "reqwest 0.12.28", + "schemars 1.2.1", "serde", "serde_json", "static_assertions", - "thiserror", + "thiserror 2.0.18", "url", ] @@ -1053,6 +1447,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -1109,6 +1509,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1157,6 +1577,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1166,6 +1592,27 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1175,6 +1622,72 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.44" @@ -1197,8 +1710,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "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 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1208,7 +1731,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1220,6 +1753,24 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rc-box" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897fecc9fac6febd4408f9e935e86df739b0023b625e610e0357535b9c8adad0" +dependencies = [ + "erasable", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1266,19 +1817,17 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-core", - "h2", + "futures-util", "http", "http-body", "http-body-util", "hyper", - "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", - "mime", + "mime_guess", "native-tls", "percent-encoding", "pin-project-lite", @@ -1289,6 +1838,48 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1298,6 +1889,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.14" @@ -1325,13 +1925,19 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.1.3" @@ -1351,6 +1957,7 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "aws-lc-rs", "once_cell", "rustls-pki-types", "rustls-webpki", @@ -1358,21 +1965,62 @@ dependencies = [ "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 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1390,13 +2038,34 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[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.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ - "windows-sys 0.61.2", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] @@ -1437,7 +2106,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1500,7 +2182,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "memchr", "serde", @@ -1529,13 +2211,44 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_yml" version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "libyml", "memchr", @@ -1598,7 +2311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1676,7 +2389,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.13.0", "log", "memchr", "once_cell", @@ -1685,7 +2398,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -1759,7 +2472,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -1767,7 +2480,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -1797,14 +2510,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -1828,7 +2541,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.18", "tracing", "url", ] @@ -1839,6 +2552,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1856,6 +2582,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -1900,7 +2632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -1914,6 +2646,88 @@ dependencies = [ "libc", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "takecell" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" + +[[package]] +name = "teloxide" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84992abeed3ae42e8401b25d266d12bcba1def0abe59d22f6b9781167545f71e" +dependencies = [ + "aquamarine", + "bytes", + "derive_more", + "dptree", + "either", + "futures", + "log", + "mime", + "pin-project", + "serde", + "serde_json", + "teloxide-core", + "teloxide-macros", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "teloxide-core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7a34ca8e971fa892e633858c07547fe138ef4a02e4a4eaa1d35e517d6e0bc4" +dependencies = [ + "bitflags", + "bytes", + "chrono", + "derive_more", + "either", + "futures", + "log", + "mime", + "once_cell", + "pin-project", + "rc-box", + "reqwest 0.12.28", + "rgb", + "serde", + "serde_json", + "serde_with", + "stacker", + "take_mut", + "takecell", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "url", + "uuid", +] + +[[package]] +name = "teloxide-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300fadcaf0c182f19b5ca10bf23a45dc9a48925f00c704405fd90ee2c03942f9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tempfile" version = "3.24.0" @@ -1927,13 +2741,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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", + "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", ] [[package]] @@ -1956,6 +2790,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "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 = "tinystr" version = "0.8.2" @@ -2059,7 +2924,7 @@ version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde_core", "serde_spanned", "toml_datetime", @@ -2207,6 +3072,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -2234,6 +3105,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -2250,6 +3127,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2258,6 +3136,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2276,6 +3165,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[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" @@ -2365,6 +3264,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -2375,6 +3287,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.6.1" @@ -2385,6 +3316,50 @@ dependencies = [ "wasite", ] +[[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 = "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", +] + +[[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", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -2420,6 +3395,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2438,6 +3422,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -2456,6 +3449,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2504,6 +3512,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2522,6 +3536,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2540,6 +3560,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2570,6 +3596,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2588,6 +3620,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2606,6 +3644,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2624,6 +3668,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2691,12 +3741,24 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "zeph-channels", "zeph-core", "zeph-llm", "zeph-memory", "zeph-skills", ] +[[package]] +name = "zeph-channels" +version = "0.1.0" +dependencies = [ + "anyhow", + "teloxide", + "tokio", + "tracing", + "zeph-core", +] + [[package]] name = "zeph-core" version = "0.1.0" @@ -2717,7 +3779,7 @@ version = "0.1.0" dependencies = [ "anyhow", "ollama-rs", - "reqwest", + "reqwest 0.13.1", "serde", "serde_json", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 8bd12826..ca801b02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,12 @@ repository = "https://github.com/bug-ops/zeph" [workspace.dependencies] anyhow = "1.0" ollama-rs = "0.3" -reqwest = { version = "0.12", features = ["json"] } +reqwest = { version = "0.13", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yml = "0.0" sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] } +teloxide = { version = "0.17", features = ["macros"] } thiserror = "2.0" tokio = { version = "1", features = ["full"] } toml = "0.9" @@ -39,6 +40,7 @@ anyhow.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true +zeph-channels = { path = "crates/zeph-channels" } zeph-core = { path = "crates/zeph-core" } zeph-llm = { path = "crates/zeph-llm" } zeph-memory = { path = "crates/zeph-memory" } diff --git a/crates/zeph-channels/Cargo.toml b/crates/zeph-channels/Cargo.toml new file mode 100644 index 00000000..c93d30c5 --- /dev/null +++ b/crates/zeph-channels/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "zeph-channels" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow.workspace = true +teloxide.workspace = true +tokio.workspace = true +tracing.workspace = true +zeph-core = { path = "../zeph-core" } + +[lints] +workspace = true diff --git a/crates/zeph-channels/src/lib.rs b/crates/zeph-channels/src/lib.rs new file mode 100644 index 00000000..2fb1e64d --- /dev/null +++ b/crates/zeph-channels/src/lib.rs @@ -0,0 +1,3 @@ +//! Channel implementations for the Zeph agent. + +pub mod telegram; diff --git a/crates/zeph-channels/src/telegram.rs b/crates/zeph-channels/src/telegram.rs new file mode 100644 index 00000000..ae799a2c --- /dev/null +++ b/crates/zeph-channels/src/telegram.rs @@ -0,0 +1,178 @@ +use teloxide::prelude::*; +use teloxide::types::ChatAction; +use tokio::sync::mpsc; +use zeph_core::channel::{Channel, ChannelMessage}; + +const MAX_MESSAGE_LEN: usize = 4096; + +/// Telegram channel adapter using teloxide. +#[derive(Debug)] +pub struct TelegramChannel { + bot: Bot, + chat_id: Option, + rx: mpsc::Receiver, + allowed_users: Vec, +} + +#[derive(Debug)] +struct IncomingMessage { + chat_id: ChatId, + text: String, +} + +impl TelegramChannel { + #[must_use] + pub fn new(token: String, allowed_users: Vec) -> Self { + let bot = Bot::new(token); + let (_, rx) = mpsc::channel(64); + Self { + bot, + chat_id: None, + rx, + allowed_users, + } + } + + /// Spawn the teloxide update listener and return `self` ready for use. + /// + /// # Errors + /// + /// Returns an error if the bot cannot be initialized. + pub fn start(mut self) -> anyhow::Result { + let (tx, rx) = mpsc::channel::(64); + self.rx = rx; + + let bot = self.bot.clone(); + let allowed = self.allowed_users.clone(); + + tokio::spawn(async move { + let handler = Update::filter_message().endpoint( + move |msg: Message, _bot: Bot| { + let tx = tx.clone(); + let allowed = allowed.clone(); + async move { + let username = msg + .from + .as_ref() + .and_then(|u| u.username.clone()); + + if !allowed.is_empty() { + let is_allowed = username + .as_deref() + .is_some_and(|u| allowed.iter().any(|a| a == u)); + if !is_allowed { + tracing::warn!( + "rejected message from unauthorized user: {:?}", + username + ); + return respond(()); + } + } + + let Some(text) = msg.text() else { + return respond(()); + }; + + let _ = tx + .send(IncomingMessage { + chat_id: msg.chat.id, + text: text.to_string(), + }) + .await; + + respond(()) + } + }, + ); + + Dispatcher::builder(bot, handler) + .enable_ctrlc_handler() + .build() + .dispatch() + .await; + }); + + tracing::info!("telegram bot listener started"); + Ok(self) + } + + fn is_command(text: &str) -> Option<&str> { + let cmd = text.split_whitespace().next()?; + if cmd.starts_with('/') { Some(cmd) } else { None } + } +} + +impl Channel for TelegramChannel { + async fn recv(&mut self) -> anyhow::Result> { + loop { + let Some(incoming) = self.rx.recv().await else { + return Ok(None); + }; + + self.chat_id = Some(incoming.chat_id); + + if let Some(cmd) = Self::is_command(&incoming.text) { + match cmd { + "/start" => { + self.send("Welcome to Zeph! Send me a message to get started.") + .await?; + continue; + } + "/reset" => { + return Ok(Some(ChannelMessage { + text: "/reset".to_string(), + })); + } + "/skills" => { + return Ok(Some(ChannelMessage { + text: "/skills".to_string(), + })); + } + _ => {} + } + } + + return Ok(Some(ChannelMessage { + text: incoming.text, + })); + } + } + + async fn send(&mut self, text: &str) -> anyhow::Result<()> { + let Some(chat_id) = self.chat_id else { + anyhow::bail!("no active chat to send message to"); + }; + + if text.len() <= MAX_MESSAGE_LEN { + self.bot.send_message(chat_id, text).await?; + } else { + for chunk in text.as_bytes().chunks(MAX_MESSAGE_LEN) { + let chunk_str = String::from_utf8_lossy(chunk); + self.bot.send_message(chat_id, chunk_str.as_ref()).await?; + } + } + + Ok(()) + } + + async fn send_typing(&mut self) -> anyhow::Result<()> { + let Some(chat_id) = self.chat_id else { + return Ok(()); + }; + self.bot.send_chat_action(chat_id, ChatAction::Typing).await?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_command_detection() { + assert_eq!(TelegramChannel::is_command("/start"), Some("/start")); + assert_eq!(TelegramChannel::is_command("/reset now"), Some("/reset")); + assert_eq!(TelegramChannel::is_command("hello"), None); + assert_eq!(TelegramChannel::is_command(""), None); + } +} diff --git a/crates/zeph-core/src/agent.rs b/crates/zeph-core/src/agent.rs index f088c963..b2a7dd17 100644 --- a/crates/zeph-core/src/agent.rs +++ b/crates/zeph-core/src/agent.rs @@ -1,29 +1,31 @@ -use std::io::{self, BufRead, Write}; use std::time::Duration; use tokio::process::Command; use zeph_llm::provider::{LlmProvider, Message, Role}; use zeph_memory::sqlite::{SqliteStore, role_str}; +use crate::channel::Channel; use crate::context::build_system_prompt; const MAX_SHELL_ITERATIONS: usize = 3; const SHELL_TIMEOUT: Duration = Duration::from_secs(30); -pub struct Agent { +pub struct Agent { provider: P, + channel: C, messages: Vec, memory: Option, conversation_id: Option, history_limit: u32, } -impl Agent

{ +impl Agent { #[must_use] - pub fn new(provider: P, skills_prompt: &str) -> Self { + pub fn new(provider: P, channel: C, skills_prompt: &str) -> Self { let system_prompt = build_system_prompt(skills_prompt); Self { provider, + channel, messages: vec![Message { role: Role::System, content: system_prompt, @@ -62,40 +64,28 @@ impl Agent

{ Ok(()) } - /// Run the interactive chat loop, reading from stdin until EOF or "exit"/"quit". + /// Run the chat loop, receiving messages via the channel until EOF or shutdown. /// /// # Errors /// - /// Returns an error if stdout flushing or stdin reading fails. + /// Returns an error if channel I/O or LLM communication fails. pub async fn run(&mut self) -> anyhow::Result<()> { - let stdin = io::stdin(); - let mut reader = stdin.lock().lines(); - loop { - print!("You: "); - io::stdout().flush()?; - - let Some(Ok(line)) = reader.next() else { + let Some(incoming) = self.channel.recv().await? else { break; }; - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - if trimmed == "exit" || trimmed == "quit" { - break; - } - self.messages.push(Message { role: Role::User, - content: trimmed.to_string(), + content: incoming.text.clone(), }); - self.persist_message(Role::User, trimmed).await; + self.persist_message(Role::User, &incoming.text).await; if let Err(e) = self.process_response().await { tracing::error!("LLM error: {e:#}"); - eprintln!("Error: {e:#}"); + self.channel + .send(&format!("Error: {e:#}")) + .await?; self.messages.pop(); } } @@ -105,8 +95,10 @@ impl Agent

{ async fn process_response(&mut self) -> anyhow::Result<()> { for _ in 0..MAX_SHELL_ITERATIONS { + self.channel.send_typing().await?; + let response = self.provider.chat(&self.messages).await?; - println!("Zeph: {response}"); + self.channel.send(&response).await?; self.messages.push(Message { role: Role::Assistant, @@ -118,7 +110,7 @@ impl Agent

{ return Ok(()); }; - println!("[shell output]\n{output}"); + self.channel.send(&format!("[shell output]\n{output}")).await?; let shell_msg = format!("[shell output]\n{output}"); self.messages.push(Message { diff --git a/crates/zeph-core/src/channel.rs b/crates/zeph-core/src/channel.rs new file mode 100644 index 00000000..3087142d --- /dev/null +++ b/crates/zeph-core/src/channel.rs @@ -0,0 +1,109 @@ +/// Incoming message from a channel. +#[derive(Debug, Clone)] +pub struct ChannelMessage { + pub text: String, +} + +/// Bidirectional communication channel for the agent. +pub trait Channel: Send { + /// Receive the next message. Returns `None` on EOF or shutdown. + /// + /// # Errors + /// + /// Returns an error if the underlying I/O fails. + fn recv(&mut self) -> impl Future>> + Send; + + /// Send a text response. + /// + /// # Errors + /// + /// Returns an error if the underlying I/O fails. + fn send(&mut self, text: &str) -> impl Future> + Send; + + /// Send a typing indicator. No-op by default. + /// + /// # Errors + /// + /// Returns an error if the underlying I/O fails. + fn send_typing(&mut self) -> impl Future> + Send { + async { Ok(()) } + } +} + +/// CLI channel that reads from stdin and writes to stdout. +#[derive(Debug)] +pub struct CliChannel { + _private: (), +} + +impl CliChannel { + #[must_use] + pub fn new() -> Self { + Self { _private: () } + } +} + +impl Default for CliChannel { + fn default() -> Self { + Self::new() + } +} + +impl Channel for CliChannel { + async fn recv(&mut self) -> anyhow::Result> { + use std::io::{BufRead, Write}; + + let line = tokio::task::spawn_blocking(|| { + let stdin = std::io::stdin(); + let mut reader = stdin.lock(); + let mut buf = String::new(); + + print!("You: "); + std::io::stdout().flush()?; + + match reader.read_line(&mut buf) { + Ok(0) => Ok(None), + Ok(_) => Ok(Some(buf)), + Err(e) => Err(anyhow::anyhow!(e)), + } + }) + .await??; + + let Some(raw) = line else { + return Ok(None); + }; + + let trimmed = raw.trim(); + if trimmed.is_empty() || trimmed == "exit" || trimmed == "quit" { + return Ok(None); + } + + Ok(Some(ChannelMessage { + text: trimmed.to_string(), + })) + } + + async fn send(&mut self, text: &str) -> anyhow::Result<()> { + println!("Zeph: {text}"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn channel_message_creation() { + let msg = ChannelMessage { + text: "hello".to_string(), + }; + assert_eq!(msg.text, "hello"); + } + + #[test] + fn cli_channel_default() { + let ch = CliChannel::default(); + let _ = format!("{ch:?}"); + } +} diff --git a/crates/zeph-core/src/config.rs b/crates/zeph-core/src/config.rs index 69464b49..2c22b2e8 100644 --- a/crates/zeph-core/src/config.rs +++ b/crates/zeph-core/src/config.rs @@ -9,6 +9,7 @@ pub struct Config { pub llm: LlmConfig, pub skills: SkillsConfig, pub memory: MemoryConfig, + pub telegram: Option, } #[derive(Debug, Deserialize)] @@ -41,6 +42,13 @@ pub struct MemoryConfig { pub history_limit: u32, } +#[derive(Debug, Clone, Deserialize)] +pub struct TelegramConfig { + pub token: Option, + #[serde(default)] + pub allowed_users: Vec, +} + impl Config { /// Load configuration from a TOML file with env var overrides. /// @@ -75,6 +83,13 @@ impl Config { if let Ok(v) = std::env::var("ZEPH_SQLITE_PATH") { self.memory.sqlite_path = v; } + if let Ok(v) = std::env::var("ZEPH_TELEGRAM_TOKEN") { + let tg = self.telegram.get_or_insert(TelegramConfig { + token: None, + allowed_users: Vec::new(), + }); + tg.token = Some(v); + } } fn default() -> Self { @@ -95,6 +110,7 @@ impl Config { sqlite_path: "./data/zeph.db".into(), history_limit: 50, }, + telegram: None, } } } @@ -105,6 +121,19 @@ mod tests { use super::*; + const LLM_ENV_KEYS: [&str; 4] = [ + "ZEPH_LLM_PROVIDER", + "ZEPH_LLM_BASE_URL", + "ZEPH_LLM_MODEL", + "ZEPH_SQLITE_PATH", + ]; + + fn clear_llm_env() { + for key in LLM_ENV_KEYS { + unsafe { std::env::remove_var(key) }; + } + } + #[test] fn defaults_when_file_missing() { let config = Config::default(); @@ -114,6 +143,7 @@ mod tests { assert_eq!(config.agent.name, "Zeph"); assert_eq!(config.memory.history_limit, 50); assert!(config.llm.cloud.is_none()); + assert!(config.telegram.is_none()); } #[test] @@ -142,10 +172,7 @@ history_limit = 10 ) .unwrap(); - // Remove any ZEPH_ env vars that could interfere - for key in ["ZEPH_LLM_PROVIDER", "ZEPH_LLM_BASE_URL", "ZEPH_LLM_MODEL", "ZEPH_SQLITE_PATH"] { - unsafe { std::env::remove_var(key) }; - } + clear_llm_env(); let config = Config::load(&path).unwrap(); assert_eq!(config.agent.name, "TestBot"); @@ -184,9 +211,7 @@ history_limit = 50 ) .unwrap(); - for key in ["ZEPH_LLM_PROVIDER", "ZEPH_LLM_BASE_URL", "ZEPH_LLM_MODEL", "ZEPH_SQLITE_PATH"] { - unsafe { std::env::remove_var(key) }; - } + clear_llm_env(); let config = Config::load(&path).unwrap(); assert_eq!(config.llm.provider, "claude"); @@ -206,4 +231,55 @@ history_limit = 50 assert_eq!(config.llm.model, "phi3:mini"); } + + #[test] + fn telegram_config_from_toml() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("tg.toml"); + let mut f = std::fs::File::create(&path).unwrap(); + write!( + f, + r#" +[agent] +name = "Zeph" + +[llm] +provider = "ollama" +base_url = "http://localhost:11434" +model = "mistral:7b" + +[skills] +paths = ["./skills"] + +[memory] +sqlite_path = "./data/zeph.db" +history_limit = 50 + +[telegram] +token = "123:ABC" +allowed_users = ["alice", "bob"] +"# + ) + .unwrap(); + + clear_llm_env(); + + let config = Config::load(&path).unwrap(); + let tg = config.telegram.unwrap(); + assert_eq!(tg.token.as_deref(), Some("123:ABC")); + assert_eq!(tg.allowed_users, vec!["alice", "bob"]); + } + + #[test] + fn telegram_env_override() { + let mut config = Config::default(); + assert!(config.telegram.is_none()); + + unsafe { std::env::set_var("ZEPH_TELEGRAM_TOKEN", "env-token") }; + config.apply_env_overrides(); + unsafe { std::env::remove_var("ZEPH_TELEGRAM_TOKEN") }; + + let tg = config.telegram.unwrap(); + assert_eq!(tg.token.as_deref(), Some("env-token")); + } } diff --git a/crates/zeph-core/src/lib.rs b/crates/zeph-core/src/lib.rs index 930e6c4b..2bb815df 100644 --- a/crates/zeph-core/src/lib.rs +++ b/crates/zeph-core/src/lib.rs @@ -1,5 +1,6 @@ //! Agent loop, configuration loading, and context builder. pub mod agent; +pub mod channel; pub mod config; pub mod context; diff --git a/src/main.rs b/src/main.rs index e90f67b5..71324d6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, bail}; +use zeph_channels::telegram::TelegramChannel; use zeph_core::agent::Agent; +use zeph_core::channel::{Channel, ChannelMessage, CliChannel}; use zeph_core::config::Config; use zeph_llm::any::AnyProvider; use zeph_llm::claude::ClaudeProvider; @@ -10,6 +12,36 @@ use zeph_memory::sqlite::SqliteStore; use zeph_skills::prompt::format_skills_prompt; use zeph_skills::registry::SkillRegistry; +/// Enum dispatch for runtime channel selection, following the `AnyProvider` pattern. +#[derive(Debug)] +enum AnyChannel { + Cli(CliChannel), + Telegram(TelegramChannel), +} + +impl Channel for AnyChannel { + async fn recv(&mut self) -> anyhow::Result> { + match self { + Self::Cli(c) => c.recv().await, + Self::Telegram(c) => c.recv().await, + } + } + + async fn send(&mut self, text: &str) -> anyhow::Result<()> { + match self { + Self::Cli(c) => c.send(text).await, + Self::Telegram(c) => c.send(text).await, + } + } + + async fn send_typing(&mut self) -> anyhow::Result<()> { + match self { + Self::Cli(c) => c.send_typing().await, + Self::Telegram(c) => c.send_typing().await, + } + } +} + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); @@ -24,7 +56,11 @@ async fn main() -> anyhow::Result<()> { tracing::info!("loaded {} skill(s)", registry.all().len()); - println!("zeph v{}", env!("CARGO_PKG_VERSION")); + let channel = create_channel(&config)?; + + if matches!(channel, AnyChannel::Cli(_)) { + println!("zeph v{}", env!("CARGO_PKG_VERSION")); + } let store = SqliteStore::new(&config.memory.sqlite_path).await?; let conversation_id = match store.latest_conversation_id().await? { @@ -34,7 +70,7 @@ async fn main() -> anyhow::Result<()> { tracing::info!("conversation id: {conversation_id}"); - let mut agent = Agent::new(provider, &skills_prompt) + let mut agent = Agent::new(provider, channel, &skills_prompt) .with_memory(store, conversation_id, config.memory.history_limit); agent.load_history().await?; agent.run().await @@ -63,3 +99,23 @@ fn create_provider(config: &Config) -> anyhow::Result { other => bail!("unknown LLM provider: {other}"), } } + +fn create_channel(config: &Config) -> anyhow::Result { + let token = config + .telegram + .as_ref() + .and_then(|t| t.token.clone()); + + if let Some(token) = token { + let allowed = config + .telegram + .as_ref() + .map_or_else(Vec::new, |t| t.allowed_users.clone()); + + let tg = TelegramChannel::new(token, allowed).start()?; + tracing::info!("running in Telegram mode"); + Ok(AnyChannel::Telegram(tg)) + } else { + Ok(AnyChannel::Cli(CliChannel::new())) + } +}