diff --git a/.loki-project.json b/.loki-project.json new file mode 100644 index 0000000..165f4c7 --- /dev/null +++ b/.loki-project.json @@ -0,0 +1 @@ +{"type":"rust","build":"cargo build","test":"cargo test","check":"cargo check","_detected_by":"heuristic","_cached_at":"2026-04-13T13:36:33-06:00"} diff --git a/Cargo.lock b/Cargo.lock index a52948f..0db1cee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "anndists" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8238f99889a837cd6641360f9f3ead18f70b07bf6ce1f04a319bc6bd8a2f48f1" +checksum = "9a8396b473aa0bceed68fb32462505387ea39fa47c7029417e0a49f10592b036" dependencies = [ "anyhow", "cfg-if", @@ -145,6 +145,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.6.1" @@ -165,25 +174,25 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] [[package]] name = "argc" -version = "1.23.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed45b16ecde78101a90574aeeac8e61749203b7dba72a4abb70a870e92af0047" +checksum = "17060e608fbc0809d62a996a65cdee9e7c441a979f40f2d1d2fbdce9eef60dad" dependencies = [ "anyhow", "base64", - "convert_case 0.8.0", + "convert_case 0.11.0", "dirs", "either", - "indexmap 2.13.0", + "indexmap 2.14.0", "natord", "nom 8.0.0", "num_cpus", @@ -194,6 +203,7 @@ dependencies = [ "shell-words", "textwrap", "threadpool", + "unicode-width", "which", ] @@ -205,15 +215,15 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -262,15 +272,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.8.13" +version = "1.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c456581cb3c77fafcc8c67204a70680d40b61112d6da78c77bd31d945b65f1b5" +checksum = "517aa062d8bd9015ee23d6daa5e1c1372328412fdae4e6c4c1be9b69c6ad37a2" dependencies = [ "aws-credential-types", "aws-runtime", @@ -282,13 +292,14 @@ dependencies = [ "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", "fastrand", "hex", "http 1.4.0", - "ring", + "sha1", "time", "tokio", "tracing", @@ -298,9 +309,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.11" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -310,19 +321,19 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ - "aws-lc-sys 0.39.1", + "aws-lc-sys 0.41.0", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "bindgen", "cc", @@ -333,9 +344,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.1" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -345,9 +356,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.6.0" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c635c2dc792cb4a11ce1a4f392a925340d1bdf499289b5ec1ec6810954eb43f5" +checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -358,6 +369,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", + "bytes-utils", "fastrand", "http 1.4.0", "http-body 1.0.1", @@ -369,9 +381,9 @@ dependencies = [ [[package]] name = "aws-sdk-secretsmanager" -version = "1.99.0" +version = "1.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f5e5b4f5612dc147e56bf2c1c436e8e098dc3433fb525cfd7562128a78b53f" +checksum = "1c4e56ac810211dc33810c7aa3612eda29a8b1e8c7e2db6e960c8657e3d95e42" dependencies = [ "aws-credential-types", "aws-runtime", @@ -393,9 +405,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.93.0" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcb38bb33fc0a11f1ffc3e3e85669e0a11a37690b86f77e75306d8f369146a0" +checksum = "9f4055e6099b2ec264abdc0d9bbfffce306c1601809275c861594779a0b04b45" dependencies = [ "aws-credential-types", "aws-runtime", @@ -417,9 +429,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.95.0" +version = "1.101.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ada8ffbea7bd1be1f53df1dadb0f8fdb04badb13185b3321b929d1ee3caad09" +checksum = "02f009ba0284c5d696425fd7b4dcc5b189f5726f4041b7a5794daecb3a68d598" dependencies = [ "aws-credential-types", "aws-runtime", @@ -441,9 +453,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.97.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6443ccadc777095d5ed13e21f5c364878c9f5bad4e35187a6cdbd863b0afcad" +checksum = "6aa6622798e19e6a76b690562085dd4771c736cd48343464a53ab4ae2f2c9f84" dependencies = [ "aws-credential-types", "aws-runtime", @@ -466,9 +478,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.8" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa49f3c607b92daae0c078d48a4571f599f966dce3caee5f1ea55c4d9073f99" +checksum = "b7083fb918b38474ac65ffbf8a69fc8792d36879f4ac5f1667b43aec61efe9a5" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -477,20 +489,20 @@ dependencies = [ "bytes", "form_urlencoded", "hex", - "hmac", + "hmac 0.13.0", "http 0.2.12", "http 1.4.0", "percent-encoding", - "sha2", + "sha2 0.11.0", "time", "tracing", ] [[package]] name = "aws-smithy-async" -version = "1.2.11" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52eec3db979d18cb807fc1070961cc51d87d069abe9ab57917769687368a8c6c" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" dependencies = [ "futures-util", "pin-project-lite", @@ -499,9 +511,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.18" +version = "0.60.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b9c7354a3b13c66f60fe4616d6d1969c9fd36b1b5333a5dfb3ee716b33c588" +checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" dependencies = [ "aws-smithy-types", "bytes", @@ -510,9 +522,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.63.3" +version = "0.63.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630e67f2a31094ffa51b210ae030855cb8f3b7ee1329bdd8d085aaf61e8b97fc" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -531,26 +543,26 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fb0abf49ff0cab20fd31ac1215ed7ce0ea92286ba09e2854b42ba5cabe7525" +checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.3.27", - "h2 0.4.13", + "h2 0.4.14", "http 0.2.12", "http 1.4.0", "http-body 0.4.6", "hyper 0.14.32", "hyper 1.9.0", "hyper-rustls 0.24.2", - "hyper-rustls 0.27.7", + "hyper-rustls 0.27.9", "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.37", + "rustls 0.23.40", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -561,27 +573,29 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.3" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb96aa208d62ee94104645f7b2ecaf77bf27edf161590b6224bfbac2832f979" +checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a46543fbc94621080b3cf553eb4cbbdc41dd9780a30c4756400f0139440a1d" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.13" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cebbddb6f3a5bd81553643e9c7daf3cc3dc5b0b5f398ac668630e8a84e6fff0" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" dependencies = [ "aws-smithy-types", "urlencoding", @@ -589,15 +603,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3df87c14f0127a0d77eb261c3bc45d5b4833e2a1f63583ebfb728e4852134ee" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-http-client", "aws-smithy-observability", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "bytes", "fastrand", @@ -614,11 +629,12 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49952c52f7eebb72ce2a754d3866cc0f87b97d2a46146b79f80f3a93fb2b3716" +checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" dependencies = [ "aws-smithy-async", + "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", @@ -630,10 +646,32 @@ dependencies = [ ] [[package]] -name = "aws-smithy-types" -version = "1.4.3" +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3a26048eeab0ddeba4b4f9d51654c79af8c3b32357dc5f336cee85ab331c33" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.0", +] + +[[package]] +name = "aws-smithy-types" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" dependencies = [ "base64-simd", "bytes", @@ -657,22 +695,23 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.13" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.11" +version = "1.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" dependencies = [ "aws-credential-types", "aws-smithy-async", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "rustc_version", "tracing", @@ -680,9 +719,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -859,7 +898,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.2", + "rustc-hash", "shlex", "syn", ] @@ -881,9 +920,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -894,7 +933,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -906,6 +945,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -931,10 +979,19 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.20.2" +name = "bs58" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -999,9 +1056,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -1038,7 +1095,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] @@ -1048,7 +1116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -1074,7 +1142,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -1092,9 +1160,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -1115,9 +1183,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.0" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap", "clap_lex", @@ -1137,9 +1205,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -1171,6 +1239,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "colorchoice" version = "1.0.5" @@ -1198,9 +1272,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "compression-core", "flate2", @@ -1209,9 +1283,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concurrent-queue" @@ -1248,13 +1322,16 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.8.0" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" -dependencies = [ - "unicode-segmentation", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "convert_case" @@ -1265,6 +1342,44 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1300,6 +1415,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32c" version = "0.6.8" @@ -1343,24 +1467,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags", - "crossterm_winapi", - "filedescriptor", - "mio", - "parking_lot", - "rustix 0.38.44", - "serde", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.29.0" @@ -1371,9 +1477,11 @@ dependencies = [ "crossterm_winapi", "derive_more 2.1.1", "document-features", + "filedescriptor", "mio", "parking_lot", - "rustix 1.1.4", + "rustix", + "serde", "signal-hook", "signal-hook-mio", "winapi", @@ -1388,12 +1496,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "crypto-common" version = "0.1.7" @@ -1405,6 +1507,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "cssparser" version = "0.34.0" @@ -1428,6 +1539,42 @@ dependencies = [ "syn", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.11" @@ -1497,6 +1644,16 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1507,6 +1664,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1518,34 +1686,13 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", -] - [[package]] name = "derive_more" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl 2.1.1", -] - -[[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", + "derive_more-impl", ] [[package]] @@ -1598,11 +1745,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1699,6 +1858,31 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "signature", + "subtle", + "zeroize", +] + [[package]] name = "ego-tree" version = "0.10.0" @@ -1707,9 +1891,9 @@ checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encode_unicode" @@ -1717,6 +1901,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1830,9 +2023,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fd-lock" @@ -1841,10 +2034,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.4", + "rustix", "windows-sys 0.59.0", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "filedescriptor" version = "0.8.3" @@ -1856,6 +2055,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1876,6 +2085,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -2001,12 +2211,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.32" @@ -2057,7 +2261,7 @@ dependencies = [ "once_cell", "prost", "prost-types", - "reqwest", + "reqwest 0.12.28", "secret-vault-value", "serde", "serde_json", @@ -2070,21 +2274,6 @@ dependencies = [ "url", ] -[[package]] -name = "generator" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2101,19 +2290,10 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-link", ] -[[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" @@ -2150,6 +2330,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -2168,15 +2349,15 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gman" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c3a428900217107275faf709b30c00f37e1112ec2b75742987b5ca88700eaa" +checksum = "742225eb41061a0938aa0924ce8d08a1ec48875789b72ce3f0cb02eda52ab1db" dependencies = [ "anyhow", "argon2", "async-trait", "aws-config", - "aws-lc-sys 0.37.1", + "aws-lc-sys 0.39.1", "aws-sdk-secretsmanager", "azure_core", "azure_identity", @@ -2189,7 +2370,7 @@ dependencies = [ "clap_complete", "confy", "crc32c", - "crossterm 0.29.0", + "crossterm", "dialoguer", "dirs", "futures", @@ -2226,7 +2407,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -2235,9 +2416,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -2245,7 +2426,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -2258,15 +2439,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -2280,9 +2452,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -2308,7 +2480,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -2324,14 +2505,14 @@ dependencies = [ "cpu-time", "env_logger", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "lazy_static", "log", "mmap-rs", "num-traits", "num_cpus", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "rayon", "serde", ] @@ -2472,6 +2653,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -2506,7 +2696,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", + "h2 0.4.14", "http 1.4.0", "http-body 1.0.1", "httparse", @@ -2535,20 +2725,18 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", "hyper 1.9.0", "hyper-util", - "rustls 0.23.37", + "rustls 0.23.40", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots", ] [[package]] @@ -2734,9 +2922,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -2755,16 +2943,29 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "indoc" version = "2.0.7" @@ -2790,7 +2991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6654738b8024300cf062d04a1c13c10c8e2cea598ec1c47dc9b6641159429756" dependencies = [ "bitflags", - "crossterm 0.29.0", + "crossterm", "dyn-clone", "fuzzy-matcher", "unicode-segmentation", @@ -2803,16 +3004,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is-docker" version = "0.2.0" @@ -2822,18 +3013,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-macro" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -2870,15 +3049,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -2905,9 +3075,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -2918,15 +3088,64 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2939,9 +3158,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -2951,14 +3170,14 @@ dependencies = [ [[package]] name = "json-patch" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -2973,9 +3192,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.3.0" +version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ "base64", "getrandom 0.2.17", @@ -2985,14 +3204,9 @@ dependencies = [ "serde_json", "signature", "simple_asn1", + "zeroize", ] -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" - [[package]] name = "lazy_static" version = "1.5.0" @@ -3007,9 +3221,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -3021,27 +3235,15 @@ dependencies = [ "windows-link", ] -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -3101,7 +3303,7 @@ dependencies = [ "log-mdc", "mock_instant", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde-value", "serde_json", @@ -3134,21 +3336,20 @@ dependencies = [ "clap_complete", "clap_complete_nushell", "colored", - "crossterm 0.28.1", + "crossterm", "dirs", "duct", + "dunce", + "eventsource-stream", "fancy-regex", "futures-util", "fuzzy-matcher", "gman", - "hmac", + "hmac 0.12.1", "hnsw_rs", "html_to_markdown", "http 1.4.0", - "http-body-util", - "hyper 1.9.0", - "hyper-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "indoc", "inquire", "is-terminal", @@ -3162,31 +3363,28 @@ dependencies = [ "parking_lot", "path-absolutize", "pretty_assertions", - "rand 0.9.2", - "rayon", + "rand 0.10.1", "reedline", - "reqwest", - "reqwest-eventsource", + "reqwest 0.13.3", "rmcp", "rust-embed", - "rustpython-ast", - "rustpython-parser", "scraper", + "self_update", "serde", "serde_json", "serde_yaml", - "sha2", + "serial_test", + "sha2 0.10.9", "shell-words", - "strum_macros 0.27.2", + "strum_macros", "syntect", "sys-locale", "terminal-colorsaurus", "textwrap", - "time", "tokio", - "tokio-graceful", - "tokio-stream", - "unicode-segmentation", + "tree-sitter", + "tree-sitter-python", + "tree-sitter-typescript", "unicode-width", "url", "urlencoding", @@ -3194,22 +3392,6 @@ dependencies = [ "which", ] -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "pin-utils", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -3231,64 +3413,6 @@ dependencies = [ "libc", ] -[[package]] -name = "malachite" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbdf9cb251732db30a7200ebb6ae5d22fe8e11397364416617d2c2cf0c51cb5" -dependencies = [ - "malachite-base", - "malachite-nz", - "malachite-q", -] - -[[package]] -name = "malachite-base" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" -dependencies = [ - "hashbrown 0.14.5", - "itertools 0.11.0", - "libm", - "ryu", -] - -[[package]] -name = "malachite-bigint" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d149aaa2965d70381709d9df4c7ee1fc0de1c614a4efc2ee356f5e43d68749f8" -dependencies = [ - "derive_more 1.0.0", - "malachite", - "num-integer", - "num-traits", - "paste", -] - -[[package]] -name = "malachite-nz" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" -dependencies = [ - "itertools 0.11.0", - "libm", - "malachite-base", -] - -[[package]] -name = "malachite-q" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f235d5747b1256b47620f5640c2a17a88c7569eebdf27cd9cb130e1a619191" -dependencies = [ - "itertools 0.11.0", - "malachite-base", - "malachite-nz", -] - [[package]] name = "markup5ever" version = "0.12.1" @@ -3340,15 +3464,6 @@ dependencies = [ "syn", ] -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - [[package]] name = "matchit" version = "0.8.4" @@ -3471,9 +3586,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags", "cfg-if", @@ -3530,9 +3645,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -3766,9 +3881,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "onig" -version = "6.5.1" +version = "6.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" dependencies = [ "bitflags", "libc", @@ -3778,9 +3893,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.9.1" +version = "69.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" dependencies = [ "cc", "pkg-config", @@ -3794,9 +3909,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open" -version = "5.3.3" +version = "5.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" dependencies = [ "is-wsl", "libc", @@ -3805,15 +3920,14 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -3837,18 +3951,18 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.5+3.5.5" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -3874,13 +3988,13 @@ dependencies = [ [[package]] name = "os_info" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" dependencies = [ "android_system_properties", "log", - "nix 0.30.1", + "nix 0.31.3", "objc2", "objc2-foundation", "objc2-ui-kit", @@ -3943,17 +4057,11 @@ dependencies = [ "subtle", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pastey" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" [[package]] name = "path-absolutize" @@ -4003,7 +4111,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -4033,7 +4141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -4060,18 +4168,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -4091,20 +4199,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.32" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ "base64", - "indexmap 2.13.0", - "quick-xml 0.38.4", + "indexmap 2.14.0", + "quick-xml 0.39.4", "serde", "time", ] @@ -4115,7 +4233,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4128,9 +4246,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -4223,8 +4341,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.13.0", - "nix 0.31.2", + "indexmap 2.14.0", + "nix 0.31.3", "tokio", "tracing", "windows 0.62.2", @@ -4273,9 +4391,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", ] @@ -4291,8 +4409,8 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.2", - "rustls 0.23.37", + "rustc-hash", + "rustls 0.23.40", "socket2 0.6.3", "thiserror 2.0.18", "tokio", @@ -4306,13 +4424,14 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", - "rustc-hash 2.1.2", - "rustls 0.23.37", + "rustc-hash", + "rustls 0.23.40", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -4358,33 +4477,32 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ - "libc", - "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "chacha20 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -4416,10 +4534,16 @@ dependencies = [ ] [[package]] -name = "rayon" -version = "1.11.0" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -4457,20 +4581,20 @@ dependencies = [ [[package]] name = "reedline" -version = "0.40.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5cdfab7494d13ebfb6ce64828648518205d3ce8541ef1f94a27887f29d2d50b" +checksum = "2066729dce9fecd28d1c6850a159ee68719130f149b22467c362353e16994e90" dependencies = [ "chrono", - "crossterm 0.28.1", + "crossterm", "fd-lock", "itertools 0.13.0", "nu-ansi-term", "serde", "strip-ansi-escapes", "strum", - "strum_macros 0.26.4", "thiserror 2.0.18", + "unicase", "unicode-segmentation", "unicode-width", ] @@ -4544,7 +4668,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.9.0", - "hyper-rustls 0.27.7", + "hyper-rustls 0.27.9", "hyper-tls", "hyper-util", "js-sys", @@ -4553,8 +4677,7 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls 0.23.37", + "rustls 0.23.40", "rustls-native-certs", "rustls-pki-types", "serde", @@ -4571,25 +4694,55 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", - "webpki-roots", ] [[package]] -name = "reqwest-eventsource" -version = "0.6.0" +name = "reqwest" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ - "eventsource-stream", + "base64", + "bytes", + "futures-channel", "futures-core", - "futures-timer", - "mime", - "nom 7.1.3", + "futures-util", + "h2 0.4.14", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.9.0", + "hyper-rustls 0.27.9", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "native-tls", + "percent-encoding", "pin-project-lite", - "reqwest", - "thiserror 1.0.69", + "quinn", + "rustls 0.23.40", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", ] [[package]] @@ -4617,21 +4770,24 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.16.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c9c94680f75470ee8083a0667988b5d7b5beb70b9f998a8e51de7c682ce60" +checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" dependencies = [ "async-trait", "base64", "chrono", "futures", + "http 1.4.0", "pastey", "pin-project-lite", "process-wrap", + "reqwest 0.13.3", "rmcp-macros", "schemars 1.2.1", "serde", "serde_json", + "sse-stream", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -4641,9 +4797,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.16.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c23c8f26cae4da838fbc3eadfaecf2d549d97c04b558e7bd90526a9c28b42a" +checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -4654,29 +4810,29 @@ dependencies = [ [[package]] name = "roff" -version = "0.2.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" +checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189" [[package]] name = "rpassword" -version = "7.4.0" +version = "7.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +checksum = "835a57a69104632d64deb0df2e09a69945cd7a6eab4070fc9b1d7e50cf6c3edc" dependencies = [ "libc", "rtoolbox", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rtoolbox" -version = "0.0.3" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4709,7 +4865,7 @@ version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ - "sha2", + "sha2 0.10.9", "walkdir", ] @@ -4729,12 +4885,6 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.2" @@ -4750,19 +4900,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" @@ -4772,7 +4909,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -4790,16 +4927,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -4818,14 +4955,41 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.40", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.13", + "security-framework", + "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.101.7" @@ -4838,9 +5002,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -4848,63 +5012,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustpython-ast" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdaf8ee5c1473b993b398c174641d3aa9da847af36e8d5eb8291930b72f31a5" -dependencies = [ - "is-macro", - "malachite-bigint", - "rustpython-parser-core", - "static_assertions", -] - -[[package]] -name = "rustpython-parser" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868f724daac0caf9bd36d38caf45819905193a901e8f1c983345a68e18fb2abb" -dependencies = [ - "anyhow", - "is-macro", - "itertools 0.11.0", - "lalrpop-util", - "log", - "malachite-bigint", - "num-traits", - "phf", - "phf_codegen", - "rustc-hash 1.1.0", - "rustpython-ast", - "rustpython-parser-core", - "tiny-keccak", - "unic-emoji-char", - "unic-ucd-ident", - "unicode_names2", -] - -[[package]] -name = "rustpython-parser-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b6c12fa273825edc7bccd9a734f0ad5ba4b8a2f4da5ff7efe946f066d0f4ad" -dependencies = [ - "is-macro", - "memchr", - "rustpython-parser-vendored", -] - -[[package]] -name = "rustpython-parser-vendored" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04fcea49a4630a3a5d940f4d514dc4f575ed63c14c3e3ed07146634aed7f67a6" -dependencies = [ - "memchr", - "once_cell", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -4926,6 +5033,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.29" @@ -4973,12 +5089,6 @@ dependencies = [ "syn", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -4994,7 +5104,7 @@ dependencies = [ "cssparser", "ego-tree", "html5ever 0.29.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "precomputed-hash", "selectors", "tendril", @@ -5010,6 +5120,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "secrecy" version = "0.10.3" @@ -5073,10 +5189,47 @@ dependencies = [ ] [[package]] -name = "semver" -version = "1.0.27" +name = "self-replace" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "03ec815b5eab420ab893f63393878d89c90fdd94c0bcc44c07abb8ad95552fb7" +dependencies = [ + "fastrand", + "tempfile", + "windows-sys 0.52.0", +] + +[[package]] +name = "self_update" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e79722b5a505d4ddc77527455a97244e9e8c4c07533ff44cf4421cce7bb6d17" +dependencies = [ + "either", + "flate2", + "http 1.4.0", + "indicatif", + "log", + "quick-xml 0.38.4", + "regex", + "reqwest 0.13.3", + "self-replace", + "semver", + "serde", + "serde_json", + "tar", + "tempfile", + "ureq", + "urlencoding", + "zip", + "zipsign-api", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -5131,11 +5284,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -5166,15 +5319,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" dependencies = [ "base64", + "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -5185,9 +5339,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -5201,13 +5355,39 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "servo_arc" version = "0.4.3" @@ -5217,6 +5397,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5224,17 +5415,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "sha2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ - "lazy_static", + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -5314,6 +5507,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5323,6 +5517,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "simple_asn1" version = "0.6.4" @@ -5337,9 +5547,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -5379,18 +5589,46 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sse-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3962b63f038885f15bce2c6e02c0e7925c072f1ac86bb60fd44c5c6b762fb72" +dependencies = [ + "bytes", + "futures-util", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stop-words" version = "0.9.0" @@ -5400,6 +5638,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + [[package]] name = "string_cache" version = "0.8.9" @@ -5442,21 +5686,11 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", + "strum_macros", ] [[package]] @@ -5565,6 +5799,17 @@ dependencies = [ "windows 0.62.2", ] +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -5574,7 +5819,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -5621,7 +5866,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -5736,15 +5981,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -5772,9 +6008,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -5787,24 +6023,11 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-graceful" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45740b38b48641855471cd402922e89156bdfbd97b69b45eeff170369cc18c7d" -dependencies = [ - "loom", - "pin-project-lite", - "slab", - "tokio", - "tracing", -] - [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -5837,7 +6060,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls 0.23.40", "tokio", ] @@ -5850,7 +6073,6 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", ] [[package]] @@ -5895,15 +6117,15 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "axum", "base64", "bytes", - "h2 0.4.13", + "h2 0.4.14", "http 1.4.0", "http-body 1.0.1", "http-body-util", @@ -5926,9 +6148,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", @@ -5943,7 +6165,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -5956,9 +6178,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", "bitflags", @@ -5968,13 +6190,13 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "iri-string", "pin-project-lite", "tokio", "tokio-util", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -6018,36 +6240,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "tree-sitter" +version = "0.26.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "4dab76d0b724ba557954125188cf0633a1ca43199ced82d95c7b9c32cc3de1f3" dependencies = [ - "log", - "once_cell", - "tracing-core", + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", ] [[package]] -name = "tracing-subscriber" -version = "0.3.23" +name = "tree-sitter-language" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" + +[[package]] +name = "tree-sitter-python" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf85fd39652e740bf60f46f4cda9492c3a9ad75880575bf14960f775cb74a1c" dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", + "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]] @@ -6078,9 +6310,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "typespec" @@ -6108,8 +6340,8 @@ dependencies = [ "futures", "getrandom 0.3.4", "pin-project", - "rand 0.9.2", - "reqwest", + "rand 0.9.4", + "reqwest 0.12.28", "serde", "serde_json", "time", @@ -6133,58 +6365,6 @@ dependencies = [ "syn", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-emoji-char" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.9.0" @@ -6222,26 +6402,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "unicode_names2" -version = "1.3.0" +name = "unit-prefix" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd" -dependencies = [ - "phf", - "unicode_names2_generator", -] - -[[package]] -name = "unicode_names2_generator" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91e5b84611016120197efd7dc93ef76774f4e084cd73c9fb3ea4a86c570c56e" -dependencies = [ - "getopts", - "log", - "phf_codegen", - "rand 0.8.5", -] +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "universal-hash" @@ -6249,7 +6413,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -6280,6 +6444,40 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "cookie_store", + "encoding_rs", + "flate2", + "log", + "percent-encoding", + "rustls 0.23.40", + "rustls-pki-types", + "serde", + "serde_json", + "socks", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -6304,6 +6502,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6318,9 +6522,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -6357,12 +6561,6 @@ dependencies = [ "syn", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "vcpkg" version = "0.2.15" @@ -6417,11 +6615,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -6430,14 +6628,14 @@ 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", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -6448,9 +6646,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -6458,9 +6656,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6468,9 +6666,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -6481,9 +6679,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -6505,7 +6703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -6523,6 +6721,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -6531,7 +6742,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] @@ -6543,7 +6754,7 @@ checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.4", + "rustix", "smallvec", "wayland-sys", ] @@ -6555,7 +6766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ "bitflags", - "rustix 1.1.4", + "rustix", "wayland-backend", "wayland-scanner", ] @@ -6592,7 +6803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", - "quick-xml 0.39.2", + "quick-xml 0.39.4", "quote", ] @@ -6607,9 +6818,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -6626,10 +6837,19 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "1.0.6" +name = "webpki-root-certs" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -7030,6 +7250,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -7049,7 +7275,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn", "wasm-metadata", @@ -7080,7 +7306,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -7099,7 +7325,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "semver", "serde", @@ -7118,7 +7344,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix 1.1.4", + "rustix", "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", @@ -7129,9 +7355,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x11rb" @@ -7140,7 +7366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "gethostname", - "rustix 1.1.4", + "rustix", "x11rb-protocol", ] @@ -7150,6 +7376,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "xml5ever" version = "0.18.1" @@ -7224,9 +7460,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -7296,8 +7532,52 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap 2.14.0", + "memchr", + "time", + "zopfli", +] + +[[package]] +name = "zipsign-api" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba6063ff82cdbd9a765add16d369abe81e520f836054e997c2db217ceca40c0" +dependencies = [ + "base64", + "ed25519-dalek", + "thiserror 2.0.18", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 1683cb0..132d44f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/Dark-Alex-17/loki" categories = ["command-line-utilities"] readme = "README.md" license = "MIT" -rust-version = "1.89.0" +rust-version = "1.95.0" exclude = [".github", "CONTRIBUTING.md"] [dependencies] @@ -18,10 +18,11 @@ anyhow = "1.0.69" bytes = "1.4.0" clap = { version = "4.5.40", features = ["cargo", "derive", "wrap_help"] } dirs = "6.0.0" +dunce = "1.0.5" futures-util = "0.3.29" inquire = "0.9.4" is-terminal = "0.4.9" -reedline = "0.40.0" +reedline = "0.47.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = { version = "1.0.93", features = ["preserve_order"] } serde_yaml = "0.9.17" @@ -33,11 +34,7 @@ tokio = { version = "1.34.0", features = [ "rt-multi-thread", "full", ] } -tokio-graceful = "0.2.2" -tokio-stream = { version = "0.1.15", default-features = false, features = [ - "sync", -] } -crossterm = "0.28.1" +crossterm = "0.29.0" chrono = "0.4.23" bincode = { version = "2.0.0", features = [ "serde", @@ -50,7 +47,7 @@ nu-ansi-term = "0.50.0" async-trait = "0.1.74" textwrap = "0.16.0" ansi_colours = "1.2.2" -reqwest-eventsource = "0.6.0" +eventsource-stream = "0.2.3" log = "0.4.28" log4rs = { version = "1.4.0", features = ["file_appender"] } shell-words = "1.1.0" @@ -58,20 +55,14 @@ sha2 = "0.10.8" unicode-width = "0.2.0" async-recursion = "1.1.1" http = "1.1.0" -http-body-util = "0.1" -hyper = { version = "1.0", features = ["full"] } -hyper-util = { version = "0.1", features = ["server-auto", "client-legacy"] } -time = { version = "0.3.36", features = ["macros"] } indexmap = { version = "2.2.6", features = ["serde"] } hmac = "0.12.1" aws-smithy-eventstream = "0.60.4" urlencoding = "2.1.3" -unicode-segmentation = "1.11.0" json-patch = { version = "4.0.0", default-features = false } bitflags = "2.5.0" path-absolutize = "3.1.1" hnsw_rs = "0.3.0" -rayon = "1.10.0" uuid = { version = "1.9.1", features = ["v4"] } scraper = { version = "0.23.1", default-features = false, features = [ "deterministic", @@ -88,26 +79,41 @@ duct = "1.0.0" argc = "1.23.0" strum_macros = "0.27.2" indoc = "2.0.6" -rmcp = { version = "0.16.0", features = ["client", "transport-child-process"] } +rmcp = { version = "1.5.0", features = [ + "client", + "transport-child-process", + "transport-streamable-http-client-reqwest", + "reqwest-native-tls", +] } num_cpus = "1.17.0" -rustpython-parser = "0.4.0" -rustpython-ast = "0.4.0" +tree-sitter = "0.26.8" +tree-sitter-python = "0.25.0" +tree-sitter-typescript = "0.23" colored = "3.0.0" clap_complete = { version = "4.5.58", features = ["unstable-dynamic"] } -gman = "0.3.0" +gman = "0.4.1" clap_complete_nushell = "4.5.9" open = "5" -rand = "0.9.0" +rand = { version = "0.10.0", features = ["default"] } url = "2.5.8" +self_update = { version = "0.44", default-features = false, features = [ + "reqwest", + "rustls", + "archive-tar", + "compression-flate2", + "archive-zip", + "compression-zip-deflate", +] } [dependencies.reqwest] -version = "0.12.0" +version = "0.13.3" features = [ "json", "multipart", + "stream", + "form", "socks", - "rustls-tls", - "rustls-tls-native-roots", + "rustls", ] default-features = false @@ -117,7 +123,7 @@ default-features = false features = ["parsing", "regex-onig", "plist-load"] [target.'cfg(target_os = "macos")'.dependencies] -crossterm = { version = "0.28.1", features = ["use-dev-tty"] } +crossterm = { version = "0.29.0", features = ["use-dev-tty"] } [target.'cfg(target_os = "linux")'.dependencies] arboard = { version = "3.3.0", default-features = false, features = [ @@ -129,6 +135,7 @@ arboard = { version = "3.3.0", default-features = false } [dev-dependencies] pretty_assertions = "1.4.0" +serial_test = "3" [[bin]] name = "loki" diff --git a/README.md b/README.md index df5e59e..c1f98db 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Loki: All-in-one, batteries-included LLM CLI Tool ![Test](https://github.com/Dark-Alex-17/loki/actions/workflows/ci.yaml/badge.svg) -![LOC](https://tokei.rs/b1/github/Dark-Alex-17/loki?category=code) [![crates.io link](https://img.shields.io/crates/v/loki-ai.svg)](https://crates.io/crates/loki-ai) ![Release](https://img.shields.io/github/v/release/Dark-Alex-17/loki?color=%23c694ff) ![Crate.io downloads](https://img.shields.io/crates/d/loki-ai?label=Crate%20downloads) @@ -11,53 +10,47 @@ Loki is an all-in-one, batteries-included, LLM CLI tool featuring Shell Assistan Agents, and More. It is designed to include a number of useful agents, roles, macros, and more so users can get up and running with Loki -in as little time as possible. +in as little time as possible. You can also install entire bundles of agents, roles, macros, tools, and MCP servers from +any git repository — see [Sharing Configurations](#sharing-configurations). -![Agent example](./docs/images/agents/sql.gif) +![Agent example](https://raw.githubusercontent.com/wiki/Dark-Alex-17/loki/images/agents/sql.gif) -Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration guide](./docs/AICHAT-MIGRATION.md) to get started. +Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration guide](https://github.com/Dark-Alex-17/loki/wiki/AIChat-Migration) to get started. ## Quick Links -* [AIChat Migration Guide](./docs/AICHAT-MIGRATION.md): Coming from AIChat? Follow the migration guide to get started. +* [AIChat Migration Guide](https://github.com/Dark-Alex-17/loki/wiki/AIChat-Migration): Coming from AIChat? Follow the migration guide to get started. * [Installation](#install): Install Loki * [Getting Started](#getting-started): Get started with Loki by doing first-run setup steps. -* [REPL](./docs/REPL.md): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Loki. - * [Custom REPL Prompt](./docs/REPL-PROMPT.md): Customize the REPL prompt to provide useful contextual information. -* [Vault](./docs/VAULT.md): Securely store and manage sensitive information such as API keys and credentials. -* [Shell Integrations](./docs/SHELL-INTEGRATIONS.md): Seamlessly integrate Loki with your shell environment for enhanced command-line assistance. -* [Function Calling](./docs/function-calling/TOOLS.md#Tools): Leverage function calling capabilities to extend Loki's functionality with custom tools - * [Creating Custom Tools](./docs/function-calling/CUSTOM-TOOLS.md): You can create your own custom tools to enhance Loki's capabilities. - * [Create Custom Python Tools](./docs/function-calling/CUSTOM-TOOLS.md#custom-python-based-tools) - * [Create Custom Bash Tools](./docs/function-calling/CUSTOM-BASH-TOOLS.md) - * [Bash Prompt Utilities](./docs/function-calling/BASH-PROMPT-HELPERS.md) -* [First-Class MCP Server Support](./docs/function-calling/MCP-SERVERS.md): Easily connect and interact with MCP servers for advanced functionality. -* [Macros](./docs/MACROS.md): Automate repetitive tasks and workflows with Loki "scripts" (macros). -* [RAG](./docs/RAG.md): Retrieval-Augmented Generation for enhanced information retrieval and generation. -* [Sessions](/docs/SESSIONS.md): Manage and persist conversational contexts and settings across multiple interactions. -* [Roles](./docs/ROLES.md): Customize model behavior for specific tasks or domains. -* [Agents](/docs/AGENTS.md): Leverage AI agents to perform complex tasks and workflows, including sub-agent spawning, teammate messaging, and user interaction tools. - * [Todo System](./docs/TODO-SYSTEM.md): Built-in task tracking for improved agent reliability with smaller models. -* [Environment Variables](./docs/ENVIRONMENT-VARIABLES.md): Override and customize your Loki configuration at runtime with environment variables. -* [Client Configurations](./docs/clients/CLIENTS.md): Configuration instructions for various LLM providers. - * [Authentication (API Key & OAuth)](./docs/clients/CLIENTS.md#authentication): Authenticate with API keys or OAuth for subscription-based access. - * [Patching API Requests](./docs/clients/PATCHES.md): Learn how to patch API requests for advanced customization. -* [Custom Themes](./docs/THEMES.md): Change the look and feel of Loki to your preferences with custom themes. +* [Sharing Configurations](https://github.com/Dark-Alex-17/loki/wiki/Sharing-Configurations): Install bundles of agents, roles, macros, tools, and MCP servers from any git repo, and share your own. +* [REPL](https://github.com/Dark-Alex-17/loki/wiki/REPL): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Loki. + * [Custom REPL Prompt](https://github.com/Dark-Alex-17/loki/wiki/REPL-Prompt): Customize the REPL prompt to provide useful contextual information. +* [Vault](https://github.com/Dark-Alex-17/loki/wiki/Vault): Securely store and manage sensitive information such as API keys and credentials. +* [Shell Integrations](https://github.com/Dark-Alex-17/loki/wiki/Shell-Integrations): Seamlessly integrate Loki with your shell environment for enhanced command-line assistance. +* [Function Calling](https://github.com/Dark-Alex-17/loki/wiki/Tools): Leverage function calling capabilities to extend Loki's functionality with custom tools + * [Creating Custom Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Tools): You can create your own custom tools to enhance Loki's capabilities. + * [Create Custom Python Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Tools#custom-python-based-tools) + * [Create Custom TypeScript Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Tools#custom-typescript-based-tools) + * [Create Custom Bash Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Bash-Tools) + * [Bash Prompt Utilities](https://github.com/Dark-Alex-17/loki/wiki/Bash-Prompt-Helpers) +* [First-Class MCP Server Support](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers): Easily connect and interact with MCP servers for advanced functionality. +* [Macros](https://github.com/Dark-Alex-17/loki/wiki/Macros): Automate repetitive tasks and workflows with Loki "scripts" (macros). +* [RAG](https://github.com/Dark-Alex-17/loki/wiki/RAG): Retrieval-Augmented Generation for enhanced information retrieval and generation. +* [Sessions](https://github.com/Dark-Alex-17/loki/wiki/Sessions): Manage and persist conversational contexts and settings across multiple interactions. +* [Roles](https://github.com/Dark-Alex-17/loki/wiki/Roles): Customize model behavior for specific tasks or domains. +* [Agents](https://github.com/Dark-Alex-17/loki/wiki/Agents): Leverage AI agents to perform complex tasks and workflows, including sub-agent spawning, teammate messaging, and user interaction tools. + * [Graph Agents](https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents): Define an agent as a declarative, YAML-driven workflow. A directed graph of typed nodes (LLM calls, scripts, approvals, user input, RAG retrieval, sub-agent spawns). +* [Todo System](https://github.com/Dark-Alex-17/loki/wiki/TODO-System): Built-in task tracking for improved LLM reliability with smaller models. +* [Environment Variables](https://github.com/Dark-Alex-17/loki/wiki/Environment-Variables): Override and customize your Loki configuration at runtime with environment variables. +* [Client Configurations](https://github.com/Dark-Alex-17/loki/wiki/Clients): Configuration instructions for various LLM providers. + * [Authentication (API Key & OAuth)](https://github.com/Dark-Alex-17/loki/wiki/Clients#authentication): Authenticate with API keys or OAuth for subscription-based access. + * [Patching API Requests](https://github.com/Dark-Alex-17/loki/wiki/Patches): Learn how to patch API requests for advanced customization. +* [Custom Themes](https://github.com/Dark-Alex-17/loki/wiki/Themes): Change the look and feel of Loki to your preferences with custom themes. * [History](#history): A history of how Loki came to be. ## Prerequisites Loki requires the following tools to be installed on your system: * [jq](https://github.com/jqlang/jq) * `brew install jq` -* [jira (optional)](https://github.com/ankitpokhrel/jira-cli/wiki/Installation) (For the `query_jira_issues` tool) - * `brew tap ankitpokhrel/jira-cli && brew install jira-cli` - * You'll need to [create a JIRA API token](https://id.atlassian.com/manage-profile/security/api-tokens) for authentication - * Then, save it as an environment variable to your shell profile: - ```sh - # ~/.bashrc or ~/.zshrc - export JIRA_API_TOKEN="your_jira_api_token_here" - ``` - * Then run `jira init`, select installation type as `cloud`, and provide the required details to generate a config - file for the Jira CLI. * [usql](https://github.com/xo/usql) (For the `sql` agent) * `brew install xo/xo/usql` * [docker](https://docs.docker.com/engine/install/) @@ -65,7 +58,7 @@ Loki requires the following tools to be installed on your system: * `curl -LsSf https://astral.sh/uv/install.sh | sh` These tools are used to provide various functionalities within Loki, such as document processing, JSON manipulation, -interaction with Jira, and they are used within agents and tools. +etc., and they are used within agents and tools. ## Install @@ -137,6 +130,29 @@ To use a binary from the releases page on Linux/MacOS, do the following: 3. Extract the binary with `tar -C /usr/local/bin -xzf loki-.tar.gz` (Note: This may require `sudo`) 4. Now you can run `loki`! +## Updating +Loki can update itself in place to the latest GitHub release. Run `loki --update` +for the newest release, or `loki --update v0.4.0` for a specific version: + +```shell +loki --update +loki --update v0.4.0 +``` + +The same is available from within the REPL via `.update` and `.update v0.4.0`. + +If Loki was installed with a package manager, prefer that package manager so its +records stay in sync with the binary on disk; i.e. `brew upgrade loki` for Homebrew, +or `cargo install --locked loki-ai` for Cargo. + +When Loki detects a package-manager install it prints a warning and asks for +confirmation. In a non-interactive shell (no TTY), pass `--force` to update +anyway: + +```shell +loki --update --force +``` + ## Getting Started After installation, you can generate the configuration files and directories by simply running: @@ -153,23 +169,22 @@ loki --list-secrets ### Authentication Each client in your configuration needs authentication (with a few exceptions; e.g. ollama). Most clients use an API key -(set via `api_key` in the config or through the [vault](./docs/VAULT.md)). For providers that support OAuth (e.g. Claude Pro/Max +(set via `api_key` in the config or through the [vault](https://github.com/Dark-Alex-17/loki/wiki/Vault)). For providers that support OAuth (e.g. Claude Pro/Max subscribers, Google Gemini), you can authenticate with your existing subscription instead: ```yaml # In your config.yaml clients: - type: claude - name: my-claude-oauth auth: oauth # Indicate you want to authenticate with OAuth instead of an API key ``` ```sh -loki --authenticate my-claude-oauth +loki --authenticate claude # Or via the REPL: .authenticate ``` -For full details, see the [authentication documentation](./docs/clients/CLIENTS.md#authentication). +For full details, see the [authentication documentation](https://github.com/Dark-Alex-17/loki/wiki/Clients#authentication). ### Tab-Completions You can also enable tab completions to make using Loki easier. To do so, add the following to your shell profile: @@ -246,7 +261,7 @@ shown below: | Setting | Description | |-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `repl_prelude` | This setting lets you specify a default `session` or `role` to use when starting Loki in [REPL](./docs/REPL.md) mode.
Values can be | +| `repl_prelude` | This setting lets you specify a default `session` or `role` to use when starting Loki in [REPL](https://github.com/Dark-Alex-17/loki/wiki/REPL) mode.
Values can be | | `cmd_prelude` | This setting lets you specify a default `session` or `role` to use when running one-off queries in Loki via the CLI.
Values can be | | `agent_session` | This setting is used to specify a default session that all agents should start into, unless otherwise specified in the agent configuration. (e.g. `temp`, `default`) | diff --git a/assets/agents/coder/README.md b/assets/agents/coder/README.md index dd23a02..51c4477 100644 --- a/assets/agents/coder/README.md +++ b/assets/agents/coder/README.md @@ -1,40 +1,82 @@ # Coder -An AI agent that assists you with your coding tasks. +A graph-based implementation agent. Plans, implements, and runs build + +tests in a bounded fix-loop until verified. Designed to be delegated to by +the **[Sisyphus](../sisyphus/README.md)** agent. -This agent is designed to be delegated to by the **[Sisyphus](../sisyphus/README.md)** agent to implement code specifications. Sisyphus -acts as the coordinator/architect, while Coder handles the implementation details. +Coder is a [graph agent](https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents): its workflow is +defined declaratively in `graph.yaml`, with verification and the +implement-fix loop enforced as graph edges rather than prose. -## Features +## Workflow -- 🏗️ Intelligent project structure creation and management -- 🖼️ Convert screenshots into clean, functional code -- 📁 Comprehensive file system operations (create folders, files, read/write files) -- 🧐 Advanced code analysis and improvement suggestions -- 📊 Precise diff-based file editing for controlled code modifications +``` +analyze_request (llm + output_schema) plan + complexity extraction + ↓ +route_complexity (script) opt-out approval gate (complexity ≥ 7) + ↓ +gate_approval (approval, optional) + ↓ +implement (llm + fs tools) actual file edits + ↓ +verify_build (script) + ↓ +verify_tests (script) + ↓ +fix_loop_gate (script) back-edge to implement (bounded) + ↓ +end_success / end_rejected / end_failure +``` -It can also be used as a standalone tool for direct coding assistance. +End nodes emit one of three sentinel outcomes for the caller: -## Pro-Tip: Use an IDE MCP Server for Improved Performance -Many modern IDEs now include MCP servers that let LLMs perform operations within the IDE itself and use IDE tools. Using -an IDE's MCP server dramatically improves the performance of coding agents. So if you have an IDE, try adding that MCP -server to your config (see the [MCP Server docs](../../../docs/function-calling/MCP-SERVERS.md) to see how to configure -them), and modify the agent definition to look like this: +- `CODER_COMPLETE` — build and tests passed. +- `CODER_REJECTED` — user rejected the plan at the approval gate. +- `CODER_FAILED` — fix-loop exhausted; build/tests still failing. + +## Tuning + +The agent's `project_dir` is exposed via the standard `variables:` block, +so it accepts the runtime override flag: + +```sh +# Invoke from inside the project (project_dir defaults to ".") +cd /path/to/your/project +loki -a coder "Add a foo() function..." + +# Or invoke from anywhere with an explicit override +loki -a coder --agent-variable project_dir /path/to/your/project "Add..." +``` + +`graph.yaml` `initial_state` exposes: + +- `max_fix_attempts` (default `3`) — fix-loop budget before `end_failure`. + +Environment overrides honored by the script nodes: + +- `BUILD_CMD` — skip project-type detection for the build/check command. +- `TEST_CMD` — skip detection for tests. +- `CODER_AUTOAPPROVE=1` — bypass the approval gate (for non-interactive runs + where complexity might trip the gate). + +## Pro-Tip: IDE MCP Server + +Modern IDEs (JetBrains, VS Code, Cursor, Zed, etc.) expose MCP servers +that let LLMs use IDE tools directly. To wire one in, edit `graph.yaml`: ```yaml -# ... - mcp_servers: - - jetbrains # The name of your configured IDE MCP server + - your-ide-mcp-server global_tools: - # Keep useful read-only tools for reading files in other non-project directories + # Keep read-only fs tools for files outside the IDE project - fs_read.sh - fs_grep.sh - fs_glob.sh # - fs_write.sh # - fs_patch.sh - execute_command.sh +``` -# ... -``` \ No newline at end of file +Then add the MCP server's write/patch tools to the `implement` node's +`tools:` whitelist. diff --git a/assets/agents/coder/config.yaml b/assets/agents/coder/config.yaml deleted file mode 100644 index 730ce8c..0000000 --- a/assets/agents/coder/config.yaml +++ /dev/null @@ -1,129 +0,0 @@ -name: coder -description: Implementation agent - writes code, follows patterns, verifies with builds -version: 1.0.0 -temperature: 0.1 - -auto_continue: true -max_auto_continues: 15 -inject_todo_instructions: true - -variables: - - name: project_dir - description: Project directory to work in - default: '.' - - name: auto_confirm - description: Auto-confirm command execution - default: '1' - -global_tools: - - fs_read.sh - - fs_grep.sh - - fs_glob.sh - - fs_write.sh - - fs_patch.sh - - execute_command.sh - -instructions: | - You are a senior engineer. You write code that works on the first try. - - ## Your Mission - - Given an implementation task: - 1. Check for orchestrator context first (see below) - 2. Fill gaps only. Read files NOT already covered in context - 3. Write the code (using tools, NOT chat output) - 4. Verify it compiles/builds - 5. Signal completion with a summary - - ## Using Orchestrator Context (IMPORTANT) - - When spawned by sisyphus, your prompt will often contain a `` block - with prior findings: file paths, code patterns, and conventions discovered by - explore agents. - - **If context is provided:** - 1. Use it as your primary reference. Don't re-read files already summarized - 2. Follow the code patterns shown. Snippets in context ARE the style guide - 3. Read the referenced files ONLY IF you need more detail (e.g. full function - signature, import list, or adjacent code not included in the snippet) - 4. If context includes a "Conventions" section, follow it exactly - - **If context is NOT provided or is too vague to act on:** - Fall back to self-exploration: grep for similar files, read 1-2 examples, - match their style. - - **Never ignore provided context.** It represents work already done upstream. - - ## Todo System - - For multi-file changes: - 1. `todo__init` with the implementation goal - 2. `todo__add` for each file to create/modify - 3. Implement each, calling `todo__done` immediately after - - ## Writing Code - - **CRITICAL**: Write code using `write_file` tool, NEVER paste code in chat. - - Correct: - ``` - write_file --path "src/user.rs" --content "pub struct User { ... }" - ``` - - Wrong: - ``` - Here's the implementation: - \`\`\`rust - pub struct User { ... } - \`\`\` - ``` - - ## File Reading Strategy (IMPORTANT - minimize token usage) - - 1. **Use grep to find relevant code** - `fs_grep --pattern "fn handle_request" --include "*.rs"` finds where things are - 2. **Read only what you need** - `fs_read --path "src/main.rs" --offset 50 --limit 30` reads lines 50-79 - 3. **Never cat entire large files** - If 500+ lines, read the relevant section after grepping for it - 4. **Use glob to find files** - `fs_glob --pattern "*.rs" --path src/` discovers files by name - - ## Pattern Matching - - Before writing ANY file: - 1. Find a similar existing file (use `fs_grep` to locate, then `fs_read` to examine) - 2. Match its style: imports, naming, structure - 3. Follow the same patterns exactly - - ## Verification - - After writing files: - 1. Run `verify_build` to check compilation - 2. If it fails, fix the error (minimal change) - 3. Don't move on until build passes - - ## Completion Signal - - When done, end your response with a summary so the parent agent knows what happened: - - ``` - CODER_COMPLETE: [summary of what was implemented, which files were created/modified, and build status] - ``` - - Or if something went wrong: - ``` - CODER_FAILED: [what went wrong] - ``` - - ## Rules - - 1. **Write code via tools** - Never output code to chat - 2. **Follow patterns** - Read existing files first - 3. **Verify builds** - Don't finish without checking - 4. **Minimal fixes** - If build fails, fix precisely - 5. **No refactoring** - Only implement what's asked - - ## Context - - Project: {{project_dir}} - - CWD: {{__cwd__}} - - Shell: {{__shell__}} - - ## Available tools: - {{__tools__}} \ No newline at end of file diff --git a/assets/agents/coder/graph.yaml b/assets/agents/coder/graph.yaml new file mode 100644 index 0000000..1d5e9df --- /dev/null +++ b/assets/agents/coder/graph.yaml @@ -0,0 +1,278 @@ +name: coder +description: | + Implementation agent. Plans, implements, and runs build + tests in a + bounded fix-loop until verified. Designed to be delegated to by sisyphus. +version: "1.0" + +temperature: 0.1 + +global_tools: + - fs_cat.sh + - fs_ls.sh + - fs_write.sh + - fs_patch.sh + - execute_command.sh + +variables: + - name: project_dir + description: | + Absolute path to the project directory. Defaults to "." which is the + directory you invoked `loki` from. Override at runtime with + `loki -a coder --agent-variable project_dir /abs/path "..."`. + default: "." + +settings: + max_loop_iterations: 20 + log_state_snapshots: true + validate_before_run: true + timeout: 1800 + +initial_state: + project_dir: "" + fix_attempts: 0 + max_fix_attempts: 3 + fix_instructions: "" + build_output: "" + tests_output: "" + last_node_output: "" + plan_summary: "" + files_to_modify: [] + files_to_create: [] + risks: [] + complexity_score: 0 + +start: resolve_paths + +nodes: + resolve_paths: + id: resolve_paths + type: script + description: Resolve project_dir to an absolute path from the agent variable + script: scripts/resolve_paths.sh + timeout: 5 + fallback: end_failure + + analyze_request: + id: analyze_request + type: llm + description: Extract a structured plan and complexity score from the orchestrator's prompt + instructions: | + You are a senior engineer's planning assistant. Read the orchestrator's + request and emit a structured plan. You only plan. You never edit files. + + Score complexity from 1 to 10: + 1-3: trivial - single file, <=20 lines changed, obvious approach + 4-6: moderate - 2-5 files, clear approach, some pattern matching + 7-10: complex - multi-component, ambiguous tradeoffs, refactoring, + or wide blast radius + + Be specific in `files_to_modify` and `files_to_create`. All paths + MUST be absolute. The project root is {{project_dir}}. Prefer paths + like "{{project_dir}}/src/foo.rs" over "src/foo.rs". The implementer + uses these paths directly with fs_write and fs_patch tools, which + resolve relative paths against the loki invocation directory (NOT + the project dir). Empty arrays are fine if no files in that category. + + `risks` is a list of short strings. Anything that could derail the + implementation: unknown dependencies, brittle tests, blast radius, + etc. Empty list is fine. + + Project directory: {{project_dir}} + prompt: "{{initial_prompt}}" + tools: [] + output_schema: + type: object + properties: + plan_summary: + type: string + description: 1-3 sentences summarizing what will be done + files_to_modify: + type: array + items: {type: string} + files_to_create: + type: array + items: {type: string} + complexity_score: + type: integer + minimum: 1 + maximum: 10 + risks: + type: array + items: {type: string} + required: [plan_summary, files_to_modify, files_to_create, complexity_score, risks] + state_updates: + last_node_output: "{{output}}" + fallback: end_failure + next: route_complexity + + route_complexity: + id: route_complexity + type: script + description: Route to approval gate for complex plans; skip otherwise + script: scripts/route_complexity.sh + timeout: 5 + fallback: implement + + gate_approval: + id: gate_approval + type: approval + description: Optional human checkpoint for high-complexity plans + question: | + ## Plan + {{plan_summary}} + + ## Files to modify + {{files_to_modify}} + + ## Files to create + {{files_to_create}} + + ## Risks + {{risks}} + + Complexity: {{complexity_score}}/10 + + Approve this plan? + options: + - "yes" + - "no" + routes: + "yes": implement + "no": end_rejected + on_other: end_rejected + + implement: + id: implement + type: llm + description: Write code via fs tools. Bounded tool-call loop. + instructions: | + You are a senior engineer. Implement the plan by writing code via + tools. Follow existing patterns in the codebase. + + ## Writing code + + 1. Use `fs_patch` for surgical edits to existing files. + 2. Use `fs_write` for new files or full rewrites. + 3. NEVER output code to chat. Always use tools. + 4. ALWAYS pass ABSOLUTE paths to fs_write and fs_patch. Relative + paths resolve against the loki invocation directory (not the + project dir), which is rarely what you want. The project root + is {{project_dir}}. + + ## File reading + + 1. Use `execute_command` to grep/find: + `execute_command --command "grep -rn 'fn handle_request' --include='*.rs' ."` + `execute_command --command "find . -name '*.rs' -not -path '*/target/*'"` + 2. Read only what you need: + `fs_cat --path "src/main.rs" --offset 50 --limit 30` + 3. Never read entire large files. Use offset/limit. + 4. Use `fs_ls` to list directory contents. + + ## Pattern matching + + Before writing ANY file: + 1. Find a similar existing file (grep, then read). + 2. Match its style: imports, naming, structure, error handling. + 3. Follow the same patterns exactly. Do not invent new ones. + + ## Fix loop + + If the "Fix loop status" section in your user prompt is non-empty, + the previous attempt failed verification. Read the error, identify + the minimal fix, apply it. Do not refactor while fixing. + + ## Rules + + 1. Match existing patterns - read examples first. + 2. Minimal changes - implement only what's asked. + 3. Never suppress errors (`as any`, `@ts-ignore`, `#[allow(...)]` + on unfamiliar lints, etc.). + 4. No dead code, no commented-out blocks, no premature abstractions. + 5. End your turn when editing is done. The graph runs verification next. + + Project directory: {{project_dir}} + prompt: | + ## Plan summary + {{plan_summary}} + + ## Files involved + - Modify: {{files_to_modify}} + - Create: {{files_to_create}} + + ## Original request from the orchestrator + {{initial_prompt}} + + ## Fix loop status + {{fix_instructions}} + tools: + - fs_cat + - fs_ls + - fs_write + - fs_patch + - execute_command + max_iterations: 30 + state_updates: + last_node_output: "{{output}}" + fallback: end_failure + next: verify_build + + verify_build: + id: verify_build + type: script + description: Run the project's check/build command. Routes to verify_tests on success, fix_loop_gate on failure. + script: scripts/verify_build.sh + timeout: 300 + fallback: fix_loop_gate + + verify_tests: + id: verify_tests + type: script + description: Run the project's test command. Routes to end_success on pass, fix_loop_gate on failure. + script: scripts/verify_tests.sh + timeout: 600 + fallback: fix_loop_gate + + fix_loop_gate: + id: fix_loop_gate + type: script + description: Budget gate. Loops back to implement with fix_instructions populated, or terminates as end_failure. + script: scripts/fix_loop_gate.sh + timeout: 5 + fallback: end_failure + + end_success: + id: end_success + type: end + output: | + CODER_COMPLETE + Plan: {{plan_summary}} + Files modified: {{files_to_modify}} + Files created: {{files_to_create}} + Build: passed + Tests: passed + + end_rejected: + id: end_rejected + type: end + output: | + CODER_REJECTED + Plan was rejected at the approval gate. + Plan: {{plan_summary}} + + end_failure: + id: end_failure + type: end + output: | + CODER_FAILED + Plan: {{plan_summary}} + Attempts: {{fix_attempts}}/{{max_fix_attempts}} + + Last node output: + {{last_node_output}} + + Last build output: + {{build_output}} + + Last tests output: + {{tests_output}} diff --git a/assets/agents/coder/scripts/fix_loop_gate.sh b/assets/agents/coder/scripts/fix_loop_gate.sh new file mode 100644 index 0000000..29a0bf0 --- /dev/null +++ b/assets/agents/coder/scripts/fix_loop_gate.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then + state=$(cat "$GRAPH_STATE_FILE") +elif [[ -n "${GRAPH_STATE:-}" ]]; then + state="$GRAPH_STATE" +else + state='{}' +fi + +fix_attempts=$(echo "$state" | jq -r '.fix_attempts // 0') +max_fix_attempts=$(echo "$state" | jq -r '.max_fix_attempts // 3') +build_ok=$(echo "$state" | jq -r '.build_ok | if . == null then "true" else (. | tostring) end') +tests_ok=$(echo "$state" | jq -r '.tests_ok | if . == null then "true" else (. | tostring) end') +build_output=$(echo "$state" | jq -r '.build_output // ""') +tests_output=$(echo "$state" | jq -r '.tests_output // ""') + +if (( fix_attempts >= max_fix_attempts )); then + jq -nc \ + --argjson n "$fix_attempts" \ + '{ + "fix_attempts": $n, + "_next": "end_failure" + }' + exit 0 +fi + +next_attempts=$((fix_attempts + 1)) + +if [[ "$build_ok" != "true" ]]; then + fix_instructions=$(printf '## Fix loop status (attempt %d of %d)\n\nThe previous attempt failed the build.\n\nBuild output:\n```\n%s\n```\n\nIdentify the minimal fix and apply it. Do not refactor.' \ + "$next_attempts" "$max_fix_attempts" "$build_output") +elif [[ "$tests_ok" != "true" ]]; then + fix_instructions=$(printf '## Fix loop status (attempt %d of %d)\n\nBuild passed but tests failed.\n\nTest output:\n```\n%s\n```\n\nIdentify the minimal fix and apply it. Do not refactor.' \ + "$next_attempts" "$max_fix_attempts" "$tests_output") +else + fix_instructions=$(printf '## Fix loop status (attempt %d of %d)\n\nfix_loop_gate was reached but no failure was detected in state. Re-run the verification step.' \ + "$next_attempts" "$max_fix_attempts") +fi + +jq -nc \ + --argjson n "$next_attempts" \ + --arg fi "$fix_instructions" \ + '{ + "fix_attempts": $n, + "fix_instructions": $fi, + "_next": "implement" + }' diff --git a/assets/agents/coder/scripts/resolve_paths.sh b/assets/agents/coder/scripts/resolve_paths.sh new file mode 100644 index 0000000..2c9433c --- /dev/null +++ b/assets/agents/coder/scripts/resolve_paths.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}" +resolved=$(cd "$project_dir" 2>/dev/null && pwd) || resolved="$project_dir" + +jq -nc \ + --arg pd "$resolved" \ + '{ + "project_dir": $pd, + "_next": "analyze_request" + }' diff --git a/assets/agents/coder/scripts/route_complexity.sh b/assets/agents/coder/scripts/route_complexity.sh new file mode 100644 index 0000000..d45ca91 --- /dev/null +++ b/assets/agents/coder/scripts/route_complexity.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then + state=$(cat "$GRAPH_STATE_FILE") +elif [[ -n "${GRAPH_STATE:-}" ]]; then + state="$GRAPH_STATE" +else + state='{}' +fi + +complexity=$(echo "$state" | jq -r '.complexity_score // 0') + +if [[ "${CODER_AUTOAPPROVE:-0}" == "1" ]]; then + jq -nc '{"_next": "implement"}' + exit 0 +fi + +if (( complexity >= 7 )); then + jq -nc '{"_next": "gate_approval"}' +else + jq -nc '{"_next": "implement"}' +fi diff --git a/assets/agents/coder/scripts/verify_build.sh b/assets/agents/coder/scripts/verify_build.sh new file mode 100644 index 0000000..f9b9d65 --- /dev/null +++ b/assets/agents/coder/scripts/verify_build.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -uo pipefail + +# shellcheck disable=SC1091 +source "$(dirname "$0")/../../.shared/utils.sh" + +if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then + state=$(cat "$GRAPH_STATE_FILE") +elif [[ -n "${GRAPH_STATE:-}" ]]; then + state="$GRAPH_STATE" +else + state='{}' +fi + +project_dir=$(echo "$state" | jq -r '.project_dir // "."') + +if [[ -n "${BUILD_CMD:-}" ]]; then + cmd="$BUILD_CMD" +else + project_info=$(detect_project "$project_dir") + cmd=$(echo "$project_info" | jq -r '.check // .build // ""') +fi + +if [[ -z "$cmd" || "$cmd" == "null" ]]; then + jq -nc '{ + "build_ok": true, + "build_output": "(no build/check command available for this project type)", + "_next": "verify_tests" + }' + exit 0 +fi + +exit_code=0 +output=$(cd "$project_dir" && eval "$cmd" 2>&1) || exit_code=$? + +if (( exit_code == 0 )); then + jq -nc \ + --arg out "$output" \ + --arg cmd "$cmd" \ + '{ + "build_ok": true, + "build_output": ("Ran: " + $cmd + "\n\n" + $out), + "_next": "verify_tests" + }' +else + jq -nc \ + --arg out "$output" \ + --arg cmd "$cmd" \ + --argjson rc "$exit_code" \ + '{ + "build_ok": false, + "build_output": ("Ran: " + $cmd + "\nExit code: " + ($rc | tostring) + "\n\n" + $out), + "_next": "fix_loop_gate" + }' +fi diff --git a/assets/agents/coder/scripts/verify_tests.sh b/assets/agents/coder/scripts/verify_tests.sh new file mode 100644 index 0000000..102364e --- /dev/null +++ b/assets/agents/coder/scripts/verify_tests.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -uo pipefail + +# shellcheck disable=SC1091 +source "$(dirname "$0")/../../.shared/utils.sh" + +if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then + state=$(cat "$GRAPH_STATE_FILE") +elif [[ -n "${GRAPH_STATE:-}" ]]; then + state="$GRAPH_STATE" +else + state='{}' +fi + +project_dir=$(echo "$state" | jq -r '.project_dir // "."') + +if [[ -n "${TEST_CMD:-}" ]]; then + cmd="$TEST_CMD" +else + project_info=$(detect_project "$project_dir") + cmd=$(echo "$project_info" | jq -r '.test // ""') +fi + +if [[ -z "$cmd" || "$cmd" == "null" ]]; then + jq -nc '{ + "tests_ok": true, + "tests_output": "(no test command available for this project type)", + "_next": "end_success" + }' + exit 0 +fi + +exit_code=0 +output=$(cd "$project_dir" && eval "$cmd" 2>&1) || exit_code=$? + +if (( exit_code == 0 )); then + jq -nc \ + --arg out "$output" \ + --arg cmd "$cmd" \ + '{ + "tests_ok": true, + "tests_output": ("Ran: " + $cmd + "\n\n" + $out), + "_next": "end_success" + }' +else + jq -nc \ + --arg out "$output" \ + --arg cmd "$cmd" \ + --argjson rc "$exit_code" \ + '{ + "tests_ok": false, + "tests_output": ("Ran: " + $cmd + "\nExit code: " + ($rc | tostring) + "\n\n" + $out), + "_next": "fix_loop_gate" + }' +fi diff --git a/assets/agents/coder/tools.sh b/assets/agents/coder/tools.sh index e7cc50a..d1086a7 100755 --- a/assets/agents/coder/tools.sh +++ b/assets/agents/coder/tools.sh @@ -14,99 +14,6 @@ _project_dir() { (cd "${dir}" 2>/dev/null && pwd) || echo "${dir}" } -# Normalize a path to be relative to project root. -# Strips the project_dir prefix if the LLM passes an absolute path. -# Usage: local rel_path; rel_path=$(_normalize_path "/abs/or/rel/path") -_normalize_path() { - local input_path="$1" - local project_dir - project_dir=$(_project_dir) - - if [[ "${input_path}" == /* ]]; then - input_path="${input_path#"${project_dir}"/}" - fi - - input_path="${input_path#./}" - echo "${input_path}" -} - -# @cmd Read a file's contents before modifying -# @option --path! Path to the file (relative to project root) -read_file() { - local file_path - # shellcheck disable=SC2154 - file_path=$(_normalize_path "${argc_path}") - local project_dir - project_dir=$(_project_dir) - local full_path="${project_dir}/${file_path}" - - if [[ ! -f "${full_path}" ]]; then - warn "File not found: ${file_path}" >> "$LLM_OUTPUT" - return 0 - fi - - { - info "Reading: ${file_path}" - echo "" - cat "${full_path}" - } >> "$LLM_OUTPUT" -} - -# @cmd Write complete file contents -# @option --path! Path for the file (relative to project root) -# @option --content! Complete file contents to write -write_file() { - local file_path - file_path=$(_normalize_path "${argc_path}") - # shellcheck disable=SC2154 - local content="${argc_content}" - local project_dir - project_dir=$(_project_dir) - local full_path="${project_dir}/${file_path}" - - mkdir -p "$(dirname "${full_path}")" - printf '%s' "${content}" > "${full_path}" - - green "Wrote: ${file_path}" >> "$LLM_OUTPUT" -} - -# @cmd Find files similar to a given path (for pattern matching) -# @option --path! Path to find similar files for -find_similar_files() { - local file_path - file_path=$(_normalize_path "${argc_path}") - local project_dir - project_dir=$(_project_dir) - - local ext="${file_path##*.}" - local dir - dir=$(dirname "${file_path}") - - info "Similar files to: ${file_path}" >> "$LLM_OUTPUT" - echo "" >> "$LLM_OUTPUT" - - local results - results=$(find "${project_dir}/${dir}" -maxdepth 1 -type f -name "*.${ext}" \ - ! -name "$(basename "${file_path}")" \ - ! -name "*test*" \ - ! -name "*spec*" \ - 2>/dev/null | sed "s|^${project_dir}/||" | head -3) - - if [[ -z "${results}" ]]; then - results=$(find "${project_dir}/src" -type f -name "*.${ext}" \ - ! -name "*test*" \ - ! -name "*spec*" \ - -not -path '*/target/*' \ - 2>/dev/null | sed "s|^${project_dir}/||" | head -3) - fi - - if [[ -n "${results}" ]]; then - echo "${results}" >> "$LLM_OUTPUT" - else - warn "No similar files found" >> "$LLM_OUTPUT" - fi -} - # @cmd Verify the project builds successfully verify_build() { local project_dir @@ -189,28 +96,3 @@ get_project_structure() { } >> "$LLM_OUTPUT" } -# @cmd Search for content in the codebase -# @option --pattern! Pattern to search for -search_code() { - # shellcheck disable=SC2154 - local pattern="${argc_pattern}" - local project_dir - project_dir=$(_project_dir) - - info "Searching: ${pattern}" >> "$LLM_OUTPUT" - echo "" >> "$LLM_OUTPUT" - - local results - results=$(grep -rn "${pattern}" "${project_dir}" 2>/dev/null | \ - grep -v '/target/' | \ - grep -v '/node_modules/' | \ - grep -v '/.git/' | \ - sed "s|^${project_dir}/||" | \ - head -20) || true - - if [[ -n "${results}" ]]; then - echo "${results}" >> "$LLM_OUTPUT" - else - warn "No matches" >> "$LLM_OUTPUT" - fi -} \ No newline at end of file diff --git a/assets/agents/deep-research/README.md b/assets/agents/deep-research/README.md new file mode 100644 index 0000000..fbb1f2f --- /dev/null +++ b/assets/agents/deep-research/README.md @@ -0,0 +1,274 @@ +# deep-research + +A deep web research agent, built as a Loki graph agent. It plans an +investigation, decomposes it into sub-questions researched in +parallel, grounds the work in a local knowledge corpus, vets the +credibility of cited sources, runs a reflexion self-critique loop to +revise weak findings, delegates the final write-up to a focused +sub-agent, checks that the cited sources are reachable, and gates the +result behind human approval. + +Unlike a regular agent (which takes a goal and improvises the steps), +this agent runs a fixed graph: every request goes through the same +`plan -> parallel research -> vet -> critique -> synthesize -> verify -> approve` +pipeline. + +This agent is also the **canonical reference for the Loki graph +system**: it exercises every node type (`script`, `llm`, `rag`, `map`, +`agent`, `input`, `approval`, `end`) and both static fan-out and +dynamic `map` fan-out. If you are learning how to build a graph +agent, this is the file to read alongside the +[Graph-Agents wiki](https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents). + +## Workflow + +17 nodes. `->` is the static route; a script node can also route +dynamically via `_next`. The `▶▶` line is a parallel super-step — +those branches run concurrently: + +``` +parse_request (script) -> bootstrap_research (or -> ask_topic if no topic) +ask_topic (input) -> bootstrap_research +bootstrap_research (script) -> [plan, knowledge_lookup] ▶▶ parallel +plan (llm + output_schema) -> research_each_question +knowledge_lookup (rag) -> research_each_question +research_each_question (map) -> combine_findings (spawns one branch per question) + └─ research_one_question (llm) (atomic; runs N×, joins at map) +combine_findings (script) -> vet_sources +vet_sources (llm + custom tool) -> critique +critique (llm) -> reflexion_gate +reflexion_gate (script) -> synthesize (or -> research_each_question: reflexion loop) +synthesize (agent: report-writer) -> verify_sources +verify_sources (script) -> approve +approve (approval) -> end_accepted ("accept") + -> end_rejected ("reject") + -> incorporate_feedback (any free-form answer) +incorporate_feedback (script) -> research_each_question (the human-feedback loop) +``` + +### Node-type breakdown + +| Type | Nodes | +|---|---| +| `script` (Python) | `parse_request`, `bootstrap_research`, `combine_findings`, `reflexion_gate`, `verify_sources`, `incorporate_feedback` | +| `llm` (tools: `[]`) | `plan`, `critique` | +| `llm` (with tool whitelist) | `research_one_question`, `vet_sources` | +| `rag` | `knowledge_lookup` — local corpus retrieval | +| `map` | `research_each_question` — dynamic fan-out per sub-question | +| `agent` | `synthesize` — spawns the `report-writer` sub-agent | +| `input` | `ask_topic` | +| `approval` | `approve` | +| `end` | `end_accepted`, `end_rejected` | + +## Parallel execution + +The graph has two parallel super-steps where Loki's BSP scheduler runs +branches concurrently. + +**1. Context loading (`plan` ‖ `knowledge_lookup`)** — after +`bootstrap_research`, the LLM planner (which decomposes the topic into +sub-questions) and the RAG retrieval over the local `knowledge/` +corpus run side by side. They write disjoint state keys (`plan` writes +`research_plan` and `questions`; `knowledge_lookup` writes +`local_context` and `local_sources`) so no reducer is needed. + +**2. Per-question research (`research_each_question` map)** — the +plan emits a `questions` array (3-5 entries, enforced by its +`output_schema`). The `map` node spawns one parallel branch per +question (`max_concurrency: 3`). Each branch is an isolated +`research_one_question` LLM invocation with web tools, instructed to +investigate exactly its assigned question. Outputs collect into +`question_findings` in input order, then `combine_findings` joins +them into a single `findings` Markdown document for downstream nodes. + +`settings.max_concurrency: 4` is the graph-wide cap; the per-`map` +override (`max_concurrency: 3` on `research_each_question`) is +deliberately lower to leave headroom for the planner's tool calls +running alongside RAG. + +## Local knowledge corpus + +`knowledge_lookup` is a `rag` node — it runs hybrid (vector + keyword) +retrieval over every file in `knowledge/`. The directory ships with a +small `research-style-notes.md` so the RAG node has something to +retrieve against on a clean install; drop your own Markdown notes, +PDFs, or text files into `knowledge/` to bias the research toward +your local context. + +The knowledge base is built once, at agent-load time, into +`~/.config/loki/agents/deep-research/knowledge_lookup.yaml`. Because +the node fully specifies its build config (`embedding_model`, +`chunk_size`, `chunk_overlap`), the build is non-interactive. Delete +that cached file after adding or changing knowledge to force a +rebuild. + +## Sub-agent: report-writer + +The `synthesize` node is an `agent` node that spawns the +`report-writer` sub-agent (`assets/agents/report-writer/`). This is +the agent-as-tool pattern: the orchestrating graph delegates the +writing phase to a focused sub-agent dedicated to coherent prose, +while the research phase uses different (typically cheaper) LLM nodes +for fast-and-many-question investigation. + +The `report-writer` sub-agent has no tools — it cannot access the +web, cannot search, and cannot invent facts. It reads only the +findings it is given and produces a final Markdown report preserving +every inline citation. See `assets/agents/report-writer/README.md` +for details. + +## Tools and tool scoping + +This agent demonstrates Loki's three tool sources and how an `llm` +node's `tools:` whitelist scopes them per node. + +The agent's full tool universe, declared in `graph.yaml`: + +- **Global tools** (`global_tools`): `web_search_loki`, + `fetch_url_via_curl`, `search_arxiv` - Loki's built-in tool scripts. +- **MCP server** (`mcp_servers`): `ddg-search` - a DuckDuckGo web + search MCP server. Referenced in a whitelist as `mcp:ddg-search`. +- **Custom agent tool** (`tools.sh`): `classify_source` - a + deterministic source-credibility classifier shipped with this agent. + +No node receives all of these. Each `llm` node's `tools:` whitelist +narrows the universe to exactly what that step needs: + +| Node | `tools:` whitelist | Draws from | +|---|---|---| +| `plan`, `critique` | `[]` | nothing - pure reasoning | +| `research_one_question` | `web_search_loki`, `fetch_url_via_curl`, `search_arxiv`, `mcp:ddg-search` | global tools + MCP | +| `vet_sources` | `classify_source` | the custom tool only | + +`research_one_question` (each parallel branch of the map) can search +and fetch but cannot classify sources; `vet_sources` can classify +sources but cannot touch the web. That separation is the point of the +`tools:` whitelist: a node gets only the tools its job calls for, +never the agent's full set. + +The `classify_source` custom tool (`tools.sh`) takes a URL and returns +a credibility tier (government, academic, preprint, organization, +unverified) derived from the host and top-level domain. It is +deterministic - exactly the kind of logic a tool should own rather than +the LLM guessing. + +Web search may require API-key configuration; see the +[Tools](https://github.com/Dark-Alex-17/loki/wiki/Tools) docs. +`fetch_url_via_curl`, `search_arxiv`, and `classify_source` work +without a key. + +## Setup + +`research_one_question` (each parallel branch of the `map`) uses the +`ddg-search` MCP server via `mcp:ddg-search`. It is one of Loki's +default MCP servers; make sure it is registered in +`~/.config/loki/mcp.json` (run `loki --install mcp_config` to restore +the default template if it is missing). If `ddg-search` is unavailable, +the branches still have their global web-search tools to fall back on. + +The `synthesize` node spawns the `report-writer` sub-agent. Both +agents ship with `loki agents install`; if you install one manually, +install both so the agent reference resolves. + +## Reflexion + +The agent has two loops, both built with script nodes that route via +`_next`. The engine allows back-edges at runtime; the validator only +rejects cycles built from static `next` / `routes` edges, so script +`_next` loops are always allowed. + +**Automated reflexion loop.** After the parallel research map and +`vet_sources`, the `critique` node reviews the merged findings +against the research plan and the source credibility assessment, and +emits `VERDICT: PASS` or `VERDICT: REVISE` with specific feedback. +`reflexion_gate.py` then: + +- `PASS` -> continue to `synthesize`. +- `REVISE`, budget remaining -> loop back to `research_each_question`, + with the critique injected as `research_feedback` so every parallel + branch sees it on the retry. +- `REVISE`, budget spent -> continue to `synthesize` anyway (the human + approval step is the final backstop). + +The budget is `MAX_REFLEXION_REVISIONS` in `reflexion_gate.py` +(default 2, so the research map runs at most 3 times per pass). + +**Human-feedback loop.** At `approve` the user answers `accept`, +`reject`, or types their own feedback. A free-form answer routes via +the approval node's `on_other` to `incorporate_feedback.py`, which +folds that text into `research_feedback` and loops back to +`research_each_question` for another parallel pass. + +`settings.max_loop_iterations` (40) is the engine's infinite-loop +backstop: it caps the total visits to any single node. + +## Running + +```sh +loki agents install # ships deep-research +loki -a deep-research "How does HTTP/3 differ from HTTP/2?" +loki -a deep-research "Recent advances in solid-state batteries" +loki -a deep-research # no prompt -> triggers ask_topic +``` + +## Anti-hallucination + +- `research_one_question` (each map branch) is instructed to back + every claim with a real retrieved source and never to fabricate + URLs, titles, or DOIs. +- `vet_sources` classifies every cited source so weak sources are + visible to the critique step. +- `critique` independently reviews the merged findings and sends weak + or uncited work back for another parallel research pass. +- `synthesize` (the `report-writer` sub-agent) is grounded: it may use + only the gathered findings and must keep each claim's inline source. + It has no tools and cannot browse the web. +- `verify_sources` probes every cited URL / DOI with an HTTP HEAD + request and reports which are unreachable, so the human reviewer + sees broken citations before approving. + +## Customizing + +- **Loop budget.** `MAX_REFLEXION_REVISIONS` in `reflexion_gate.py`. +- **Map concurrency.** The `research_each_question` node's + `max_concurrency: 3` caps simultaneous web-research branches. + Raise to investigate more questions in parallel; lower to be gentle + on rate-limited providers. +- **Per-node model.** Add `model: anthropic:...` to any `llm` node. + Cheap models work well for `plan` / `critique` / `vet_sources`; the + heavy intelligence is needed in `research_one_question` and the + `report-writer` sub-agent. +- **Tool scope.** Narrow the `research_one_question` node's `tools:` + list to constrain where each branch looks (for example, drop + `web_search_loki` and `mcp:ddg-search` to force arXiv-only + research). +- **Local knowledge.** Drop files into `knowledge/` to bias every + research branch toward your local context (see the *Local + knowledge corpus* section above). +- **Different writer.** Replace `agent: report-writer` on the + `synthesize` node with the name of any other agent. The + orchestrator does not care what kind of agent the writer is. +- **Skip approval.** Point both `approve` routes at `end_accepted`, + or wire `verify_sources` straight to an `end` node. + +## Files + +``` +assets/agents/deep-research/ + graph.yaml - agent config + 17-node workflow + tools.sh - classify_source custom tool + README.md - this file + knowledge/ + README.md - corpus-format notes + research-style-notes.md - starter knowledge file (replace with your notes) + scripts/ + parse_request.py - _next: bootstrap_research, or ask_topic if no topic + bootstrap_research.py - fan-out source: next [plan, knowledge_lookup] + combine_findings.py - joins map output (question_findings) into findings + reflexion_gate.py - _next: research_each_question (revise) or synthesize + verify_sources.py - HTTP HEAD on cited URLs / DOIs + incorporate_feedback.py - _next: research_each_question, with user feedback +``` + +See also `assets/agents/report-writer/` — the sub-agent the +`synthesize` node spawns. diff --git a/assets/agents/deep-research/graph.yaml b/assets/agents/deep-research/graph.yaml new file mode 100644 index 0000000..7c8f9e6 --- /dev/null +++ b/assets/agents/deep-research/graph.yaml @@ -0,0 +1,293 @@ +name: deep-research +description: | + Deep web research workflow. Plans an investigation, decomposes it + into sub-questions researched in parallel, grounds the work in a + local knowledge corpus, vets the credibility of cited sources, runs + a reflexion self-critique loop to revise weak or incomplete findings, + delegates the final write-up to a focused sub-agent, checks that the + cited sources are reachable, and gates the result behind human + approval. A reviewer's free-form feedback at the approval step feeds + back into another research pass. + + This is the canonical Loki graph-agent reference: it exercises every + node type (script, llm, rag, map, agent, input, approval, end) and + both static fan-out and dynamic map fan-out. + +version: "1.0" + +temperature: 0.0 + +global_tools: + - web_search_loki.sh + - fetch_url_via_curl.sh + - search_arxiv.sh + +mcp_servers: + - ddg-search + +conversation_starters: + - "How does HTTP/3 differ from HTTP/2?" + - "Summarize recent advances in solid-state battery chemistry" + +settings: + max_loop_iterations: 40 + log_state_snapshots: false + validate_before_run: true + max_concurrency: 4 + +initial_state: + research_feedback: "" + research_attempts: 0 + local_context: "" + local_sources: "" + +start: parse_request + +nodes: + + parse_request: + id: parse_request + type: script + script: scripts/parse_request.py + next: bootstrap_research + + ask_topic: + id: ask_topic + type: input + question: "What would you like me to research?" + validation: "len(input) > 0" + state_updates: + topic: "{{input}}" + next: bootstrap_research + + bootstrap_research: + id: bootstrap_research + type: script + script: scripts/bootstrap_research.py + next: [plan, knowledge_lookup] + + plan: + id: plan + type: llm + instructions: | + You are a research planner. Given a topic, produce a focused + research plan and decompose it into 3-5 specific sub-questions + that can each be researched independently in parallel. + + The plan is a short narrative naming the key questions and the + kinds of sources that would be authoritative. The sub-questions + are precise, self-contained queries (each one is sent on its own + to a separate research worker, so they must be answerable + without each other's context). + prompt: "Research topic: {{topic}}" + tools: [] + output_schema: + type: object + properties: + research_plan: + type: string + description: A short plan narrative. + questions: + type: array + items: { type: string } + minItems: 1 + maxItems: 6 + description: 3-5 specific, self-contained sub-questions. + required: [research_plan, questions] + next: research_each_question + + knowledge_lookup: + id: knowledge_lookup + type: rag + documents: + - ./knowledge/ + query: "{{topic}}" + top_k: 6 + chunk_size: 1000 + chunk_overlap: 100 + state_updates: + local_context: "{{output.context}}" + local_sources: "{{output.sources}}" + next: research_each_question + + research_each_question: + id: research_each_question + type: map + over: "{{questions}}" + as: question + branch: research_one_question + collect_into: question_findings + max_concurrency: 3 + next: combine_findings + + research_one_question: + id: research_one_question + type: llm + instructions: | + You are a web research assistant. Investigate the SINGLE question + given to you using your tools: search the web, fetch and read + pages, and search arXiv for academic sources. + + Rules: + - Every factual claim must be backed by a real source you + actually retrieved. Never fabricate URLs, page titles, + authors, or DOIs. + - Prefer primary and authoritative sources over aggregators. + - Where sources disagree, report the disagreement rather than + papering over it. + - Put the URL (or DOI) inline next to each claim it supports. + + Return organized findings in plain text. Do not include + meta-commentary about the process. + prompt: | + Research question: {{question}} + + Local context that may help: + {{local_context}} + + {{research_feedback}} + tools: + - web_search_loki + - fetch_url_via_curl + - search_arxiv + - mcp:ddg-search + max_iterations: 10 + max_attempts: 2 + temperature: 0.1 + + combine_findings: + id: combine_findings + type: script + script: scripts/combine_findings.py + next: vet_sources + + vet_sources: + id: vet_sources + type: llm + instructions: | + You assess the credibility of the sources cited in a set of + research findings. For every distinct source URL in the findings, + call the `classify_source` tool to get its credibility tier. Then + summarize: which claims rest on HIGH-credibility sources, and + which rest on PREPRINT or UNVERIFIED sources and so need + corroboration. Do NOT do any new research -- assess only what is + already cited. + prompt: | + Findings to assess: + {{findings}} + tools: + - classify_source + max_iterations: 15 + state_updates: + source_assessment: "{{output}}" + next: critique + + critique: + id: critique + type: llm + instructions: | + You are a meticulous research reviewer. Judge whether the + findings below are good enough to synthesize a complete, + well-supported report that answers the research plan. + + Mark the findings REVISE if ANY of these hold: + - A research-plan question is unanswered or only weakly + addressed. + - A factual claim has no source, or cites a source that looks + fabricated. + - The findings lean on a single source where corroboration is + needed. + - A key claim rests only on a PREPRINT or UNVERIFIED source, + per the source credibility assessment below. + - An obvious counter-perspective or recent development is + missing. + Otherwise mark them PASS. + + Respond in EXACTLY this format, nothing else: + + VERDICT: + FEEDBACK: + prompt: | + Research plan: + {{research_plan}} + + Findings under review: + {{findings}} + + Source credibility assessment: + {{source_assessment}} + tools: [] + state_updates: + critique: "{{output}}" + next: reflexion_gate + + reflexion_gate: + id: reflexion_gate + type: script + script: scripts/reflexion_gate.py + next: synthesize + + synthesize: + id: synthesize + type: agent + agent: report-writer + prompt: | + Research topic: {{topic}} + + Findings (organized by sub-question, with inline citations): + {{findings}} + + Source credibility assessment: + {{source_assessment}} + + Produce the final report following your instructions. + timeout: 300 + state_updates: + report: "{{output}}" + next: verify_sources + + verify_sources: + id: verify_sources + type: script + script: scripts/verify_sources.py + next: approve + + approve: + id: approve + type: approval + question: | + Research report on: {{topic}} + + {{report}} + + ---- + {{source_check}} + ---- + + Accept this report? Pick "accept" or "reject", or type specific + feedback to send the research back for another pass. + options: + - "accept" + - "reject" + routes: + "accept": end_accepted + "reject": end_rejected + on_other: incorporate_feedback + state_updates: + decision: "{{choice}}" + + incorporate_feedback: + id: incorporate_feedback + type: script + script: scripts/incorporate_feedback.py + + end_accepted: + id: end_accepted + type: end + output: "{{report}}" + + end_rejected: + id: end_rejected + type: end + output: "Research on '{{topic}}' was rejected and discarded." diff --git a/assets/agents/deep-research/knowledge/README.md b/assets/agents/deep-research/knowledge/README.md new file mode 100644 index 0000000..52b578b --- /dev/null +++ b/assets/agents/deep-research/knowledge/README.md @@ -0,0 +1,23 @@ +# Local knowledge corpus for deep-research + +The `knowledge_lookup` node in `graph.yaml` is a `rag` node that runs +hybrid (vector + keyword) retrieval over every file in this directory. +Drop your own notes, papers (PDFs), Markdown docs, or text files here +and they will be indexed into a per-agent knowledge base on first run. + +Loki supports common file types out of the box: `.md`, `.txt`, `.pdf`, +`.html`, and others. Subdirectories are walked recursively. + +A small starter file (`research-style-notes.md`) ships so the RAG +node has something non-empty to retrieve against on a clean install. +Replace or extend it with your own materials to bias the research +phase toward your local context. + +To force the knowledge base to rebuild after you add or change files, +delete the cached index: + +```sh +rm ~/.config/loki/agents/deep-research/knowledge_lookup.yaml +``` + +The next run will rebuild from the current contents of this directory. diff --git a/assets/agents/deep-research/knowledge/research-style-notes.md b/assets/agents/deep-research/knowledge/research-style-notes.md new file mode 100644 index 0000000..a64848d --- /dev/null +++ b/assets/agents/deep-research/knowledge/research-style-notes.md @@ -0,0 +1,49 @@ +# Research style notes + +These are general principles the `deep-research` agent should keep in +mind regardless of topic. Replace this file with your own notes if you +want to bias retrieval toward your local context. + +## What "good research" means here + +- **Every factual claim cites a source you actually retrieved.** Never + fabricate URLs, page titles, authors, or DOIs. +- **Primary sources beat aggregators.** Prefer the original paper, the + RFC, the standards body, or the manufacturer over a blog summarizing + them. +- **Corroboration matters where stakes are high.** If a single source + makes a strong claim, look for a second independent source before + taking it as established. +- **Disagreement is information, not noise.** If two credible sources + disagree, report the disagreement and the reasoning on each side. +- **Old does not mean wrong.** A 2014 RFC is still authoritative if no + newer one has obsoleted it; check before assuming a source is stale. + +## Source-tier heuristics + +The `vet_sources` node uses these rough tiers to weigh credibility. +The custom tool `classify_source` (see `tools.sh`) implements this +deterministically by hostname / TLD. + +- **HIGH:** government domains (`.gov`, `.mil`), academic institutions + (`.edu`, university subdomains), peer-reviewed journals, standards + bodies (IETF/RFCs, W3C, ISO, IEEE, NIST), and primary documents from + the entities being researched (e.g. a vendor's official spec page). +- **PREPRINT:** arXiv, bioRxiv, medRxiv, SSRN. Useful but not yet + peer-reviewed; treat numeric claims with extra caution. +- **ORGANIZATION:** established nonprofits, standards-adjacent groups, + industry consortia. Reliable for their stated mission but may have a + perspective. +- **UNVERIFIED:** general web pages, blogs, news aggregators, social + media. Useful for leads but should not be the only source for a + factual claim. + +## Common pitfalls to flag in critique + +- A claim cited only to a PREPRINT or UNVERIFIED source on a numeric + or contested point. +- A research-plan question that the findings address only obliquely. +- "Findings" that paraphrase a single source three times rather than + triangulating. +- Citation collisions where two sources are listed but turn out to + be the same study reported via different aggregators. diff --git a/assets/agents/deep-research/scripts/bootstrap_research.py b/assets/agents/deep-research/scripts/bootstrap_research.py new file mode 100644 index 0000000..d230afc --- /dev/null +++ b/assets/agents/deep-research/scripts/bootstrap_research.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +"""Fan-out source for context loading. + +Has no logic of its own. Exists so the static `next: [plan, knowledge_lookup]` +list on this node fans out into two parallel branches (the LLM planner and +the RAG knowledge lookup) as a single super-step. The validator requires +declared parallel-branch script outputs, so we emit an empty JSON object +explicitly here. +""" +import json + + +def main(): + print(json.dumps({})) + + +if __name__ == "__main__": + main() diff --git a/assets/agents/deep-research/scripts/combine_findings.py b/assets/agents/deep-research/scripts/combine_findings.py new file mode 100644 index 0000000..b55d7aa --- /dev/null +++ b/assets/agents/deep-research/scripts/combine_findings.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Join the per-question map outputs into a single `findings` string. + +The `research_each_question` map writes `question_findings` (an array, +one entry per sub-question, in input order). Downstream nodes +(`vet_sources`, `critique`, `synthesize`) read `{{findings}}` as a +single block, so this script renders the array as a Markdown document +with one section per question. +""" +import json +import os + + +def load_state(): + path = os.environ.get("GRAPH_STATE_FILE") + if path: + with open(path) as f: + return json.load(f) + return json.loads(os.environ.get("GRAPH_STATE", "{}")) + + +def main(): + state = load_state() + questions = state.get("questions") or [] + per_question = state.get("question_findings") or [] + + sections = [] + for idx, q in enumerate(questions): + body = per_question[idx] if idx < len(per_question) else "" + if isinstance(body, dict) or isinstance(body, list): + body = json.dumps(body, indent=2) + sections.append(f"## {q}\n\n{body}") + + findings = "\n\n".join(sections) if sections else "No findings gathered." + print(json.dumps({"findings": findings})) + + +if __name__ == "__main__": + main() diff --git a/assets/agents/deep-research/scripts/incorporate_feedback.py b/assets/agents/deep-research/scripts/incorporate_feedback.py new file mode 100644 index 0000000..27c22f2 --- /dev/null +++ b/assets/agents/deep-research/scripts/incorporate_feedback.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Fold a reviewer's free-form feedback back into the research loop. + +Runs when the user answers the approval step with their own text +instead of "accept" or "reject". That text (saved by the approval node +as `decision`) becomes `research_feedback`, and the graph loops back to +`research_each_question` for another informed pass (each sub-question is +re-researched in parallel with the new feedback in context). The +reflexion counter is reset so the user-driven pass gets a fresh revision +budget. + +Routing (`_next`): always research_each_question. +""" +import json +import os + + +def load_state(): + path = os.environ.get("GRAPH_STATE_FILE") + if path: + with open(path) as f: + return json.load(f) + return json.loads(os.environ.get("GRAPH_STATE", "{}")) + + +def main(): + state = load_state() + feedback = (state.get("decision") or "").strip() + output = { + "_next": "research_each_question", + "research_attempts": 0, + "research_feedback": ( + "The user reviewed the report and asked for changes. Treat " + "this as the top priority for the next pass:\n\n" + feedback + ), + } + print(json.dumps(output)) + + +if __name__ == "__main__": + main() diff --git a/assets/agents/deep-research/scripts/parse_request.py b/assets/agents/deep-research/scripts/parse_request.py new file mode 100644 index 0000000..faae28c --- /dev/null +++ b/assets/agents/deep-research/scripts/parse_request.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Entry router for deep-research. + +Reads the caller's prompt from state. If it contains a usable research +topic, stores it as `topic` and falls through to the static `next` +(plan). If the prompt is empty, routes to `ask_topic` so the user can +supply one interactively. + +Routing (`_next`): + - prompt present -> (no _next; static next: plan) + - prompt empty -> ask_topic +""" +import json +import os + + +def load_state(): + path = os.environ.get("GRAPH_STATE_FILE") + if path: + with open(path) as f: + return json.load(f) + return json.loads(os.environ.get("GRAPH_STATE", "{}")) + + +def main(): + state = load_state() + prompt = (state.get("initial_prompt") or "").strip() + if prompt: + print(json.dumps({"topic": prompt})) + else: + print(json.dumps({"_next": "ask_topic"})) + + +if __name__ == "__main__": + main() diff --git a/assets/agents/deep-research/scripts/reflexion_gate.py b/assets/agents/deep-research/scripts/reflexion_gate.py new file mode 100644 index 0000000..2dd1e6b --- /dev/null +++ b/assets/agents/deep-research/scripts/reflexion_gate.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Reflexion gate for deep-research. + +Runs after `critique` has reviewed the current research findings. If the +critique's verdict is REVISE and the reflexion budget is not spent, +loops back to `research` with the critique attached as +`research_feedback`, so the retry is informed rather than a blind +re-run. Otherwise it proceeds to `synthesize`. + +Routing (`_next`): + - verdict PASS -> synthesize + - verdict REVISE, budget remaining -> research_each_question (+ research_feedback) + - verdict REVISE, budget spent -> synthesize + +Reflexion is a best-effort quality booster, not a hard gate: once the +budget is spent the workflow proceeds anyway, and the human approval +step is the final backstop. +""" +import json +import os +import re + +# Automated revision passes allowed. `research` runs at most +# MAX_REFLEXION_REVISIONS + 1 times per user pass. Bump to allow more. +MAX_REFLEXION_REVISIONS = 2 + + +def load_state(): + path = os.environ.get("GRAPH_STATE_FILE") + if path: + with open(path) as f: + return json.load(f) + return json.loads(os.environ.get("GRAPH_STATE", "{}")) + + +def as_int(value, default=0): + try: + return int(value) + except (TypeError, ValueError): + return default + + +def parse_verdict(critique): + """Pull PASS/REVISE from the critique's `VERDICT:` line. Defaults to + PASS when no verdict line is found, so a malformed critique lets the + workflow proceed instead of burning the whole revision budget.""" + match = re.search(r"VERDICT:\s*([A-Za-z]+)", critique, re.IGNORECASE) + if not match: + return "PASS" + return match.group(1).upper() + + +def main(): + state = load_state() + critique = state.get("critique") or "" + verdict = parse_verdict(critique) + attempts = as_int(state.get("research_attempts")) + + if verdict == "REVISE" and attempts < MAX_REFLEXION_REVISIONS: + feedback = ( + "A reviewer judged the previous research pass incomplete. " + "Address every point in the critique below:\n\n" + critique + ) + output = { + "_next": "research_each_question", + "research_attempts": attempts + 1, + "research_feedback": feedback, + } + else: + output = {"_next": "synthesize"} + + print(json.dumps(output)) + + +if __name__ == "__main__": + main() diff --git a/assets/agents/deep-research/scripts/verify_sources.py b/assets/agents/deep-research/scripts/verify_sources.py new file mode 100644 index 0000000..9828341 --- /dev/null +++ b/assets/agents/deep-research/scripts/verify_sources.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Check that the sources cited in the research report are reachable. + +Scans the final report for URLs and DOIs, probes each with a HEAD +request, and writes a `source_check` summary into state so the human +reviewer sees broken citations at the approval step. + +Times out per request so a slow source cannot stall the graph. +""" +import json +import os +import re +import urllib.error +import urllib.request + +DOI_RE = re.compile(r"\b(10\.\d{4,9}/[-._;()/:A-Z0-9]+)", re.IGNORECASE) +URL_RE = re.compile(r"https?://[^\s)\]\}\"'>]+") + + +def load_state(): + path = os.environ.get("GRAPH_STATE_FILE") + if path: + with open(path) as f: + return json.load(f) + return json.loads(os.environ.get("GRAPH_STATE", "{}")) + + +def reachable(url, timeout=5.0): + req = urllib.request.Request(url, method="HEAD") + try: + with urllib.request.urlopen(req, timeout=timeout) as resp: + return 200 <= resp.status < 400 + except urllib.error.HTTPError as e: + return 200 <= e.code < 400 + except Exception: + return False + + +def main(): + state = load_state() + report = state.get("report") or "" + + urls = sorted({u.rstrip(".,;)") for u in URL_RE.findall(report)}) + dois = sorted(set(DOI_RE.findall(report))) + + results = [] + for url in urls: + ok = reachable(url) + results.append(f" {'OK' if ok else 'UNREACHABLE'} {url}") + for doi in dois: + url = f"https://doi.org/{doi}" + if url in urls: + continue + ok = reachable(url) + results.append(f" {'OK' if ok else 'UNREACHABLE'} DOI {doi} ({url})") + + if not results: + summary = "No web sources were cited in the report." + else: + summary = ( + f"Source reachability ({len(results)} checked):\n" + + "\n".join(results) + ) + + print(json.dumps({"source_check": summary})) + + +if __name__ == "__main__": + main() diff --git a/assets/agents/deep-research/tools.sh b/assets/agents/deep-research/tools.sh new file mode 100644 index 0000000..b715364 --- /dev/null +++ b/assets/agents/deep-research/tools.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -e + +# @env LLM_OUTPUT=/dev/stdout The output path + +# @cmd Classify the credibility tier of a web source from its URL. +# A deterministic check based on the host and top-level domain. Use it +# to weigh how much trust to place in a source before relying on it. +# @option --url! The full source URL to classify +classify_source() { + # shellcheck disable=SC2154 + local url="$argc_url" + local host="${url#*://}" + host="${host%%/*}" + host="${host##*@}" + host="${host%%:*}" + host="$(printf '%s' "$host" | tr '[:upper:]' '[:lower:]')" + + local tier + case "$host" in + '') + tier="UNKNOWN - no host could be parsed from the URL" ;; + *.gov | *.gov.* | *.mil) + tier="HIGH - government source" ;; + *.edu | *.edu.* | *.ac.*) + tier="HIGH - academic institution" ;; + arxiv.org | *.arxiv.org | biorxiv.org | *.biorxiv.org | medrxiv.org | *.medrxiv.org | ssrn.com | *.ssrn.com) + tier="PREPRINT - not yet peer reviewed, corroborate before citing" ;; + wikipedia.org | *.wikipedia.org) + tier="TERTIARY - encyclopedia, good for orientation not citation" ;; + *.org | *.org.*) + tier="MEDIUM - organization site, check for institutional bias" ;; + *) + tier="UNVERIFIED - general web source, corroborate before citing" ;; + esac + + printf '%s: %s\n' "${host:-}" "$tier" >> "$LLM_OUTPUT" +} diff --git a/assets/agents/jira-helper/README.md b/assets/agents/jira-helper/README.md deleted file mode 100644 index 2db1697..0000000 --- a/assets/agents/jira-helper/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Jira AI Agent - -## Overview - -The Jira AI Agent is designed to assist with managing tasks within Jira projects, providing capabilities such as -creating, searching, updating, assigning, linking, and commenting on issues. Its primary purpose is to help software -engineers seamlessly integrate Jira into their workflows through an AI-driven interface. - -## Configuration -This agent uses the official [Atlassian MCP Server](https://github.com/atlassian/atlassian-mcp-server). To use it, -ensure you have Node.js v18+ installed to run the local MCP proxy (`mcp-remote`). - -The server uses OAuth 2.0 so it will automatically open your browser for you to sign in to your account. No manual -configuration is necessary! diff --git a/assets/agents/jira-helper/config.yaml b/assets/agents/jira-helper/config.yaml deleted file mode 100644 index 3e50717..0000000 --- a/assets/agents/jira-helper/config.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Jira Agent -description: An AI agent that can assist with Jira tasks such as creating issues, searching for issues, and updating issues. -version: 0.1.0 -agent_session: temp -mcp_servers: - - atlassian -instructions: | - You are a AI agent designed to assist with managing Jira tasks and helping software engineers utilize and integrate - Jira into their workflows. You can create, search, update, assign, link, and comment on issues in Jira. - - ## Create Issue (MANDATORY when creating a issue) - When a user prompts you to create a Jira issue: - 1. Prompt the user for what Jira project they want the ticket created in - 2. If the ticket type requires a parent issue: - a. Query Jira for potentially relevant parents - b. Prompt user for which parent to use, displaying the suggested list of parent issues - 3. Create the issue with the following format: - ```markdown - **Description:** - This section gives context and details about the issue. - **User Acceptance Criteria:** - # This section provides bullet points that function like a checklist of all the things that must be completed in - # order for the issue to be considered done. - * Example criteria one - * Example criteria two - ``` - 4. Ask the user if the issue should be assigned to them - a. If yes, then assign the user to the newly created issue - - - Available tools: - {{__tools__}} -conversation_starters: - - What are the latest issues in my Jira project? - - Can you create a new Jira issue for me? - - What are my open Jira issues? - - Can you search for issues with the label "bug" in my Jira project? diff --git a/assets/agents/report-writer/README.md b/assets/agents/report-writer/README.md new file mode 100644 index 0000000..0adf134 --- /dev/null +++ b/assets/agents/report-writer/README.md @@ -0,0 +1,46 @@ +# report-writer + +A tiny, focused sub-agent that turns a set of research findings into a +single coherent final report. Reads only what it is given — does not +do independent research, does not access the web, does not invent +facts. It exists as a focused tool for orchestrating agents to +delegate the writing phase to. + +## Why a separate agent? + +This is an example of the **agent-as-tool** pattern in graph agents. +The `deep-research` graph agent's `synthesize` node is an `agent` node +that spawns this one (see `assets/agents/deep-research/graph.yaml`). +Separating the role has two practical benefits: + +- The orchestrating agent can use a cheap model (or a high-temperature + exploratory one) for the research phase, while letting the writing + phase use a different (typically lower-temperature, possibly larger) + model dedicated to coherent prose. +- The writing prompt is owned by this agent's `config.yaml` rather + than buried inside another agent's graph. You can polish it + independently without touching the research flow. + +## Standalone use + +You can also use this agent directly if you have a set of findings you +want polished: + +```sh +loki -a report-writer "Topic: X. Findings: " +``` + +It will produce a single Markdown report following the rules in its +system prompt: executive summary at the top, grouped sections by +related sub-questions, every inline citation preserved verbatim, and a +final "Open questions / disagreements" section. + +## What it will NOT do + +- Search the web, fetch URLs, query an MCP server, or use any tool. + It has no tools configured. +- Invent facts beyond what is in the findings you give it. +- Strip or rewrite citations. + +These constraints are the point of the agent existing: a writer that +the orchestrator can trust to stay in its lane. diff --git a/assets/agents/report-writer/config.yaml b/assets/agents/report-writer/config.yaml new file mode 100644 index 0000000..2940c7d --- /dev/null +++ b/assets/agents/report-writer/config.yaml @@ -0,0 +1,34 @@ +name: report-writer +description: Polishes research findings into a clear, citation-preserving final report +version: 1.0.0 +temperature: 0.2 + +instructions: | + You are a technical writer. You will be given: + - a research topic + - a set of findings, organized per sub-question, with inline + citations next to each claim + - a source-credibility assessment of the cited sources + + Your job is to produce a single, well-organized final report: + + Rules: + - Use ONLY the findings provided. Do not introduce facts from + your own memory. Do not speculate beyond what the findings + support. + - Preserve every inline citation. If a sentence in the findings + had a URL or DOI, the equivalent sentence in your report must + keep the same citation. + - Lead with a 2-3 sentence executive summary at the top. + - Organize the body so that related sub-questions are grouped, + not strictly one section per question. The findings are raw + material; the report should read as a single coherent answer + to the original topic. + - End with a short "Open questions / disagreements" section + naming anything the findings flagged as unresolved or + contested. + + Output plain Markdown. No metadata, no JSON wrapper. + +conversation_starters: + - "Polish these findings into a cited report" diff --git a/assets/agents/sisyphus/README.md b/assets/agents/sisyphus/README.md index e59369e..e2a3381 100644 --- a/assets/agents/sisyphus/README.md +++ b/assets/agents/sisyphus/README.md @@ -18,16 +18,15 @@ Sisyphus acts as the primary entry point, capable of handling complex tasks by c - 🛠️ **Tool Integration**: Seamlessly uses system tools for building, testing, and file manipulation. ## Pro-Tip: Use an IDE MCP Server for Improved Performance -Many modern IDEs now include MCP servers that let LLMs perform operations within the IDE itself and use IDE tools. Using -an IDE's MCP server dramatically improves the performance of coding agents. So if you have an IDE, try adding that MCP -server to your config (see the [MCP Server docs](../../../docs/function-calling/MCP-SERVERS.md) to see how to configure -them), and modify the agent definition to look like this: +Many modern IDEs (JetBrains, VS Code, Cursor, Zed, etc.) expose MCP servers that let LLMs use IDE tools directly. Using +one dramatically improves the performance of coding agents. If you have one, add it to your loki config (see the +[MCP Server docs](../../../docs/function-calling/MCP-SERVERS.md)) and reference it in this agent's `mcp_servers:` list: ```yaml # ... mcp_servers: - - jetbrains + - your-ide-mcp-server global_tools: - fs_read.sh diff --git a/assets/agents/sisyphus/config.yaml b/assets/agents/sisyphus/config.yaml index a6cb81a..8bb3821 100644 --- a/assets/agents/sisyphus/config.yaml +++ b/assets/agents/sisyphus/config.yaml @@ -119,20 +119,21 @@ instructions: | 1. todo__init --goal "Add user profiles API endpoint" 2. todo__add --task "Explore existing API patterns" 3. todo__add --task "Implement profile endpoint" - 4. todo__add --task "Verify with build/test" - 5. agent__spawn --agent explore --prompt "Find existing API endpoint patterns, route structures, and controller conventions. Include code snippets." - 6. agent__spawn --agent explore --prompt "Find existing data models and database query patterns. Include code snippets." - 7. agent__collect --id - 8. agent__collect --id - 9. todo__done --id 1 - 10. agent__spawn --agent coder --prompt "" - 11. agent__collect --id - 12. todo__done --id 2 - 13. run_build - 14. run_tests - 15. todo__done --id 3 + 4. agent__spawn --agent explore --prompt "Find existing API endpoint patterns, route structures, and controller conventions. Include code snippets." + 5. agent__spawn --agent explore --prompt "Find existing data models and database query patterns. Include code snippets." + 6. agent__collect --id + 7. agent__collect --id + 8. todo__done --id 1 + 9. agent__spawn --agent coder --prompt "" + 10. agent__collect --id + 11. todo__done --id 2 ``` + Note: the `coder` agent is a graph agent that runs verification (build + + tests) and a bounded fix-loop internally. You do NOT need to spawn a + separate build/test step. A `CODER_COMPLETE` outcome means build and + tests already passed. + ### Example 2: Architecture/design question (explore + oracle in parallel) User: "How should I structure the authentication for this app?" @@ -172,6 +173,22 @@ instructions: | 10. **Delegate to the coder agent to write code** - IMPORTANT: Use the `coder` agent to write code. Do not try to write code yourself except for trivial changes 11. **Always output a summary of changes when finished** - Make it clear to user's that you've completed your tasks + ## Coder Outcomes + + The `coder` agent is a graph agent that runs the implement -> verify_build + -> verify_tests -> fix_loop pipeline internally. It always returns one of + three sentinel outcomes: + + - `CODER_COMPLETE` - implementation succeeded with build + tests green. + Continue with any follow-up todos. + - `CODER_REJECTED` - user rejected the plan at the approval gate (only + triggered for high-complexity plans). Do NOT re-spawn coder blindly; + ask the user what to change first. + - `CODER_FAILED` - the fix-loop exhausted its budget without producing + green build/tests. The failure output includes the last build and tests + output. Surface this to the user; consider spawning `oracle` for + diagnosis if the failure is unclear. + ## When to Do It Yourself - Simple command execution diff --git a/assets/functions/mcp.json b/assets/functions/mcp.json index d3794f6..0ae8f79 100644 --- a/assets/functions/mcp.json +++ b/assets/functions/mcp.json @@ -1,6 +1,7 @@ { "mcpServers": { "github": { + "type": "stdio", "command": "docker", "args": [ "run", @@ -15,14 +16,17 @@ } }, "atlassian": { + "type": "stdio", "command": "npx", "args": ["-y", "mcp-remote@0.1.13", "https://mcp.atlassian.com/v1/mcp"] }, "docker": { + "type": "stdio", "command": "uvx", "args": ["mcp-server-docker"] }, "ddg-search": { + "type": "stdio", "command": "uvx", "args": ["duckduckgo-mcp-server"] } diff --git a/assets/functions/scripts/run-agent.py b/assets/functions/scripts/run-agent.py index 6125c5a..e22368b 100755 --- a/assets/functions/scripts/run-agent.py +++ b/assets/functions/scripts/run-agent.py @@ -50,7 +50,13 @@ def parse_raw_data(data): def parse_argv(): agent_func = sys.argv[1] - agent_data = sys.argv[2] + + tool_data_file = os.environ.get("LLM_TOOL_DATA_FILE") + if tool_data_file and os.path.isfile(tool_data_file): + with open(tool_data_file, "r", encoding="utf-8") as f: + agent_data = f.read() + else: + agent_data = sys.argv[2] if (not agent_data) or (not agent_func): print("Usage: ./{agent_name}.py ", file=sys.stderr) diff --git a/assets/functions/scripts/run-agent.sh b/assets/functions/scripts/run-agent.sh index 5dd5179..ee1af7e 100644 --- a/assets/functions/scripts/run-agent.sh +++ b/assets/functions/scripts/run-agent.sh @@ -14,7 +14,11 @@ main() { parse_argv() { agent_func="$1" - agent_data="$2" + if [[ -n "$LLM_TOOL_DATA_FILE" ]] && [[ -f "$LLM_TOOL_DATA_FILE" ]]; then + agent_data="$(cat "$LLM_TOOL_DATA_FILE")" + else + agent_data="$2" + fi if [[ -z "$agent_data" ]] || [[ -z "$agent_func" ]]; then die "usage: ./{agent_name}.sh " fi @@ -57,7 +61,6 @@ run() { if [[ "$OS" == "Windows_NT" ]]; then set -o igncr tools_path="$(cygpath -w "$tools_path")" - tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')" fi jq_script="$(cat <<-'EOF' @@ -70,11 +73,11 @@ def to_args: to_entries | .[] | (.key | split("_") | join("-")) as $key | if .value | type == "array" then - .value | .[] | "--\($key) \(. | escape_shell_word)" + .value | .[] | "--\($key)=\(. | escape_shell_word)" elif .value | type == "boolean" then if .value then "--\($key)" else "" end else - "--\($key) \(.value | escape_shell_word)" + "--\($key)=\(.value | escape_shell_word)" end; [ to_args ] | join(" ") EOF diff --git a/assets/functions/scripts/run-agent.ts b/assets/functions/scripts/run-agent.ts new file mode 100644 index 0000000..a8e02a3 --- /dev/null +++ b/assets/functions/scripts/run-agent.ts @@ -0,0 +1,189 @@ +#!/usr/bin/env tsx + +// Usage: ./{agent_name}.ts + +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { join } from "path"; +import { pathToFileURL } from "url"; + +async function main(): Promise { + const { agentFunc, rawData } = parseArgv(); + const agentData = parseRawData(rawData); + + const configDir = "{config_dir}"; + setupEnv(configDir, agentFunc); + + const agentToolsPath = join(configDir, "agents", "{agent_name}", "tools.ts"); + await run(agentToolsPath, agentFunc, agentData); +} + +function parseRawData(data: string): Record { + if (!data) { + throw new Error("No JSON data"); + } + + try { + return JSON.parse(data); + } catch { + throw new Error("Invalid JSON data"); + } +} + +function parseArgv(): { agentFunc: string; rawData: string } { + const agentFunc = process.argv[2]; + + const toolDataFile = process.env["LLM_TOOL_DATA_FILE"]; + let agentData: string; + if (toolDataFile && existsSync(toolDataFile)) { + agentData = readFileSync(toolDataFile, "utf-8"); + } else { + agentData = process.argv[3]; + } + + if (!agentFunc || !agentData) { + process.stderr.write("Usage: ./{agent_name}.ts \n"); + process.exit(1); + } + + return { agentFunc, rawData: agentData }; +} + +function setupEnv(configDir: string, agentFunc: string): void { + loadEnv(join(configDir, ".env")); + process.env["LLM_ROOT_DIR"] = configDir; + process.env["LLM_AGENT_NAME"] = "{agent_name}"; + process.env["LLM_AGENT_FUNC"] = agentFunc; + process.env["LLM_AGENT_ROOT_DIR"] = join(configDir, "agents", "{agent_name}"); + process.env["LLM_AGENT_CACHE_DIR"] = join(configDir, "cache", "{agent_name}"); +} + +function loadEnv(filePath: string): void { + let lines: string[]; + try { + lines = readFileSync(filePath, "utf-8").split("\n"); + } catch { + return; + } + + for (const raw of lines) { + const line = raw.trim(); + if (line.startsWith("#") || !line) { + continue; + } + + const eqIdx = line.indexOf("="); + if (eqIdx === -1) { + continue; + } + + const key = line.slice(0, eqIdx).trim(); + if (key in process.env) { + continue; + } + + let value = line.slice(eqIdx + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + process.env[key] = value; + } +} + +function extractParamNames(fn: Function): string[] { + const src = fn.toString(); + const match = src.match(/^(?:async\s+)?function\s*\w*\s*\(([^)]*)\)/); + if (!match) { + return []; + } + return match[1] + .split(",") + .map((p) => p.trim().replace(/[:=?].*/s, "").trim()) + .filter(Boolean); +} + +function spreadArgs( + fn: Function, + data: Record, +): unknown[] { + const names = extractParamNames(fn); + if (names.length === 0) { + return []; + } + return names.map((name) => data[name]); +} + +async function run( + agentPath: string, + agentFunc: string, + agentData: Record, +): Promise { + const mod = await import(pathToFileURL(agentPath).href); + + if (typeof mod[agentFunc] !== "function") { + throw new Error(`No module function '${agentFunc}' at '${agentPath}'`); + } + + const fn = mod[agentFunc] as Function; + const args = spreadArgs(fn, agentData); + const value = await fn(...args); + returnToLlm(value); + dumpResult(`{agent_name}:${agentFunc}`); +} + +function returnToLlm(value: unknown): void { + if (value === null || value === undefined) { + return; + } + + const output = process.env["LLM_OUTPUT"]; + const write = (s: string) => { + if (output) { + writeFileSync(output, s, "utf-8"); + } else { + process.stdout.write(s); + } + }; + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + write(String(value)); + } else if (typeof value === "object") { + write(JSON.stringify(value, null, 2)); + } +} + +function dumpResult(name: string): void { + const dumpResults = process.env["LLM_DUMP_RESULTS"]; + const llmOutput = process.env["LLM_OUTPUT"]; + + if (!dumpResults || !llmOutput || !process.stdout.isTTY) { + return; + } + + try { + const pattern = new RegExp(`\\b(${dumpResults})\\b`); + if (!pattern.test(name)) { + return; + } + } catch { + return; + } + + let data: string; + try { + data = readFileSync(llmOutput, "utf-8"); + } catch { + return; + } + + process.stdout.write( + `\x1b[2m----------------------\n${data}\n----------------------\x1b[0m\n`, + ); +} + +main().catch((err) => { + process.stderr.write(`${err}\n`); + process.exit(1); +}); diff --git a/assets/functions/scripts/run-tool.py b/assets/functions/scripts/run-tool.py index d8c90b1..9ac3c07 100644 --- a/assets/functions/scripts/run-tool.py +++ b/assets/functions/scripts/run-tool.py @@ -49,6 +49,11 @@ def parse_raw_data(data): def parse_argv(): + tool_data_file = os.environ.get("LLM_TOOL_DATA_FILE") + if tool_data_file and os.path.isfile(tool_data_file): + with open(tool_data_file, "r", encoding="utf-8") as f: + return f.read() + argv = sys.argv[:] + [None] * max(0, 2 - len(sys.argv)) tool_data = argv[1] diff --git a/assets/functions/scripts/run-tool.sh b/assets/functions/scripts/run-tool.sh index ead83f6..c612273 100644 --- a/assets/functions/scripts/run-tool.sh +++ b/assets/functions/scripts/run-tool.sh @@ -13,7 +13,11 @@ main() { } parse_argv() { - tool_data="$1" + if [[ -n "$LLM_TOOL_DATA_FILE" ]] && [[ -f "$LLM_TOOL_DATA_FILE" ]]; then + tool_data="$(cat "$LLM_TOOL_DATA_FILE")" + else + tool_data="$1" + fi if [[ -z "$tool_data" ]]; then die "usage: ./{function_name}.sh " fi @@ -54,7 +58,6 @@ run() { if [[ "$OS" == "Windows_NT" ]]; then set -o igncr tool_path="$(cygpath -w "$tool_path")" - tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')" fi jq_script="$(cat <<-'EOF' @@ -67,11 +70,11 @@ def to_args: to_entries | .[] | (.key | split("_") | join("-")) as $key | if .value | type == "array" then - .value | .[] | "--\($key) \(. | escape_shell_word)" + .value | .[] | "--\($key)=\(. | escape_shell_word)" elif .value | type == "boolean" then if .value then "--\($key)" else "" end else - "--\($key) \(.value | escape_shell_word)" + "--\($key)=\(.value | escape_shell_word)" end; [ to_args ] | join(" ") EOF diff --git a/assets/functions/scripts/run-tool.ts b/assets/functions/scripts/run-tool.ts new file mode 100644 index 0000000..228166a --- /dev/null +++ b/assets/functions/scripts/run-tool.ts @@ -0,0 +1,184 @@ +#!/usr/bin/env tsx + +// Usage: ./{function_name}.ts + +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { join } from "path"; +import { pathToFileURL } from "url"; + +async function main(): Promise { + const rawData = parseArgv(); + const toolData = parseRawData(rawData); + + const rootDir = "{root_dir}"; + setupEnv(rootDir); + + const toolPath = "{tool_path}.ts"; + await run(toolPath, "run", toolData); +} + +function parseRawData(data: string): Record { + if (!data) { + throw new Error("No JSON data"); + } + + try { + return JSON.parse(data); + } catch { + throw new Error("Invalid JSON data"); + } +} + +function parseArgv(): string { + const toolDataFile = process.env["LLM_TOOL_DATA_FILE"]; + if (toolDataFile && existsSync(toolDataFile)) { + return readFileSync(toolDataFile, "utf-8"); + } + + const toolData = process.argv[2]; + + if (!toolData) { + process.stderr.write("Usage: ./{function_name}.ts \n"); + process.exit(1); + } + + return toolData; +} + +function setupEnv(rootDir: string): void { + loadEnv(join(rootDir, ".env")); + process.env["LLM_ROOT_DIR"] = rootDir; + process.env["LLM_TOOL_NAME"] = "{function_name}"; + process.env["LLM_TOOL_CACHE_DIR"] = join(rootDir, "cache", "{function_name}"); +} + +function loadEnv(filePath: string): void { + let lines: string[]; + try { + lines = readFileSync(filePath, "utf-8").split("\n"); + } catch { + return; + } + + for (const raw of lines) { + const line = raw.trim(); + if (line.startsWith("#") || !line) { + continue; + } + + const eqIdx = line.indexOf("="); + if (eqIdx === -1) { + continue; + } + + const key = line.slice(0, eqIdx).trim(); + if (key in process.env) { + continue; + } + + let value = line.slice(eqIdx + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + process.env[key] = value; + } +} + +function extractParamNames(fn: Function): string[] { + const src = fn.toString(); + const match = src.match(/^(?:async\s+)?function\s*\w*\s*\(([^)]*)\)/); + if (!match) { + return []; + } + return match[1] + .split(",") + .map((p) => p.trim().replace(/[:=?].*/s, "").trim()) + .filter(Boolean); +} + +function spreadArgs( + fn: Function, + data: Record, +): unknown[] { + const names = extractParamNames(fn); + if (names.length === 0) { + return []; + } + return names.map((name) => data[name]); +} + +async function run( + toolPath: string, + toolFunc: string, + toolData: Record, +): Promise { + const mod = await import(pathToFileURL(toolPath).href); + + if (typeof mod[toolFunc] !== "function") { + throw new Error(`No module function '${toolFunc}' at '${toolPath}'`); + } + + const fn = mod[toolFunc] as Function; + const args = spreadArgs(fn, toolData); + const value = await fn(...args); + returnToLlm(value); + dumpResult("{function_name}"); +} + +function returnToLlm(value: unknown): void { + if (value === null || value === undefined) { + return; + } + + const output = process.env["LLM_OUTPUT"]; + const write = (s: string) => { + if (output) { + writeFileSync(output, s, "utf-8"); + } else { + process.stdout.write(s); + } + }; + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + write(String(value)); + } else if (typeof value === "object") { + write(JSON.stringify(value, null, 2)); + } +} + +function dumpResult(name: string): void { + const dumpResults = process.env["LLM_DUMP_RESULTS"]; + const llmOutput = process.env["LLM_OUTPUT"]; + + if (!dumpResults || !llmOutput || !process.stdout.isTTY) { + return; + } + + try { + const pattern = new RegExp(`\\b(${dumpResults})\\b`); + if (!pattern.test(name)) { + return; + } + } catch { + return; + } + + let data: string; + try { + data = readFileSync(llmOutput, "utf-8"); + } catch { + return; + } + + process.stdout.write( + `\x1b[2m----------------------\n${data}\n----------------------\x1b[0m\n`, + ); +} + +main().catch((err) => { + process.stderr.write(`${err}\n`); + process.exit(1); +}); diff --git a/assets/functions/tools/demo_py.py b/assets/functions/tools/demo_py.py index 8b0ca0a..d71cd52 100644 --- a/assets/functions/tools/demo_py.py +++ b/assets/functions/tools/demo_py.py @@ -1,6 +1,7 @@ import os from typing import List, Literal, Optional + def run( string: str, string_enum: Literal["foo", "bar"], @@ -9,26 +10,38 @@ def run( number: float, array: List[str], string_optional: Optional[str] = None, + integer_with_default: int = 42, + boolean_with_default: bool = True, + number_with_default: float = 3.14, + string_with_default: str = "hello", array_optional: Optional[List[str]] = None, ): - """Demonstrates how to create a tool using Python and how to use comments. + """Demonstrates all supported Python parameter types and variations. Args: - string: Define a required string property - string_enum: Define a required string property with enum - boolean: Define a required boolean property - integer: Define a required integer property - number: Define a required number property - array: Define a required string array property - string_optional: Define an optional string property - array_optional: Define an optional string array property + string: A required string property + string_enum: A required string property constrained to specific values + boolean: A required boolean property + integer: A required integer property + number: A required number (float) property + array: A required string array property + string_optional: An optional string property (Optional[str] with None default) + integer_with_default: An optional integer with a non-None default value + boolean_with_default: An optional boolean with a default value + number_with_default: An optional number with a default value + string_with_default: An optional string with a default value + array_optional: An optional string array property """ output = f"""string: {string} string_enum: {string_enum} -string_optional: {string_optional} boolean: {boolean} integer: {integer} number: {number} array: {array} +string_optional: {string_optional} +integer_with_default: {integer_with_default} +boolean_with_default: {boolean_with_default} +number_with_default: {number_with_default} +string_with_default: {string_with_default} array_optional: {array_optional}""" for key, value in os.environ.items(): diff --git a/assets/functions/tools/demo_ts.ts b/assets/functions/tools/demo_ts.ts new file mode 100644 index 0000000..26dc8d1 --- /dev/null +++ b/assets/functions/tools/demo_ts.ts @@ -0,0 +1,53 @@ +/** + * Demonstrates all supported TypeScript parameter types and variations. + * + * @param string - A required string property + * @param string_enum - A required string property constrained to specific values + * @param boolean - A required boolean property + * @param number - A required number property + * @param array_bracket - A required string array using bracket syntax + * @param array_generic - A required string array using generic syntax + * @param string_optional - An optional string using the question mark syntax + * @param string_nullable - An optional string using the union-with-null syntax + * @param number_with_default - An optional number with a default value + * @param boolean_with_default - An optional boolean with a default value + * @param string_with_default - An optional string with a default value + * @param array_optional - An optional string array using the question mark syntax + */ +export function run( + string: string, + string_enum: "foo" | "bar", + boolean: boolean, + number: number, + array_bracket: string[], + array_generic: Array, + string_optional?: string, + string_nullable: string | null = null, + number_with_default: number = 42, + boolean_with_default: boolean = true, + string_with_default: string = "hello", + array_optional?: string[], +): string { + const parts = [ + `string: ${string}`, + `string_enum: ${string_enum}`, + `boolean: ${boolean}`, + `number: ${number}`, + `array_bracket: ${JSON.stringify(array_bracket)}`, + `array_generic: ${JSON.stringify(array_generic)}`, + `string_optional: ${string_optional}`, + `string_nullable: ${string_nullable}`, + `number_with_default: ${number_with_default}`, + `boolean_with_default: ${boolean_with_default}`, + `string_with_default: ${string_with_default}`, + `array_optional: ${JSON.stringify(array_optional)}`, + ]; + + for (const [key, value] of Object.entries(process.env)) { + if (key.startsWith("LLM_")) { + parts.push(`${key}: ${value}`); + } + } + + return parts.join("\n"); +} diff --git a/assets/functions/tools/get_current_weather.ts b/assets/functions/tools/get_current_weather.ts new file mode 100644 index 0000000..34bbf0d --- /dev/null +++ b/assets/functions/tools/get_current_weather.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env tsx + +import { appendFileSync, mkdirSync } from "fs"; +import { dirname } from "path"; + +/** + * Get the current weather in a given location + * @param location - The city and optionally the state or country (e.g., "London", "San Francisco, CA"). + */ +export async function run(location: string): string { + const encoded = encodeURIComponent(location); + const url = `https://wttr.in/${encoded}?format=4`; + + const resp = await fetch(url); + const data = await resp.text(); + + const dest = process.env["LLM_OUTPUT"] ?? "/dev/stdout"; + if (dest !== "-" && dest !== "/dev/stdout") { + mkdirSync(dirname(dest), { recursive: true }); + appendFileSync(dest, data, "utf-8"); + } + + return data; +} diff --git a/assets/functions/tools/query_jira_issues.sh b/assets/functions/tools/query_jira_issues.sh deleted file mode 100755 index 4160be0..0000000 --- a/assets/functions/tools/query_jira_issues.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -e - -# @meta require-tools jira -# @describe Query for jira issues using a Jira Query Language (JQL) query -# @option --jql-query! The Jira Query Language query to execute -# @env LLM_OUTPUT=/dev/stdout The output path - -main() { - jira issue ls -q "$argc_jql_query" --plain >> "$LLM_OUTPUT" -} \ No newline at end of file diff --git a/assets/roles/atlassian.md b/assets/roles/atlassian.md new file mode 100644 index 0000000..510dd5f --- /dev/null +++ b/assets/roles/atlassian.md @@ -0,0 +1,8 @@ +--- +enabled_mcp_servers: atlassian +--- +You are the librarian for the company's Confluence and Jira knowledge bases. Your job is to help users find and retrieve +information from these platforms. Use all tools at your disposal to answer user queries. + +Available Tools: +{{__tools__}} diff --git a/config.agent.example.yaml b/config.agent.example.yaml index db26d3b..d79b633 100644 --- a/config.agent.example.yaml +++ b/config.agent.example.yaml @@ -17,16 +17,18 @@ agent_session: null # Set a session to use when starting the agent. name: # Name of the agent, used in the UI and logs description: # Description of the agent, used in the UI version: 1 # Version of the agent -# Todo System & Auto-Continuation -# These settings help smaller models handle multi-step tasks more reliably. -# See docs/TODO-SYSTEM.md for detailed documentation. +# Auto-Continue (Todo System) +# The auto-continue system provides built-in task tracking for improved reliability. +# When enabled, the model can create todo lists and the system will automatically +# prompt it to continue when incomplete tasks remain. +# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information auto_continue: false # Enable automatic continuation when incomplete todos remain max_auto_continues: 10 # Maximum number of automatic continuations before stopping inject_todo_instructions: true # Inject the default todo tool usage instructions into the agent's system prompt continuation_prompt: null # Custom prompt used when auto-continuing (optional; uses default if null) # Sub-Agent Spawning System # Enable this agent to spawn and manage child agents in parallel. -# See docs/AGENTS.md for detailed documentation. +# See https://github.com/Dark-Alex-17/loki/wiki/Agents for detailed documentation. can_spawn_agents: false # Enable the agent to spawn child agents max_concurrent_agents: 4 # Maximum number of agents that can run simultaneously max_agent_depth: 3 # Maximum nesting depth for sub-agents (prevents runaway spawning) diff --git a/config.example.yaml b/config.example.yaml index b626163..0418818 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -27,18 +27,18 @@ sync_models_url: > # URL to sync model changes from https://raw.githubusercontent.com/Dark-Alex-17/loki/refs/heads/main/models.yaml # ---- REPL Prompt ---- -# Custom REPL left/right prompts; see the [REPL Prompt Documentation](./docs/REPL-PROMPT.md) for more information +# Custom REPL left/right prompts; see the [REPL Prompt Documentation](https://github.com/Dark-Alex-17/loki/wiki/REPL-Prompt) for more information left_prompt: '{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} ' right_prompt: '{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}' # ---- Vault ---- -# See the [Vault documentation](./docs/VAULT.md) for more information on the Loki vault +# See the [Vault documentation](https://github.com/Dark-Alex-17/loki/wiki/Vault) for more information on the Loki vault vault_password_file: null # Path to a file containing the password for the Loki vault (cannot be a secret template) # ---- Function Calling ---- -# See the [Tools documentation](./docs/function-calling/TOOLS.md) for more details +# See the [Tools documentation](https://github.com/Dark-Alex-17/loki/wiki/Tools) for more details function_calling: true # Enables or disables function calling (Globally). mapping_tools: # Alias for a tool or toolset fs: 'fs_cat,fs_ls,fs_mkdir,fs_rm,fs_write,fs_read,fs_glob,fs_grep' @@ -46,6 +46,7 @@ enabled_tools: null # Which tools to enable by default. (e.g. 'fs,w visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools') # - demo_py.py # - demo_sh.sh +# - demo_ts.ts - execute_command.sh # - execute_py_code.py # - execute_sql_code.sh @@ -61,8 +62,8 @@ visible_tools: # Which tools are visible to be compiled (and a # - fs_write.sh - get_current_time.sh # - get_current_weather.py +# - get_current_weather.ts - get_current_weather.sh - - query_jira_issues.sh # - search_arxiv.sh # - search_wikipedia.sh # - search_wolframalpha.sh @@ -73,14 +74,24 @@ visible_tools: # Which tools are visible to be compiled (and a # - web_search_tavily.sh # ---- MCP Servers ---- -# See the [MCP Servers documentation](./docs/MCP-SERVERS.md) for more details +# See the [MCP Servers documentation](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers) for more details mcp_server_support: true # Enables or disables MCP servers (globally). mapping_mcp_servers: # Alias for an MCP server or set of servers git: github,gitmcp enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack,ddg-search') +# ---- Auto-Continue (Todo System) ---- +# The auto-continue system provides built-in task tracking for improved reliability. +# When enabled, the model can create todo lists and the system will automatically +# prompt it to continue when incomplete tasks remain. +# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information +auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false) +max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10) +inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true) +continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default + # ---- Session ---- -# See the [Session documentation](./docs/SESSIONS.md) for more information +# See the [Session documentation](https://github.com/Dark-Alex-17/loki/wiki/Sessions) for more information save_session: null # Controls the persistence of the session. If true, auto save; if false, don't auto-save save; if null, ask the user what to do compression_threshold: 4000 # Compress the session when the token count reaches or exceeds this threshold summarization_prompt: > # The text prompt used for creating a concise summary of session message @@ -89,7 +100,7 @@ summary_context_prompt: > # The text prompt used for including the summar 'This is a summary of the chat history as a recap: ' # ---- RAG ---- -# See the [RAG Docs](./docs/RAG.md) for more details. +# See the [RAG Docs](https://github.com/Dark-Alex-17/loki/wiki/RAG) for more details. rag_embedding_model: null # Specifies the embedding model used for context retrieval rag_reranker_model: null # Specifies the reranker model used for sorting retrieved documents; Loki uses Reciprocal Rank Fusion by default rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries @@ -135,7 +146,7 @@ document_loaders: sh -c "yek $1 --json | jq 'map({ path: .filename, contents: .content })'" # ---- Clients ---- -# See the [Clients documentation](./docs/clients/CLIENTS.md) for more details +# See the [Clients documentation](https://github.com/Dark-Alex-17/loki/wiki/Clients) for more details clients: # All clients have the following configuration: # - type: xxxx diff --git a/config.role.example.md b/config.role.example.md index 3159277..25804bf 100644 --- a/config.role.example.md +++ b/config.role.example.md @@ -1,5 +1,9 @@ --- -# Everything in this section is optional +############################################ +## Everything in this section is optional ## +############################################ + +# Role Configuration name: # The name of the role model: openai:gpt-4o # The model to use for this role temperature: 0.2 # The temperature to use for this role when querying the model @@ -8,5 +12,14 @@ enabled_tools: fs_ls,fs_cat # A comma-separated list of tools to enabl enabled_mcp_servers: github,gitmcp # A comma-separated list of MCP servers to enable for this role prompt: null # A custom prompt to use for this role that will immediately query # the model for output instead of using the instructions below +# Auto-Continue (Todo System) +# The auto-continue system provides built-in task tracking for improved reliability. +# When enabled, the model can create todo lists and the system will automatically +# prompt it to continue when incomplete tasks remain. +# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information +auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false) +max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10) +inject_todo_instructions: true # Inject default todo tool usage instructions into the system prompt (default: true) +continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default --- You are an expert at doing things. This is where you write the instructions for the role. diff --git a/docs/AGENTS.md b/docs/AGENTS.md deleted file mode 100644 index d9d0aea..0000000 --- a/docs/AGENTS.md +++ /dev/null @@ -1,722 +0,0 @@ -# Agents - -Agents in Loki follow the same style as OpenAI's GPTs. They consist of 3 parts: - -* [Role](./ROLES.md) - Tell the LLM how to behave -* [RAG](./RAG.md) - Pre-built knowledge bases specifically for the agent -* [Function Calling](./function-calling/TOOLS.md#tools) ([#2](./function-calling/MCP-SERVERS.md)) - Extends the functionality of the LLM through custom functions it can call - -![Agent example](./images/agents/sql.gif) - -Agent configuration files are stored in the `agents` subdirectory of your Loki configuration directory. The location of -this directory varies between systems so you can use the following command to locate yours: - -```shell -loki --info | grep 'agents_dir' | awk '{print $2}' -``` - -If you're looking for more example agents, refer to the [built-in agents](../assets/agents). - -## Quick Links - -- [Directory Structure](#directory-structure) -- [Metadata](#1-metadata) -- [2. Define the Instructions](#2-define-the-instructions) - - [Static Instructions](#static-instructions) - - [Special Variables](#special-variables) - - [User-Defined Variables](#user-defined-variables) - - [Dynamic Instructions](#dynamic-instructions) - - [Variables](#variables) -- [3. Initializing RAG](#3-initializing-rag) -- [4. Building Tools for Agents](#4-building-tools-for-agents) - - [Limitations](#limitations) - - [.env File Support](#env-file-support) - - [Python-Based Agent Tools](#python-based-agent-tools) - - [Bash-Based Agent Tools](#bash-based-agent-tools) -- [5. Conversation Starters](#5-conversation-starters) -- [6. Todo System & Auto-Continuation](#6-todo-system--auto-continuation) -- [7. Sub-Agent Spawning System](#7-sub-agent-spawning-system) - - [Configuration](#spawning-configuration) - - [Spawning & Collecting Agents](#spawning--collecting-agents) - - [Task Queue with Dependencies](#task-queue-with-dependencies) - - [Active Task Dispatch](#active-task-dispatch) - - [Output Summarization](#output-summarization) - - [Teammate Messaging](#teammate-messaging) - - [Runaway Safeguards](#runaway-safeguards) -- [8. User Interaction Tools](#8-user-interaction-tools) - - [Available Tools](#user-interaction-available-tools) - - [Escalation (Sub-Agent to User)](#escalation-sub-agent-to-user) -- [9. Auto-Injected Prompts](#9-auto-injected-prompts) -- [Built-In Agents](#built-in-agents) - - ---- - -## Directory Structure -Agent configurations often have the following directory structure: - -``` -/agents - └── my-agent - ├── config.yaml - ├── tools.sh - or - ├── tools.py -``` - -This means that agent configurations often are only two files: the agent configuration file (`config.yaml`), and the -tool definitions (`agents/my-agent/tools.sh` or `tools.py`). - -To see a full example configuration file, refer to the [example agent config file](../config.agent.example.yaml). - -The best way to understand how an agent is built is to go step by step in the following manner: - ---- - -## 1. Metadata -Agent configurations have the following settings available to customize each agent: - -```yaml -# Model Configuration -model: openai:gpt-4o # Specify the LLM to use -temperature: null # Set default temperature parameter, range (0, 1) -top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2), depending on the model -# Agent Metadata Configuration -agent_session: null # Set a session to use when starting the agent. (e.g. temp, default); defaults to globally set agent_session -# Agent Configuration -name: # Name of the agent, used in the UI and logs -description: # Description of the agent, used in the UI -version: 1 # Version of the agent -# Function Calling Configuration -mcp_servers: # Optional list of MCP servers that the agent utilizes - - github # Corresponds to the name of an MCP server in the `/functions/mcp.json` file -global_tools: # Optional list of additional global tools to enable for the agent; i.e. not tools specific to the agent - - web_search - - fs - - python -# Todo System & Auto-Continuation (see "Todo System & Auto-Continuation" section below) -auto_continue: false # Enable automatic continuation when incomplete todos remain -max_auto_continues: 10 # Maximum continuation attempts before stopping -inject_todo_instructions: true # Inject todo tool instructions into system prompt -continuation_prompt: null # Custom prompt for continuations (optional) -# Sub-Agent Spawning (see "Sub-Agent Spawning System" section below) -can_spawn_agents: false # Enable spawning child agents -max_concurrent_agents: 4 # Max simultaneous child agents -max_agent_depth: 3 # Max nesting depth (prevents runaway) -inject_spawn_instructions: true # Inject spawning instructions into system prompt -summarization_model: null # Model for summarizing sub-agent output (e.g. 'openai:gpt-4o-mini') -summarization_threshold: 4000 # Char count above which sub-agent output is summarized -escalation_timeout: 300 # Seconds sub-agents wait for escalated user input (default: 5 min) -``` - -As mentioned previously: Agents utilize function calling to extend a model's capabilities. However, agents operate in -isolated environment, so in order for an agent to use a tool or MCP server that you have defined globally, you must -explicitly state which tools and/or MCP servers the agent uses. Otherwise, it is assumed that the agent doesn't use any -tools outside its own custom defined tools. - -And if you don't define a `agents/my-agent/tools.sh` or `agents/my-agent/tools.py`, then the agent is really just a -`role`. - -You'll notice there's no settings for agent-specific tooling. This is because they are handled separately and -automatically. See the [Building Tools for Agents](#4-building-tools-for-agents) section below for more information. - -To see a full example configuration file, refer to the [example agent config file](../config.agent.example.yaml). - -## 2. Define the Instructions -At their heart, agents function similarly to roles in that they tell the model how to behave. Agent configuration files -have the following settings for the instruction definitions: - -```yaml -dynamic_instructions: # Whether to use dynamically generated instructions for the agent; if false, static instructions are used. False by default. -instructions: # Static instructions for the LLM; These are ignored if dynamic instructions are used -variables: # An array of optional variables that the agent expects and uses -``` - -### Static Instructions -By default, Loki agents use statically defined instructions. Think of them as being identical to the instructions for a -[role](./ROLES.md#instructions), because they virtually are. - -**Example:** -```yaml -instructions: | - You are an AI agent designed to demonstrate agentic capabilities -``` - -Just like roles, agents support variable interpolation at runtime. There's two types of variables that can be -interpolated into the instructions at runtime: special variables (like roles have), and user-defined variables. Just -like roles, variables are interpolated into your instructions anywhere Loki sees the `{{variable}}` syntax. - -#### Special Variables -The following special variables are provided by Loki at runtime and can be injected into your agent's instructions: - -| Name | Description | Example | -|-----------------|---------------------------------------------------------------------|----------------------------| -| `__os__` | Operating system name | `linux` | -| `__os_family__` | Operating system family | `unix` | -| `__arch__` | System architecture | `x86_64` | -| `__shell__` | The current user's default shell | `bash` | -| `__locale__` | The current user's preferred language and region settings | `en-US` | -| `__now__` | Current timestamp in ISO 8601 format | `2025-11-07T10:15:44.268Z` | -| `__cwd__` | The current working directory | `/tmp` | -| `__tools__` | A list of the enabled tools (global + mcp servers + agent-specific) | | - -#### User-Defined Variables -Agents also support user-defined variables that can be interpolated into the instructions, and are made available to any -agent-specific tools you define (see [Building Tools for Agents](#4-building-tools-for-agents) for more details on how to -create agent-specific tooling). - -The `variables` setting in an agent's config has the following fields: - -| Field | Required | Description | -|---------------|----------|----------------------------------------------------------------------------------------------------| -| `name` | * | The name of the variable | -| `description` | * | The description of the field | -| `default` | | A default value for the field. If left undefined, the user will be prompted for a value at runtime | - -These variables can be referenced in both the agent's instructions, and in the tool definitions via `LLM_AGENT_VAR_`. - -**Example:** -```yaml -instructions: | - You are an agent who answers questions about a user's system. - - - {{__tools__}} - - - - os: {{__os__}} - os_family: {{__os_family__}} - arch: {{__arch__}} - shell: {{__shell__}} - locale: {{__locale__}} - now: {{__now__}} - cwd: {{__cwd__}} - - - - username: {{username}} - -variables: - - name: username # Accessible from the tool definitions via the `LLM_AGENT_VAR_USERNAME` environment variable - description: Your user name -``` - -### Dynamic Instructions -Sometimes you may find it useful to dynamically generate instructions on startup. Whether that be via a call to Loki -itself to generate them, or by some other means. Loki supports this type of behavior using a special function defined -in your `agents/my-agent/tools.py` or `agents/my-agent/tools.sh`. - -**Example: Instructions for a JSON-reader agent that specializes on each JSON input it receives** -`agents/json-reader/tools.py`: -```python -import json -from pathlib import Path -from genson import SchemaBuilder - -def _instructions(): - """Generates instructions for the agent dynamically""" - value = input("Enter a JSON file path OR paste raw JSON: ").strip() - if not value: - raise SystemExit("A file path or JSON string is required.") - - p = Path(value) - if p.exists() and p.is_file(): - json_file_path = str(p.resolve()) - json_text = p.read_text(encoding="utf-8") - else: - try: - json.loads(value) - except json.JSONDecodeError as e: - raise SystemExit(f"Input is neither a file nor valid JSON.\n{e}") - json_file_path = "" - json_text = value - - try: - data = json.loads(json_text) - except json.JSONDecodeError as e: - raise SystemExit(f"Provided content is not valid JSON.\n{e}") - - builder = SchemaBuilder() - builder.add_object(data) - json_schema = builder.to_schema() - return f""" - You are an AI agent that can view and filter JSON data with jq. - - ## Context - json_file_path: {json_file_path} - json_schema: {json.dumps(json_schema, indent=2)} - """ -``` - -or - -`agents/json-reader/tools.sh`: -```bash -#!/usr/bin/env bash -set -e - -# @meta require-tools jq,genson -# @env LLM_OUTPUT=/dev/stdout The output path - -# @cmd Generates instructions for the agent dynamically -_instructions() { - read -r -p "Enter a JSON file path OR paste raw JSON: " value - - if [[ -z "${value}" ]]; then - echo "A file path or JSON string is required" >&2 - exit 1 - fi - json_file_path="" - inline_temp="" - cleanup() { - [[ -n "${inline_temp:-}" && -f "${inline_temp}" ]] && rm -f "${inline_temp}" - } - trap cleanup EXIT - - if [[ -f "${value}" ]]; then - json_file_path="$(realpath "${value}")" - if ! jq empty "${json_file_path}" >/dev/null 2>&1; then - echo "Error: File does not contain valid JSON: ${json_file_path}" >&2 - exit 1 - fi - else - inline_temp="$(mktemp)" - printf "%s" "${value}" > "${inline_temp}" - if ! jq empty "${inline_temp}" >/dev/null 2>&1; then - echo "Error: Input is neither a file nor valid JSON." >&2 - exit 1 - fi - json_file_path="" - fi - - source_file="${json_file_path}" - if [[ "${json_file_path}" == "" ]]; then - source_file="${inline_temp}" - fi - - json_schema="$(genson < "${source_file}" | jq -c '.')" - cat <> "$LLM_OUTPUT" -You are an AI agent that can view and filter JSON data with jq. - -## Context -json_file_path: ${json_file_path} -json_schema: ${json_schema} -EOF -} -``` - -For more information on how to create custom tools for your agent and the structure of the `agent/my-agent/tools.sh` or -`agent/my-agent/tools.py` files, refer to the [Building Tools for Agents](#4-building-tools-for-agents) section below. - -#### Variables -All the same variable interpolations supported by static instructions is also supported by dynamic instructions. For -more information on what variables are available and how to use them, refer to the [Special Variables](#special-variables) -and [User-Defined Variables](#user-defined-variables) sections above. - -## 3. Initializing RAG -Each agent you create also has a dedicated knowledge base that adds additional context to your queries and helps the LLM -answer queries effectively. The documents to load into RAG are defined in the `documents` array of your agent -configuration file: - -```yaml -documents: - - https://www.ohdsi.org/data-standardization/ - - https://github.com/OHDSI/Vocabulary-v5.0/wiki/** - - OMOPCDM_ddl.sql # Relative path to agent (i.e. file lives at '/agents/my-agent/OMOPCDM_ddl.sql') -``` - -These documents use the same syntax as those you'd define when constructing RAG normally. To see all the available types -of documents that Loki supports and how to use custom document loaders, refer to the [RAG documentation](./RAG.md#supported-document-sources). - -Anytime your agent starts up, it will automatically be using the RAG you've defined here. - -## 4. Building Tools for Agents -Building tools for agents is virtually identical to building custom tools, with one slight difference: instead of -defining a single function that gets executed at runtime (e.g. `main` for bash tools and `run` for Python tools), agent -tools define a number of *subcommands*. - -### Limitations -You can only utilize either a bash-based `/agents/my-agent/tools.sh` or a Python-based -`/agents/my-agent/tools.py`. However, if it's easier to achieve a task in one language vs the other, -you're free to define other scripts in your agent's configuration directory and reference them from the main -`tools.py/sh` file. **Any scripts *not* named `tools.{py,sh}` will not be picked up by Loki's compiler**, meaning they -can be used like any other set of scripts. - -It's important to keep in mind the following: - -* **Do not give agents the same name as an executable**. Loki compiles the tools for each agent into a binary that it - temporarily places on your path during execution. If you have a binary with the same name as your agent, then your - shell may execute the existing binary instead of your agent's tools -* **`LLM_ROOT_DIR` points to the agent's configuration directory**. This is where agents differ slightly from normal - tools: The `LLM_ROOT_DIR` environment variable does *not* point to the `functions/tools` directory like it does in - global tools. Instead, it points to the agent's configuration directory, making it easier to source scripts and other - miscellaneous files - -### .env File Support -When Loki loads an agent, it will also search the agent's configuration directory for a `.env` file. If found, all -environment variables defined in the file will be made available to the agent's tools. - -### Python-Based Agent Tools -Python-based tools are defined exactly the same as they are for custom tool definitions. The only difference is that -instead of a single `run` function, you define as many as you like with whatever arguments you like. - -**Example:** -`agents/my-agent/tools.py` -```python -import urllib.request - -def get_ip_info(): - """ - Get your IP information - """ - with urllib.request.urlopen("https://httpbin.org/ip") as response: - data = response.read() - return data.decode('utf-8') - -def get_ip_address_from_aws(): - """ - Find your public IP address using AWS - """ - with urllib.request.urlopen("https://checkip.amazonaws.com") as response: - data = response.read() - return data.decode('utf-8') -``` - -Loki automatically compiles these as separate functions for the LLM to call. No extra work is needed. Just make sure you -follow all the same steps to define each function as you would when creating custom Python tools. - -For more information on how to build tools in Python, refer to the [custom Python tools documentation](./function-calling/CUSTOM-TOOLS.md#custom-python-based-tools) - -### Bash-Based Agent Tools -Bash-based agent tools are virtually identical to custom bash tools, with only one difference. Instead of defining a -single entrypoint via the `main` function, you actually define as many subcommands as you like. - -**Example:** -`agents/my-agent/tools.sh` -```bash -#!/usr/bin/env bash - -# @env LLM_OUTPUT=/dev/stdout The output path -# @describe Discover network information about your computer and its place in the internet - -# Use the `@cmd` annotation to define subcommands for your script. -# @cmd Get your IP information -get_ip_info() { - curl -fsSL https://httpbin.org/ip >> "$LLM_OUTPUT" -} - -# @cmd Find your public IP address using AWS -get_ip_address_from_aws() { - curl -fsSL https://checkip.amazonaws.com >> "$LLM_OUTPUT" -} -``` -To compile the script so it's executable and testable: -```bash -$ loki --build-tools -``` - -Then you can execute your script (assuming your current working directory is `agents/my-agent`): -```bash -$ ./tools.sh get_ip_info -$ ./tools.sh get_ip_address_from_aws -``` - -All other special annotations (`@env`, `@arg`, `@option` `@flags`) apply to subcommands as well, so be sure to follow -the same syntax ad formatting as is used to create custom bash tools globally. - -For more information on how to write, [build and test](function-calling/CUSTOM-BASH-TOOLS.md#execute-and-test-your-bash-tools) tools in bash, refer to the -[custom bash tools documentation](function-calling/CUSTOM-BASH-TOOLS.md). - -## 5. Conversation Starters -It's often helpful to also have some conversation starters so users know what kinds of things the agent is capable of -doing. These are available in the REPL via the `.starter` command and are selectable. - -They are defined using the `conversation_starters` setting in your agent's configuration file: - -**Example:** -`agents/my-agent/config.yaml`: -```yaml -conversation_starters: - - What is my username? - - What is my current shell? - - What is my ip? - - How much disk space is left on my PC?? - - How to create an agent? -``` - -![Example Conversation Starters](./images/agents/conversation-starters.gif) - -## 6. Todo System & Auto-Continuation - -Loki includes a built-in task tracking system designed to improve the reliability of agents, especially when using -smaller language models. The Todo System helps models: - -- Break complex tasks into manageable steps -- Track progress through multi-step workflows -- Automatically continue work until all tasks are complete - -### Quick Configuration - -```yaml -# agents/my-agent/config.yaml -auto_continue: true # Enable auto-continuation -max_auto_continues: 10 # Max continuation attempts -inject_todo_instructions: true # Include the default todo instructions into prompt -``` - -### How It Works - -1. When `inject_todo_instructions` is enabled, agents receive instructions on using five built-in tools: - - `todo__init`: Initialize a todo list with a goal - - `todo__add`: Add a task to the list - - `todo__done`: Mark a task complete - - `todo__list`: View current todo state - - `todo__clear`: Clear the entire todo list and reset the goal - - These instructions are a reasonable default that detail how to use Loki's To-Do System. If you wish, - you can disable the injection of the default instructions and specify your own instructions for how - to use the To-Do System into your main `instructions` for the agent. - -2. When `auto_continue` is enabled and the model stops with incomplete tasks, Loki automatically sends a - continuation prompt with the current todo state, nudging the model to continue working. - -3. This continues until all tasks are done or `max_auto_continues` is reached. - -### When to Use - -- Multistep tasks where the model might lose track -- Smaller models that need more structure -- Workflows requiring guaranteed completion of all steps - -For complete documentation including all configuration options, tool details, and best practices, see the -[Todo System Guide](./TODO-SYSTEM.md). - -## 7. Sub-Agent Spawning System - -Loki agents can spawn and manage child agents that run **in parallel** as background tasks inside the same process. -This enables orchestrator-style agents that delegate specialized work to other agents, similar to how tools like -Claude Code or OpenCode handle complex multi-step tasks. - -For a working example of an orchestrator agent that uses sub-agent spawning, see the built-in -[sisyphus](../assets/agents/sisyphus) agent. For an example of the teammate messaging pattern with parallel sub-agents, -see the [code-reviewer](../assets/agents/code-reviewer) agent. - -### Spawning Configuration - -| Setting | Type | Default | Description | -|-----------------------------|---------|---------------|--------------------------------------------------------------------------------| -| `can_spawn_agents` | boolean | `false` | Enable this agent to spawn child agents | -| `max_concurrent_agents` | integer | `4` | Maximum number of child agents that can run simultaneously | -| `max_agent_depth` | integer | `3` | Maximum nesting depth for sub-agents (prevents runaway spawning chains) | -| `inject_spawn_instructions` | boolean | `true` | Inject the default spawning instructions into the agent's system prompt | -| `summarization_model` | string | current model | Model to use for summarizing long sub-agent output (e.g. `openai:gpt-4o-mini`) | -| `summarization_threshold` | integer | `4000` | Character count above which sub-agent output is summarized before returning | -| `escalation_timeout` | integer | `300` | Seconds a sub-agent waits for an escalated user interaction response | - -**Example configuration:** -```yaml -# agents/my-orchestrator/config.yaml -can_spawn_agents: true -max_concurrent_agents: 6 -max_agent_depth: 2 -inject_spawn_instructions: true -summarization_model: openai:gpt-4o-mini -summarization_threshold: 3000 -escalation_timeout: 600 -``` - -### Spawning & Collecting Agents - -When `can_spawn_agents` is enabled, the agent receives tools for spawning and managing child agents: - -| Tool | Description | -|------------------|-------------------------------------------------------------------------| -| `agent__spawn` | Spawn a child agent in the background. Returns an agent ID immediately. | -| `agent__check` | Non-blocking check: is the agent done? Returns `PENDING` or the result. | -| `agent__collect` | Blocking wait: wait for an agent to finish, return its output. | -| `agent__list` | List all spawned agents and their status. | -| `agent__cancel` | Cancel a running agent by ID. | - -The core pattern is **Spawn -> Continue -> Collect**: - -``` -# 1. Spawn agents in parallel (returns IDs immediately) -agent__spawn --agent explore --prompt "Find auth middleware patterns in src/" -agent__spawn --agent explore --prompt "Find error handling patterns in src/" - -# 2. Continue your own work while they run - -# 3. Check if done (non-blocking) -agent__check --id agent_explore_a1b2c3d4 - -# 4. Collect results when ready (blocking) -agent__collect --id agent_explore_a1b2c3d4 -agent__collect --id agent_explore_e5f6g7h8 -``` - -Any agent defined in your `/agents/` directory can be spawned as a child. Child agents: -- Run in a fully isolated environment (separate session, config, and tools) -- Have their output suppressed from the terminal (no spinner, no tool call logging) -- Return their accumulated output to the parent when collected - -### Task Queue with Dependencies - -For complex workflows where tasks have ordering requirements, the spawning system includes a dependency-aware -task queue: - -| Tool | Description | -|------------------------|-----------------------------------------------------------------------------| -| `agent__task_create` | Create a task with optional dependencies and auto-dispatch agent. | -| `agent__task_list` | List all tasks with their status, dependencies, and assignments. | -| `agent__task_complete` | Mark a task done. Returns newly unblocked tasks and auto-dispatches agents. | -| `agent__task_fail` | Mark a task as failed. Dependents remain blocked. | - -``` -# Create tasks with dependency ordering -agent__task_create --subject "Explore existing patterns" -agent__task_create --subject "Implement feature" --blocked_by ["task_1"] -agent__task_create --subject "Write tests" --blocked_by ["task_2"] - -# Mark tasks complete to unblock dependents -agent__task_complete --task_id task_1 -``` - -### Active Task Dispatch - -Tasks can optionally specify an agent to auto-spawn when the task becomes runnable: - -``` -agent__task_create \ - --subject "Implement the auth module" \ - --blocked_by ["task_1"] \ - --agent coder \ - --prompt "Implement auth module based on patterns found in task_1" -``` - -When `task_1` completes and the dependent task becomes unblocked, an agent is automatically spawned with the -specified prompt. No manual intervention needed. This enables fully automated multi-step pipelines. - -### Output Summarization - -When a child agent produces long output, it can be automatically summarized before returning to the parent. -This keeps parent context windows manageable. - -- If the output exceeds `summarization_threshold` characters (default: 4000), it is sent through an LLM - summarization pass -- The `summarization_model` setting lets you use a cheaper/faster model for summarization (e.g. `gpt-4o-mini`) -- If `summarization_model` is not set, the parent's current model is used -- The summarization preserves all actionable information: code snippets, file paths, error messages, and - concrete recommendations - -### Teammate Messaging - -All agents (including children) automatically receive tools for **direct sibling-to-sibling messaging**: - -| Tool | Description | -|-----------------------|-----------------------------------------------------| -| `agent__send_message` | Send a text message to another agent's inbox by ID. | -| `agent__check_inbox` | Drain all pending messages from your inbox. | - -This enables coordination patterns where child agents share cross-cutting findings: - -``` -# Agent A discovers something relevant to Agent B -agent__send_message --id agent_reviewer_b1c2d3e4 --message "Found a security issue in auth.rs line 42" - -# Agent B checks inbox before finalizing -agent__check_inbox -``` - -Messages are routed through the parent's supervisor. A parent can message its children, and children can message -their siblings. For a working example of the teammate pattern, see the built-in -[code-reviewer](../assets/agents/code-reviewer) agent, which spawns file-specific reviewers that share -cross-cutting findings with each other. - -### Runaway Safeguards - -The spawning system includes built-in safeguards to prevent runaway agent chains: - -- **`max_concurrent_agents`:** Caps how many agents can run at once (default: 4). Spawn attempts beyond this - limit return an error asking the agent to wait or cancel existing agents. -- **`max_agent_depth`:** Caps nesting depth (default: 3). A child agent spawning its own child increments the - depth counter. Attempts beyond the limit are rejected. -- **`can_spawn_agents`:** Only agents with this flag set to `true` can spawn children. By default, spawning is - disabled. This means child agents cannot spawn their own children unless you explicitly create them with - `can_spawn_agents: true` in their config. - -## 8. User Interaction Tools - -Loki includes built-in tools for agents (and the REPL) to interactively prompt the user for input. These tools -are **always available**. No configuration needed. They are automatically injected into every agent and into -REPL mode when function calling is enabled. - -### User Interaction Available Tools - -| Tool | Description | Returns | -|------------------|-----------------------------------------|----------------------------------| -| `user__ask` | Present a single-select list of options | The selected option string | -| `user__confirm` | Ask a yes/no question | `"yes"` or `"no"` | -| `user__input` | Request free-form text input | The text entered by the user | -| `user__checkbox` | Present a multi-select checkbox list | Array of selected option strings | - -**Parameters:** - -- `user__ask`: `--question "..." --options ["Option A", "Option B", "Option C"]` -- `user__confirm`: `--question "..."` -- `user__input`: `--question "..."` -- `user__checkbox`: `--question "..." --options ["Option A", "Option B", "Option C"]` - -At the top level (depth 0), these tools render interactive terminal prompts directly using arrow-key navigation, -checkboxes, and text input fields. - -### Escalation (Sub-Agent to User) - -When a **child agent** (depth > 0) calls a `user__*` tool, it cannot prompt the terminal directly. Instead, -the request is **automatically escalated** to the root agent: - -1. The child agent calls `user__ask(...)` and **blocks**, waiting for a reply -2. The root agent sees a `pending_escalations` notification in its next tool results -3. The root agent either answers from context or prompts the user itself, then calls - `agent__reply_escalation` to unblock the child -4. The child receives the reply and continues - -The escalation timeout is configurable via `escalation_timeout` in the agent's `config.yaml` (default: 300 -seconds / 5 minutes). If the timeout expires, the child receives a fallback message asking it to use its -best judgment. - -| Tool | Description | -|---------------------------|--------------------------------------------------------------------------| -| `agent__reply_escalation` | Reply to a pending child escalation, unblocking the waiting child agent. | - -This tool is automatically available to any agent with `can_spawn_agents: true`. - -## 9. Auto-Injected Prompts - -Loki automatically appends usage instructions to your agent's system prompt for each enabled built-in system. -These instructions are injected into both **static and dynamic instructions** after your own instructions, -ensuring agents always know how to use their available tools. - -| System | Injected When | Toggle | -|--------------------|----------------------------------------------------------------|-----------------------------| -| Todo tools | `auto_continue: true` AND `inject_todo_instructions: true` | `inject_todo_instructions` | -| Spawning tools | `can_spawn_agents: true` AND `inject_spawn_instructions: true` | `inject_spawn_instructions` | -| Teammate messaging | Always (all agents) | None (always injected) | -| User interaction | Always (all agents) | None (always injected) | - -If you prefer to write your own instructions for a system, set the corresponding `inject_*` flag to `false` -and include your custom instructions in the agent's `instructions` field. The built-in tools will still be -available; only the auto-injected prompt text is suppressed. - -## Built-In Agents -Loki comes packaged with some useful built-in agents: - -* `coder`: An agent to assist you with all your coding tasks -* `code-reviewer`: A [CodeRabbit](https://coderabbit.ai)-style code reviewer that spawns per-file reviewers using the teammate messaging pattern -* `demo`: An example agent to use for reference when learning to create your own agents -* `explore`: An agent designed to help you explore and understand your codebase -* `file-reviewer`: An agent designed to perform code-review on a single file (used by the `code-reviewer` agent) -* `jira-helper`: An agent that assists you with all your Jira-related tasks -* `oracle`: An agent for high-level architecture, design decisions, and complex debugging -* `sisyphus`: A powerhouse orchestrator agent for writing complex code and acting as a natural language interface for your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode). Uses sub-agent spawning to delegate to `explore`, `coder`, and `oracle`. -* `sql`: A universal SQL agent that enables you to talk to any relational database in natural language diff --git a/docs/AICHAT-MIGRATION.md b/docs/AICHAT-MIGRATION.md deleted file mode 100644 index f48050f..0000000 --- a/docs/AICHAT-MIGRATION.md +++ /dev/null @@ -1,211 +0,0 @@ -# AIChat to Loki Migration Guide -Loki originally started as a fork of AIChat but has since evolved into its own separate project with separate goals. - -As a result, there's some changes you'll need to make to your AIChat configuration to be able to use Loki. - -Be sure you've run `loki` at least once so that the Loki configuration directory and subdirectories exist and is -populated with the built-in defaults. - -## Global Configuration File -You should be able to copy/paste your AIChat configuration file into your Loki configuration directory. Since the -location of the Loki configuration directory varies between systems, you can use the following command to locate your -config directory: - -```shell -loki --info | grep 'config_dir' | awk '{print $2}' -``` - -Then, you'll need to make the following changes: - -* `function_calling` -> `function_calling_support` -* `use_tools` -> `enabled_tools` -* `agent_prelude` -> `agent_session` -* `compress_threshold` -> `compression_threshold` -* `summarize_prompt` -> `summarization_prompt` -* `summary_prompt` -> `summary_context_prompt` - -## Roles -Locate your `roles` directory using the following command: - -```shell -loki --info | grep 'roles_dir' | awk '{print $2}' -``` - -Update any roles that have `use_tools` to `enabled_tools`. - -## Sessions -Locate your `sessions` directory using the following command: - -```shell -loki --info | grep 'sessions_dir' | awk '{print $2}' -``` - -Update the following settings: -* `use_tools` -> `enabled_tools` -* `compress_threshold` -> `compression_threshold` -* `summarize_prompt` -> `summarization_prompt` -* `summary_prompt` -> `summary_context_prompt` - ---- - -# LLM Functions Changes -Probably the most significant difference between AIChat and Loki is how tools are handled. So if you cloned the -[llm-functions](https://github.com/sigoden/llm-functions) repo, you'll need to make the following changes. - -**Note: JavaScript functions are not supported in Loki.** - -The following guide assumes you're using the `llm-functions` repository as your base for custom functions, and thus -follows that directory structure. - -## Agents -Agents are now all handled in one place: the `agents` directory (`/agents`): - -```shell -loki --info | grep 'agents_dir' | awk '{print $2}' -``` - -And instead of separate `index.yaml` and `config.yaml` files, they're now both in a single `config.yaml` file. - -So now for all of your agents, copy all the contents of those directories to the corresponding directory in the Loki -`agents` directory. Then make the following changes: - -* Copy the contents of your `/functions/agents` directory into `/agents//tools.txt` -* No `/agents//index.yaml` - -## Functions -Loki consolidates much of the `llm-functions` repo functionality into one binary. So this means - -* There's no need to have `argc` installed anymore -* No separate repository to manage -* No `tools.txt` -* No `functions.json` -* No `functions/mcp` directory at all -* No `functions/scripts` - -Here's how to migrate your functions over to Loki from the `llm-functions` repository. - -* Copy your AIChat `/functions` directory into your Loki config directory -* Delete the following files and directories from your `/functions` directory: - * `scripts/` - * `agents.txt` - * `functions.json` - * `Argcfile.sh` - * `README.md` (irrelevant now) - * `LICENSE` (irrelevant now) - * `utils/guard_operation.sh` - * `utils/guard_path.sh` - * `utils/patch.awk` -* Everything in `tools.txt` now lives in the global config file under the `visible_tools` setting: - ```text - get_current_weather.sh - execute_command.sh - web_search.sh - #execute_py_code.py - query_jira_issues.sh - ``` - becomes the following in your `/config.yaml` - ```yaml - visible_tools: - - get_current_weather.sh - - execute_command.sh - - web_search.sh - # - web_search.sh - - query_jira_issues.sh - ``` -* If you've defined a `functions/mcp.json` file, you can leave it alone. -* Similarly to agents, if you have any bash `tools.sh` that depend on the utility scripts in the `llm-functions` - repository, they've been replaced by built-in utility scripts. So use the following to replace any matching lines in - your `tools.sh` files: - ```bash - ################## - ## Scripts file ## - ################## - ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" - # replace with - source "$LLM_PROMPT_UTILS_FILE" - - ####################### - ## guard_path script ## - ####################### - "$ROOT_DIR/utils/guard_path.sh" - # replace with - guard_path - - ############################ - ## guard_operation script ## - ############################ - "$ROOT_DIR/utils/guard_operation.sh" - # replace with - guard_operation - - ###################### - ## patch.awk script ## - ###################### - awk -f "$ROOT_DIR/utils/patch.awk" - # replace with - patch_file - ``` - -Refer to the [custom bash tools docs](./function-calling/CUSTOM-BASH-TOOLS.md) to learn how to compile and test bash -tools in Loki without needing to use `argc`. diff --git a/docs/ENVIRONMENT-VARIABLES.md b/docs/ENVIRONMENT-VARIABLES.md deleted file mode 100644 index 3bb52e0..0000000 --- a/docs/ENVIRONMENT-VARIABLES.md +++ /dev/null @@ -1,112 +0,0 @@ -# Environment Variables - -Loki is designed to be highly dynamic and customizable. As a result, Loki utilizes a number of environment variables -that can be used to modify its behavior at runtime without needing to modify the existing configuration files. - -Loki also supports defining environment variables via a `.env` file in the Loki configuration directory. This directory -varies between systems, so you can find the location of your configuration directory using the following command: - -```shell -loki --info | grep 'config_dir' | awk '{print $2}' -``` - -## Quick Links - -- [Global Configuration Related Variables](#global-configuration-related-variables) -- [Client Related Variables](#client-related-variables) -- [Files and Directory Related Variables](#files-and-directory-related-variables) -- [Agent Related Variables](#agent-related-variables) -- [Logging Related Variables](#logging-related-variables) -- [Miscellaneous Variables](#miscellaneous-variables) - - ---- - -## Global Configuration Related Variables -All configuration items in the global config file have environment variables that can be overridden at runtime. To see -all configuration options and more thorough descriptions, refer to the [example config file](../config.example.yaml). - -Below are the most commonly used configuration settings and their corresponding environment variables: - -| Setting | Environment Variable | -|----------------------------|---------------------------------| -| `model` | `LOKI_MODEL` | -| `temperature` | `LOKI_TEMPERATURE` | -| `top_p` | `LOKI_TOP_P` | -| `stream` | `LOKI_STREAM` | -| `save` | `LOKI_SAVE` | -| `editor` | `LOKI_EDITOR` | -| `wrap` | `LOKI_WRAP` | -| `wrap_code` | `LOKI_WRAP_CODE` | -| `save_session` | `LOKI_SAVE_SESSION` | -| `compression_threshold` | `LOKI_COMPRESSION_THRESHOLD` | -| `function_calling_support` | `LOKI_FUNCTION_CALLING_SUPPORT` | -| `enabled_tools` | `LOKI_ENABLED_TOOLS` | -| `mcp_server_support` | `LOKI_MCP_SERVER_SUPPORT` | -| `enabled_mcp_servers` | `LOKI_ENABLED_MCP_SERVERS` | -| `rag_embedding_model` | `LOKI_RAG_EMBEDDING_MODEL` | -| `rag_reranker_model` | `LOKI_RAG_RERANKER_MODEL` | -| `rag_top_k` | `LOKI_RAG_TOP_K` | -| `rag_chunk_size` | `LOKI_RAG_CHUNK_SIZE` | -| `rag_chunk_overlap` | `LOKI_RAG_CHUNK_OVERLAP` | -| `highlight` | `LOKI_HIGHLIGHT` | -| `theme` | `LOKI_THEME` | -| `serve_addr` | `LOKI_SERVE_ADDR` | -| `user_agent` | `LOKI_USER_AGENT` | -| `save_shell_history` | `LOKI_SAVE_SHELL_HISTORY` | -| `sync_models_url` | `LOKI_SYNC_MODELS_URL` | - - -## Client Related Variables -The following environment variables are available for clients in Loki: - -| Environment Variable | Description | -|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `{client}_API_KEY` | For clients that require an API key, you can define the keys either through environment variables or
using the [vault](./VAULT.md). The variables are named after the client to which they apply;
e.g. `OPENAI_API_KEY`, `GEMINI_API_KEY`, etc. | -| `LOKI_PLATFORM` | Combine with `{client}_API_KEY` to run Loki without a configuration file.
This variable is ignored if a configuration file exists. | -| `LOKI_PATCH_{client}_CHAT_COMPLETIONS` | Patch chat completion requests to models on the corresponding client; Can modify the URL, body,
or headers. | -| `LOKI_SHELL` | Specify the shell that Loki should be using when executing commands | - -## Files and Directory Related Variables -You can also customize the files and directories that Loki loads its configuration files from: - -| Environment Variable | Description | Default Value | -|----------------------|------------------------------------------------------------------------|---------------------------------| -| `LOKI_CONFIG_DIR` | Customize the location of the Loki configuration directory. | `/loki` | -| `LOKI_ENV_FILE` | Customize the location of the `.env` file to load at startup. | `/.env` | -| `LOKI_CONFIG_FILE` | Customize the location of the global `config.yaml` configuration file. | `/config.yaml` | -| `LOKI_ROLES_DIR` | Customize the location of the `roles` directory. | `/roles` | -| `LOKI_SESSIONS_DIR` | Customize the location of the `sessions` directory. | `/sessions` | -| `LOKI_RAGS_DIR` | Customize the location of the `rags` directory. | `/rags` | -| `LOKI_FUNCTIONS_DIR` | Customize the location of the `functions` directory. | `/functions` | - -## Agent Related Variables -You can also customize the location of full agent configurations using the following environment variables: - -| Environment Variable | Description | -|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| -| `_CONFIG_FILE` | Customize the location of the agent's configuration file; e.g. `SQL_CONFIG_FILE` | -| `_MODEL` | Customize the `model` used for the agent; e.g `SQL_MODEL` | -| `_TEMPERATURE` | Customize the `temperature` used for the agent; e.g. `SQL_TEMPERATURE` | -| `_TOP_P` | Customize the `top_p` used for the agent; e.g. `SQL_TOP_P` | -| `_GLOBAL_TOOLS` | Customize the `global_tools` that are enabled for the agent (a JSON string array); e.g. `SQL_GLOBAL_TOOLS` | -| `_MCP_SERVERS` | Customize the `mcp_servers` that are enabled for the agent (a JSON string array); e.g. `SQL_MCP_SERVERS` | -| `_AGENT_SESSION` | Customize the `agent_session` used with the agent; e.g. `SQL_SESSION` | -| `_INSTRUCTIONS` | Customize the `instructions` for the agent; e.g. `SQL_INSTRUCTIONS` | -| `_VARIABLES` | Customize the `variables` used for the agent (in JSON format of `[{"key1": "value1", "key2": "value2"}]`);
e.g. `SQL_VARIABLES` | - -## Logging Related Variables -The following variables can be used to change the log level of Loki or the location of the log file: - -| Environment Variable | Description | Default Value | -|----------------------|---------------------------------------------|----------------------------------| -| `LOKI_LOG_LEVEL` | Customize the log level of Loki | `INFO` | -| `LOKI_LOG_FILE` | Customize the location of the Loki log file | `/loki/loki.log` | - -**Pro-Tip:** You can always tail the Loki logs using the `--tail-logs` flag. If you need to disable color output, you -can also pass the `--disable-log-colors` flag as well. - -## Miscellaneous Variables -| Environment Variable | Description | Default Value | -|----------------------|--------------------------------------------------------------------------------------------------|---------------| -| `AUTO_CONFIRM` | Bypass all `guard_*` checks in the bash prompt helpers; useful for agent composition and routing | | \ No newline at end of file diff --git a/docs/MACROS.md b/docs/MACROS.md deleted file mode 100644 index a90aafe..0000000 --- a/docs/MACROS.md +++ /dev/null @@ -1,103 +0,0 @@ -# Macros -Macros are essentially Loki "scripts"; that is, a predefined sequence of REPL commands that automate repetitive tasks or -workflows. Macros run in isolated environments, ensuring that the macros don't inherit any pre-existing role, session, -RAG, or agent state, and they will not affect your current context. - -This isolation ensures that your workspace remains clean and unaffected by macro operations. - -![Macro Example](./images/macros/macros-example.gif) - -For more information on Loki's REPL, refer to the [REPL](./REPL.md) documentation. - -## Quick Links - -- [Macro Definition](#macro-definition) - - [Step Definitions](#step-definitions) - - [Macro Variables](#macro-variables) -- [Built-In Macros](#built-in-macros) - - ---- - -## Macro Definition -Macros are defined as YAML files in the `macros` subdirectory of your Loki configuration directory. The Loki configuration -directory can vary between systems, so to find the location of your macros config directory, you can use the following -command: - -```shell -loki --info | grep 'macros_dir' | awk '{print $2}' -``` - -Macro definitions are broken into two parts: the `steps` of the macro, and an optional `variables` section that lets -users pass in variables to alter the behavior of the macro at runtime. - -### Step Definitions -The step definitions for a macro are straightforward: They are simply the exact commands you would otherwise type in the -REPL. - -**Example: Macro to generate a git commit message** -`macros/generate-commit-message.yaml` -```yaml -steps: - - .file `git diff` -- generate git commit message -``` -Usage: -```shell -$ loki --macro generate-commit-message ->> .file `git diff` -- generate a git commit message -Add documentation on macros -``` - -For a full example configuration, refer to the [example macro configuration file](../config.macro.example.yaml) in the root of this project. - -### Macro Variables -Sometimes it's useful to be able to modify the behavior of a macro at runtime. This is achieved with the `variables` -array of the macro definition. - -To pass variables to a macro, since they are just Loki scripts, the syntax is the same as it is for any other scripting -language: You just pass them alongside your invocation. - -**Example:** -```shell -$ loki --macro example-variable-macro first_argument second_argument -``` - -Each variable in the `variables` array has the following properties: -* `name` (Required): the name of the variable, which can be referenced in the actual steps of the macro using the - `{{name}}` syntax. -* `default` (Optional): A default value for the variable if no value is specified. If no default value is defined, and - no value is provided for the variable at runtime, Loki will error out. -* `rest` (Optional, Boolean): When set to `true`, this variable will collect all remaining arguments passed to the - macro. This behavior is only applicable when the variable is the last variable in the list. By default, this is - `false`. - -The `variables` array is order-dependent; that is to say that all arguments passed to the macro are positional. So be -careful about the ordering if that is important to your macro's invocation. - -**Example: Simple variable example to invoke an agent** -`macros/invoke-agent.yaml` -```yaml -variables: - - name: agent # No default value means this must be defined at runtime - - name: args - rest: true # All remaining arguments to the macro are collected into this variable - default: What can you do? # This is used if no value is passed at runtime -steps: - - .agent {{agent}} - - '{{args}}' -``` -Usage: -```shell -$ loki --macro invoke-agent sql -# or -$ loki --macro invoke-agent sql What tables are available? -``` - -For a full example configuration, refer to the [example macro configuration file](../config.macro.example.yaml) in the root of this project. - -## Built-In Macros -Loki comes packaged with some useful built-in macros. These are also good examples if you're looking for more examples -on how to make your own macros, so be sure to check out the [built-in macro definitions](../assets/macros) if you're -looking for more examples. - -* `generate-commit-message` - Generate a Git commit message based on the staged changes in the current directory diff --git a/docs/RAG.md b/docs/RAG.md deleted file mode 100644 index ea3fe9e..0000000 --- a/docs/RAG.md +++ /dev/null @@ -1,307 +0,0 @@ -# RAG -Retrieval Augmented Generation (RAG) is a method of minimizing LLM hallucinations and extending the model's context -without consuming a significant portion of the context length. It uses documents and other additional resources that you -provide to give the model more context for all of your prompts. - -Loki has a built-in vector database and full-text search engine to support RAG knowledge bases for your queries. - -The generated knowledge bases are stored in the `rag` subdirectory of your Loki configuration directory. The location of -this directory varies by system, so you can use the following command to find your RAG directory: - -```shell -loki --info | grep 'rags_dir' | awk '{print $2}' -``` - -## Quick Links - -- [Usage](#usage) - - [Persistent RAG](#persistent-rag) - - [Ephemeral RAG](#ephemeral-rag) -- [How It Works](#how-it-works) - - [1. Build](#1-build) - - [2. Lookup](#2-lookup) - - [2a. Reranking (Optional)](#2a-reranking-optional) - - [3. Prompt](#3-prompt) -- [Supported Document Sources](#supported-document-sources) -- [Document Loaders](#document-loaders) - - [Document Loader Usage](#document-loader-usage) -- [Advanced Customizations](#advanced-customizations) - - [Embedding Model](#embedding-model) - - [Reranker](#reranker) - - [Chunk Size](#chunk-size) - - [Trade-Offs](#chunk-size-trade-offs) - - [Chunk Overlap](#chunk-overlap) - - [Top K](#top-k) - - [Trade-Offs](#top-k-trade-offs) - - [RAG Template](#rag-template) - - ---- - -## Usage -There's two ways to use RAG in Loki: A persistent RAG that can be loaded on-demand for queries, and an ephemeral one for -adding RAG to a single specific query. - -### Persistent RAG -In the REPL, persistent RAG is initialized via the `.rag` command: - -![Persistent RAG example](./images/rag/persistent-rag.gif) - -The generated RAG is then saved to the `rag` subdirectory of the Loki configuration, and can then be loaded whenever you -want that knowledge base via either `.rag ` or `loki --rag `. - -### Ephemeral RAG -Short-lived RAG that is only used for a single session or query is loaded using `.file`/`--file`. - -You can use it to either execute a prompt from a file, or for temporary RAG. The difference is the usage of the `--` -separator. If you only specify a filename and no `--` separator, Loki will know to read the file contents and pass them -as a query to the model. Otherwise, the `--` separator is read to indicate that this is the end of the list of documents -to load into the ephemeral RAG, and what follows is the query to pass to the model. - -```shell -.file prompt.md # Read the file as a prompt -.file %% -- translate the last reply to italian -.file `git diff` -- generate a commit message -``` - -![Ephemeral RAG Example](./images/rag/ephemeral-rag.gif) - -Once the session ends, this RAG will no longer be accessible and is only visible to the current session. - -#### The `%%` Document Type -In addition to the usual documents that can be specified for persistent RAG, ephemeral RAG has a special `%%` value. -This value references the content of the last reply. So you can use it like this: - -```shell -.file %% -- translate the last reply to italian -``` - -The `--` indicates that this is the end of your documents and the beginning of your prompt. - -#### The `cmd` Document Type -Loki also lets you use command outputs for ephemeral RAG input. Simply enclose the command in backticks: - -```shell -.file `git diff` -- generate a commit message -``` - -The `--` indicates that this is the end of your documents and the beginning of your prompt. - -## How It Works -#### 1. Build -When you define RAG, Loki will first "build" the RAG. This means that Loki will consume the documents you specified and -generate [embeddings](https://huggingface.co/spaces/hesamation/primer-llm-embedding) for that text. This essentially just means that Loki translates the document into a language -the LLM can understand. - -These embeddings are then stored in an in-memory vector database. - -#### 2. Lookup -Loki sits between you and the model. So when you submit a prompt to the model, before Loki ever sends it, it will first -convert your prompt into embeddings (LLM language), and look for relevant snippets of text in the vector database. - -Loki then passes the top `n`-snippets of text that it finds in the vector database as additional context to the model -before your prompt. - -#### 2a. Reranking (Optional) -The lookup for relevant snippets of texts uses embeddings to find text that is semantically similar to your prompt, and -returns the top `n`-results. This often works fairly well, however these top results aren't always the most relevant for -answering the specific query. - -Reranking improves these initial results (say, the top 20-100 text snippets) and re-scores them using a more -sophisticated model. The reranker model will rank documents by their actual usefulness for answering the query to ensure -the most relevant context is passed to the model alongside your query. - -This reranking model can be customized for each RAG you build in Loki. See the [Custom Reranker](#reranker) section -below for more details on how to customize this. - -#### 3. Prompt -Finally, the text snippets that were looked up in RAG are passed to the model as additional context to your prompt, -giving the model query-specific context to answer your question. - -## Supported Document Sources -Loki supports a number of document sources that can be used for RAG: - -| Source | Example | Comments | -|--------------------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| -| Files | `/tmp/dir1/file1;/tmp/dir1/file2` | | -| Directory | `/tmp/dir` | Picks up all files in a directory and all its subdirectories | -| Directory (extensions} | `/tmp/dir2/**/*.{md,txt}` | Finds all files in all subdirectories with the specified extensions | -| Recursive Filename | `/tmp/*/LOKI.md` | The following files will be picked up:
  • `/tmp/dir1/LOKI.md`
  • `/tmp/dir2/subdir1/LOKI.md`
  • `/tmp/dir2/subdir2/LOKI.md`
| -| URL | `https://www.ohdsi.org/data-standardization/` | Downloads and loads the specified webpage into the
knowledge base | -| Recursive URL (Websites) | `https://github.com/OHDSI/Vocabulary-v5.0/wiki/**` | Crawls all pages under the given URL and loads them
into the knowledge base | -| Document Loader (custom) | `jina:https://cloud.google.com/bigquery/docs/reference/standard-sql/` | Use a custom document loader to parse the given document | - -## Document Loaders -Loki only has built-in support for loading text files. But that functionality can be extended to read all kinds of files -into your knowledge bases. These custom loaders are used by both RAG and for documents specified using the -`.file`/`--file` flags. - -In the global configuration file, you can specify loaders for specific document types using the `document_loaders` -setting. Each loader is defined by specifying a name and then a command that Loki will execute to load the document. - -The following variables are interpolated at runtime by Loki and can be used as placeholders in your command definitions: -* `$1` (Required) - The input file -* `$2` (Optional) - The output file. If omitted, `stdout` is used as the output destination - -**Note:** It is your responsibility to ensure that any tools used to parse documents into text that Loki can read are -installed on your system and are available on your `$PATH`. Loki does not have any built-in way of installing -dependencies for document loaders for you. - -The following are some example loaders: -```yaml -document_loaders: - pdf: 'pdftotext $1 -' # Use pdftotext to convert a PDF file to text - # (see https://poppler.freedesktop.org for details on how to install pdftotext) - docx: 'pandoc --to plain $1' # Use pandoc to convert a .docx file to text - # (see https://pandoc.org for details on how to install pandoc) - jina: 'curl -fsSL https://r.jina.ai/$1 -H "Authorization: Bearer {{JINA_API_KEY}}' # Use Jina to translate a website into text; - # Requires a Jina API key to be added to the Loki vault - git: > # Use yek to load a git repository into the knowledgebase (https://github.com/bodo-run/yek) - sh -c "yek $1 --json | jq 'map({ path: .filename, contents: .content })'" -``` - -### Document Loader Usage -Once you have your loaders defined, you can specify when Loki should use them by prefixing any RAG file/directory/URI -with the name of the loader. - -**Example: Load a git repo into RAG** -![Git Repo Loader Example](./images/rag/git-loader.png) - -**Example: Use pdf loader for ephemeral RAG** -```shell -$ loki --file pdf:some-file.pdf -``` - -## Advanced Customizations -For those familiar with RAG, Loki exposes a handful of advanced global settings that can be used to tweak your default -RAG configurations. - -### Embedding Model -When Loki queries your RAG knowledge bases, it needs to first convert your query into embeddings. By default, Loki uses -the same embedding model that was used to create the knowledge base in the first place. - -This can be customized to any other embedding model available in your configured clients by setting the -`rag_embedding_model` setting in your global Loki configuration file: - -```yaml -rag_embedding_model: null # Specifies the embedding model used for context retrieval -``` - -### Reranker -By default, Loki uses [Reciprocal Rank Fusion (RRF)](https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion) to merge vector and keyword search results. - -You can change the default reranker model to any other reranking model in your configured clients. To change the default -reranker model, simply change the value of the `rag_reranker_model` setting in your global configuration file: - -```yaml -rag_reranker_model: null # By default, -``` - -### Chunk Size -In the context of RAG, the chunk size is the maximum length of each text chunk (measured in characters) that is created -when splitting documents. In Loki, this defaults to `2000` characters. - -You can specify a different global default by setting the `rag_chunk_size` property in your global configuration file: - -```yaml -rag_chunk_size: null # Defines the size of chunks for document processing in characters -``` - -#### Chunk Size Trade-Offs -Keep in mind the following trade-offs when changing the chunk size: - -* **Smaller chunks (e.g. 256 characters):** More precise retrieval, better semantic focus, but may lack context or split - important information -* **Larger chunks (e.g. 1024 characters):** More context preserved, fewer chunks to manage, but less precise matching - and more noise in retrieved document - -### Chunk Overlap -Chunk overlap in RAG is the number of characters that overlap between consecutive chunks to maintain continuity. - ---- - -**Example:** If the following sentence is cut off at the end of one chunk - -`I was doing fine until someone brought up` - -You'll ideally want that full sentence to be picked up at the beginning of the next chunk to make sure the full meaning -is captured. So in this example, if your chunk overlap is 42 characters, then the start of the next chunk would look -like this: - -`I was doing fine until someone brought up the game. ` - ---- - -Often, this value is 10%-20% of the chunk size. - -By default, in Loki, this value is 5% the chunk size. You can override this and specify the default chunk overlap (in -characters) that Loki should use as a global default by setting the `rag_chunk_overlap` property in the global Loki -configuration file: - -```yaml -rag_chunk_overlap: null # Defines the overlap between chunks -``` - -### Top K -In RAG, `top_k` represents the top `k`-chunks to return from the vector database query. Think of it like if you search -something on Google and only care about the top 10 results, that's what you'll use for your context. - -In Loki, the default value for this is `5`. You can customize this global default by setting the `rag_top_k` property in -your global configuration file: - -```yaml -rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries -``` - -#### Top K Trade-Offs -When customizing this value, keep in mind the following trade-offs so you get the best performance: - -* **Lower top_k (e.g. 3):** Faster, more focused context, lower cost, but risks missing relevant information -* **Higher top_k (e.g. 10):** More comprehensive coverage, but more noise, higher latency, increased token costs, and - potential context window constraints - -### RAG Template -When you use RAG in Loki, after Loki performs the lookup for relevant chunks of text to add as context to your query, it -will add the retrieved text chunks as context to your query before sending it to the model. The format of this context -is determined by the `rag_template` setting in your global Loki configuration file. - -This template utilizes three placeholders: -* `__INPUT__`: The user's actual query -* `__CONTEXT__`: The context retrieved from RAG -* `__SOURCES__`: A numbered list of the source file paths or URLs that the retrieved context came from - -These placeholders are replaced with the corresponding values into the template and make up what's actually passed to -the model at query-time. The `__SOURCES__` placeholder enables the model to cite which documents its answer is based on, -which is especially useful when building knowledge-base assistants that need to provide verifiable references. - -The default template that Loki uses is the following: - -```text -Answer the query based on the context while respecting the rules. (user query, some textual context and rules, all inside xml tags) - - -__CONTEXT__ - - - -__SOURCES__ - - - -- If you don't know, just say so. -- If you are not sure, ask for clarification. -- Answer in the same language as the user query. -- If the context appears unreadable or of poor quality, tell the user then answer as best as you can. -- If the answer is not in the context but you think you know the answer, explain that to the user then answer with your own knowledge. -- Answer directly and without using xml tags. -- When using information from the context, cite the relevant source from the section. - - - -__INPUT__ - -``` - -You can customize this template by specifying the `rag_template` setting in your global Loki configuration file. Your -template *must* include both the `__INPUT__` and `__CONTEXT__` placeholders in order for it to be valid. The -`__SOURCES__` placeholder is optional. If it is omitted, source references will not be included in the prompt. diff --git a/docs/REPL-PROMPT.md b/docs/REPL-PROMPT.md deleted file mode 100644 index 01acbb9..0000000 --- a/docs/REPL-PROMPT.md +++ /dev/null @@ -1,117 +0,0 @@ -# Customize REPL Prompt - -The prompt you see when you start the Loki REPL can be customized to your liking. This is achieved via the `left_prompt` -and `right_prompt` settings in the global Loki configuration file: - -```yaml -left_prompt: '{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} ' -right_prompt: '{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}' -``` - -The location of the global configuration file differs between systems, so you can use the following command to find your -global configuration file's location: - -```shell -loki --info | grep 'config_file' | awk '{print $2}' -``` - -## Quick Links - -- [Syntax](#syntax) -- [Variables](#variables) - - -## Syntax -The syntax for the prompts consists of plain text and templates contained in `{...}`. The plain text is -printed exactly as given. - -The syntax for the templates `{...}` is as follows: - -* `{variable}` - Replaced with the value of `variable` -* `{?variable