15 Commits

Author SHA1 Message Date
github-actions[bot] d48b11dcfa chore: bump Cargo.toml to 0.7.4
CI / All (ubuntu-latest) (push) Failing after 24s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-07-02 01:06:00 +00:00
github-actions[bot] 86dd922d2c bump: version 0.7.3 → 0.7.4 [skip ci] 2026-07-02 01:05:42 +00:00
Dark-Alex-17 9ec20d74a4 chore: updated models.yaml
CI / All (ubuntu-latest) (push) Failing after 24s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-07-01 19:04:55 -06:00
Dark-Alex-17 c78cdef5ae fix: Added back in --kit specification for the running of the sbx
CI / All (ubuntu-latest) (push) Failing after 24s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-07-01 18:52:34 -06:00
Dark-Alex-17 3df590f276 fix: sbx isn't copying base files in their respective directories
CI / All (ubuntu-latest) (push) Failing after 25s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-07-01 18:44:07 -06:00
Dark-Alex-17 91300c16fe fix: Update deprecated sbx kit config 2026-07-01 17:52:04 -06:00
Dark-Alex-17 52356ead6c fix: Properly chown the coyote config recursively and password file in the sbx
CI / All (ubuntu-latest) (push) Failing after 25s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-07-01 17:12:42 -06:00
Dark-Alex-17 ad9fc524d4 feat: Pin specific usql version to sbx kit 2026-07-01 17:11:27 -06:00
Dark-Alex-17 af50909a89 feat: recursively take ownership over the copied in coyote config for the sbx 2026-07-01 16:54:57 -06:00
Dark-Alex-17 318d9ba1cd feat: explicitly specify the COYOTE_CONFIG_DIR in the sbx kit 2026-07-01 16:54:42 -06:00
Dark-Alex-17 45d709f28e Merge branch 'main' of github.com:Dark-Alex-17/coyote
CI / All (ubuntu-latest) (push) Failing after 25s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-06-30 14:55:08 -06:00
Dark-Alex-17 9cd074cb9b build: fixed coyote install scripts 2026-06-30 14:54:59 -06:00
Dark-Alex-17 93eec45473 docs: fixed coyote install script URLs 2026-06-30 14:43:00 -06:00
Dark-Alex-17 e585e0b049 feat: --tail-logs can track log rollovers and incoporates a sleep timer to minimize idle CPU cycles
CI / All (ubuntu-latest) (push) Failing after 25s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-06-25 14:01:38 -06:00
Dark-Alex-17 13bfaf9aca feat: Added support for log rolling so log files don't just blow up over time 2026-06-25 13:57:15 -06:00
11 changed files with 278 additions and 167 deletions
+17
View File
@@ -1,3 +1,20 @@
## v0.7.4 (2026-07-02)
### Feat
- Pin specific usql version to sbx kit
- recursively take ownership over the copied in coyote config for the sbx
- explicitly specify the COYOTE_CONFIG_DIR in the sbx kit
- --tail-logs can track log rollovers and incoporates a sleep timer to minimize idle CPU cycles
- Added support for log rolling so log files don't just blow up over time
### Fix
- Added back in --kit specification for the running of the sbx
- sbx isn't copying base files in their respective directories
- Update deprecated sbx kit config
- Properly chown the coyote config recursively and password file in the sbx
## v0.7.3 (2026-06-24) ## v0.7.3 (2026-06-24)
### Fix ### Fix
Generated
+54 -60
View File
@@ -141,9 +141,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.102" version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -174,9 +174,9 @@ dependencies = [
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "1.9.1" version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
dependencies = [ dependencies = [
"rustversion", "rustversion",
] ]
@@ -321,11 +321,11 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.17.0" version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
dependencies = [ dependencies = [
"aws-lc-sys 0.41.0", "aws-lc-sys 0.42.0",
"zeroize", "zeroize",
] ]
@@ -344,14 +344,15 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-sys" name = "aws-lc-sys"
version = "0.41.0" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
dependencies = [ dependencies = [
"cc", "cc",
"cmake", "cmake",
"dunce", "dunce",
"fs_extra", "fs_extra",
"pkg-config",
] ]
[[package]] [[package]]
@@ -1193,14 +1194,14 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.6.5" version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b"
dependencies = [ dependencies = [
"clap", "clap",
"clap_lex", "clap_lex",
"is_executable", "is_executable",
"shlex 1.3.0", "shlex 2.0.1",
] ]
[[package]] [[package]]
@@ -1321,9 +1322,9 @@ dependencies = [
[[package]] [[package]]
name = "console" name = "console"
version = "0.16.3" version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
@@ -1408,7 +1409,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "coyote-ai" name = "coyote-ai"
version = "0.7.3" version = "0.7.4"
dependencies = [ dependencies = [
"ansi_colours", "ansi_colours",
"anyhow", "anyhow",
@@ -2045,9 +2046,9 @@ dependencies = [
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "1.0.1" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
dependencies = [ dependencies = [
"log", "log",
"regex", "regex",
@@ -2055,9 +2056,9 @@ dependencies = [
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.11.10" version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -2775,9 +2776,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
@@ -3069,9 +3070,9 @@ dependencies = [
[[package]] [[package]]
name = "indicatif" name = "indicatif"
version = "0.18.4" version = "0.18.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c"
dependencies = [ dependencies = [
"console", "console",
"portable-atomic", "portable-atomic",
@@ -3189,9 +3190,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.29" version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634"
dependencies = [ dependencies = [
"defmt", "defmt",
"jiff-static", "jiff-static",
@@ -3203,9 +3204,9 @@ dependencies = [
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.29" version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3273,9 +3274,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.102" version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -3345,9 +3346,9 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.17" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -3940,13 +3941,12 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]] [[package]]
name = "open" name = "open"
version = "5.3.5" version = "5.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" checksum = "cd8d3b65c44123a56e0133d2cd06ce4361bd3ca99d41198b2f25e3c3db9b8b4a"
dependencies = [ dependencies = [
"is-wsl", "is-wsl",
"libc", "libc",
"pathdiff",
] ]
[[package]] [[package]]
@@ -4112,12 +4112,6 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "pem" name = "pem"
version = "3.0.6" version = "3.0.6"
@@ -4986,9 +4980,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.14.1" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
dependencies = [ dependencies = [
"web-time", "web-time",
"zeroize", "zeroize",
@@ -6265,9 +6259,9 @@ dependencies = [
[[package]] [[package]]
name = "tree-sitter" name = "tree-sitter"
version = "0.26.9" version = "0.26.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dab76d0b724ba557954125188cf0633a1ca43199ced82d95c7b9c32cc3de1f3" checksum = "3c343ed63e3f5c64d1acdecb5d2c13d4e169cb5fde0052106ebaa6c6f27f9e55"
dependencies = [ dependencies = [
"cc", "cc",
"regex", "regex",
@@ -6543,9 +6537,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.23.3" version = "1.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
dependencies = [ dependencies = [
"getrandom 0.4.3", "getrandom 0.4.3",
"js-sys", "js-sys",
@@ -6645,9 +6639,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.125" version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -6658,9 +6652,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.75" version = "0.4.76"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -6668,9 +6662,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.125" version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -6678,9 +6672,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.125" version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@@ -6691,9 +6685,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.125" version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -6796,9 +6790,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.102" version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -7450,9 +7444,9 @@ dependencies = [
[[package]] [[package]]
name = "zlib-rs" name = "zlib-rs"
version = "0.6.4" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5"
[[package]] [[package]]
name = "zmij" name = "zmij"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "coyote-ai" name = "coyote-ai"
version = "0.7.3" version = "0.7.4"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "An all-in-one, batteries included LLM CLI Tool" description = "An all-in-one, batteries included LLM CLI Tool"
@@ -49,7 +49,7 @@ textwrap = "0.16.0"
ansi_colours = "1.2.2" ansi_colours = "1.2.2"
eventsource-stream = "0.2.3" eventsource-stream = "0.2.3"
log = "0.4.28" log = "0.4.28"
log4rs = { version = "1.4.0", features = ["file_appender"] } log4rs = { version = "1.4.0", features = ["file_appender", "rolling_file_appender", "compound_policy", "fixed_window_roller", "size_trigger"] }
shell-words = "1.1.0" shell-words = "1.1.0"
sha2 = "0.10.8" sha2 = "0.10.8"
unicode-width = "0.2.0" unicode-width = "0.2.0"
+2 -2
View File
@@ -98,7 +98,7 @@ You can use the following command to run a bash script that downloads and instal
OS (Linux/MacOS) and architecture (x86_64/arm64): OS (Linux/MacOS) and architecture (x86_64/arm64):
```shell ```shell
curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/install_coyote.sh | bash curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/scripts/install_coyote.sh | bash
``` ```
#### Windows/Linux/MacOS (`PowerShell`) #### Windows/Linux/MacOS (`PowerShell`)
@@ -106,7 +106,7 @@ You can use the following command to run a PowerShell script that downloads and
for your OS (Windows/Linux/MacOS) and architecture (x86_64/arm64): for your OS (Windows/Linux/MacOS) and architecture (x86_64/arm64):
```powershell ```powershell
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/scripts/install_coyote.ps1 | iex" powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/scripts/install_coyote.ps1 | iex"
``` ```
### Manual ### Manual
+61 -56
View File
@@ -5,20 +5,19 @@
# sbx cp $HOME/.config/coyote/ testing:/home/agent/.config/ # sbx cp $HOME/.config/coyote/ testing:/home/agent/.config/
# sbx cp $HOME/.coyote_password testing:/home/agent/ # sbx cp $HOME/.coyote_password testing:/home/agent/
# sbx run testing --kit ./sbx-kit/ # sbx run testing --kit ./sbx-kit/
schemaVersion: "1" schemaVersion: '1'
kind: agent kind: sandbox
name: coyote name: coyote
displayName: Coyote displayName: Coyote
description: > description: >
An all-in-one, batteries-included LLM CLI tool featuring Shell Assistant, An all-in-one, batteries-included LLM CLI tool featuring Shell Assistant,
CLI & REPL mode, RAG, AI tools & agents, MCP servers, skills, and macros. CLI & REPL mode, RAG, AI tools & agents, MCP servers, skills, and macros.
agent: sandbox:
image: "docker/sandbox-templates:shell-docker" image: 'docker/sandbox-templates:shell-docker'
aiFilename: COYOTE.md aiFilename: COYOTE.md
# persistence: persistent
entrypoint: entrypoint:
run: ["bash", "-lc", "exec /home/agent/.cargo/bin/coyote"] run: ['bash', '-lc', 'exec /home/agent/.cargo/bin/coyote']
network: network:
# Proxy-managed LLM providers: the proxy substitutes `proxy-managed` for # Proxy-managed LLM providers: the proxy substitutes `proxy-managed` for
@@ -51,96 +50,96 @@ network:
serviceAuth: serviceAuth:
openai: openai:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
anthropic: anthropic:
headerName: x-api-key headerName: x-api-key
valueFormat: "%s" valueFormat: '%s'
gemini: gemini:
headerName: x-goog-api-key headerName: x-goog-api-key
valueFormat: "%s" valueFormat: '%s'
cohere: cohere:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
groq: groq:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
openrouter: openrouter:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
ai21: ai21:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
cloudflare: cloudflare:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
deepinfra: deepinfra:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
deepseek: deepseek:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
mistral: mistral:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
perplexity: perplexity:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
voyageai: voyageai:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
xai: xai:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
jina: jina:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
ernie: ernie:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
hunyuan: hunyuan:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
minimax: minimax:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
moonshot: moonshot:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
qianwen: qianwen:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
zhipuai: zhipuai:
headerName: Authorization headerName: Authorization
valueFormat: "Bearer %s" valueFormat: 'Bearer %s'
allowedDomains: allowedDomains:
# Coyote release + self-update + model-registry sync # Coyote release + self-update + model-registry sync
- "github.com:443" - 'github.com:443'
- "api.github.com:443" - 'api.github.com:443'
- "raw.githubusercontent.com:443" - 'raw.githubusercontent.com:443'
- "objects.githubusercontent.com:443" - 'objects.githubusercontent.com:443'
- "*.githubusercontent.com:443" - '*.githubusercontent.com:443'
# Coyote install paths (cargo install + uv + rustup + Python tool deps at runtime) # Coyote install paths (cargo install + uv + rustup + Python tool deps at runtime)
- "crates.io:443" - 'crates.io:443'
- "static.crates.io:443" - 'static.crates.io:443'
- "pypi.org:443" - 'pypi.org:443'
- "files.pythonhosted.org:443" - 'files.pythonhosted.org:443'
- "astral.sh:443" - 'astral.sh:443'
- "sh.rustup.rs:443" - 'sh.rustup.rs:443'
- "static.rust-lang.org:443" - 'static.rust-lang.org:443'
# LLM model OAuth + API endpoints # LLM model OAuth + API endpoints
- "claude.ai:443" - 'claude.ai:443'
- "console.anthropic.com:443" - 'console.anthropic.com:443'
- "accounts.google.com:443" - 'accounts.google.com:443'
# *.googleapis.com covers oauth2 + userinfo + VertexAI regional endpoints # *.googleapis.com covers oauth2 + userinfo + VertexAI regional endpoints
# (*-aiplatform.googleapis.com). Do not narrow without re-checking VertexAI. # (*-aiplatform.googleapis.com). Do not narrow without re-checking VertexAI.
- "*.googleapis.com:443" - '*.googleapis.com:443'
# Bedrock and GitHub Models use signed / GitHub-PAT auth that the proxy # Bedrock and GitHub Models use signed / GitHub-PAT auth that the proxy
# cannot rewrite. Domains are allow-listed; credentials must be injected # cannot rewrite. Domains are allow-listed; credentials must be injected
# separately (see README "Extending"). # separately (see README "Extending").
- "*.amazonaws.com:443" - '*.amazonaws.com:443'
- "models.inference.ai.azure.com:443" - 'models.inference.ai.azure.com:443'
credentials: credentials:
sources: sources:
@@ -211,8 +210,9 @@ credentials:
environment: environment:
variables: variables:
IS_SANDBOX: "1" IS_SANDBOX: '1'
COYOTE_LOG_LEVEL: INFO COYOTE_LOG_LEVEL: INFO
COYOTE_CONFIG_DIR: /home/agent/.config/coyote
proxyManaged: proxyManaged:
- OPENAI_API_KEY - OPENAI_API_KEY
- ANTHROPIC_API_KEY - ANTHROPIC_API_KEY
@@ -250,14 +250,14 @@ commands:
libssl-dev \ libssl-dev \
pandoc \ pandoc \
bzip2 bzip2
user: "1000" user: '1000'
description: Install system prerequisites (including pandoc for fetch_url_via_curl) description: Install system prerequisites (including pandoc for fetch_url_via_curl)
- command: "curl -LsSf https://astral.sh/uv/install.sh | sh" - command: 'curl -LsSf https://astral.sh/uv/install.sh | sh'
user: "1000" user: '1000'
description: Install uv (required for Python-based custom tools) description: Install uv (required for Python-based custom tools)
- command: | - command: |
set -euo pipefail set -euo pipefail
USQL_VERSION=$(curl -sSL https://api.github.com/repos/xo/usql/releases/latest | jq -r .tag_name | sed 's/^v//') USQL_VERSION=0.21.4
ARCH=$(uname -m) ARCH=$(uname -m)
case "$ARCH" in case "$ARCH" in
x86_64) USQL_ARCH=amd64 ;; x86_64) USQL_ARCH=amd64 ;;
@@ -266,10 +266,10 @@ commands:
esac esac
TMPDIR=$(mktemp -d) TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT trap 'rm -rf "$TMPDIR"' EXIT
curl -sSL "https://github.com/xo/usql/releases/download/v${USQL_VERSION}/usql_static-${USQL_VERSION}-linux-${USQL_ARCH}.tar.bz2" -o "$TMPDIR/usql.tar.bz2" curl -fsSL --retry 3 "https://github.com/xo/usql/releases/download/v${USQL_VERSION}/usql_static-${USQL_VERSION}-linux-${USQL_ARCH}.tar.bz2" -o "$TMPDIR/usql.tar.bz2"
tar -xjf "$TMPDIR/usql.tar.bz2" -C "$TMPDIR" tar -xjf "$TMPDIR/usql.tar.bz2" -C "$TMPDIR"
sudo install -m 0755 "$TMPDIR/usql_static" /usr/local/bin/usql sudo install -m 0755 "$TMPDIR/usql_static" /usr/local/bin/usql
user: "1000" user: '1000'
description: Install the usql universal SQL CLI (used by the built-in sql agent and execute_sql_code tool) description: Install the usql universal SQL CLI (used by the built-in sql agent and execute_sql_code tool)
- command: | - command: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
@@ -279,16 +279,21 @@ commands:
--target x86_64-unknown-linux-musl --target x86_64-unknown-linux-musl
. "$HOME/.cargo/env" . "$HOME/.cargo/env"
cargo install --locked coyote-ai cargo install --locked coyote-ai
user: "1000" user: '1000'
description: Install Coyote AI CLI via Rust's Cargo description: Install Coyote AI CLI via Rust's Cargo
startup: startup:
- command: ["sh", "-c", "test -f \"$HOME/.config/coyote/config.yaml\" || coyote --info >/dev/null 2>&1 || true"] - command:
user: "1000" [
'sh',
'-c',
'test -f "$HOME/.config/coyote/config.yaml" || coyote --info >/dev/null 2>&1 || true',
]
user: '1000'
background: false background: false
description: Bootstrap Coyote config directory on first sandbox start description: Bootstrap Coyote config directory on first sandbox start
memory: | agentContext: |
## Sandbox environment ## Sandbox environment
You are running inside a Docker sandbox launched via `sbx run coyote`. The You are running inside a Docker sandbox launched via `sbx run coyote`. The
+32
View File
@@ -377,6 +377,14 @@
thinking: thinking:
type: enabled type: enabled
budget_tokens: 16000 budget_tokens: 16000
- name: claude-sonnet-5
max_input_tokens: 1000000
max_output_tokens: 128000
require_max_tokens: true
input_price: 3
output_price: 15
supports_vision: true
supports_function_calling: true
- name: claude-sonnet-4-6 - name: claude-sonnet-4-6
max_input_tokens: 200000 max_input_tokens: 200000
max_output_tokens: 8192 max_output_tokens: 8192
@@ -922,6 +930,14 @@
thinking: thinking:
type: enabled type: enabled
budget_tokens: 16000 budget_tokens: 16000
- name: claude-sonnet-5
max_input_tokens: 1000000
max_output_tokens: 128000
require_max_tokens: true
input_price: 3
output_price: 15
supports_vision: true
supports_function_calling: true
- name: claude-sonnet-4-6 - name: claude-sonnet-4-6
max_input_tokens: 200000 max_input_tokens: 200000
max_output_tokens: 8192 max_output_tokens: 8192
@@ -1103,6 +1119,14 @@
thinking: thinking:
type: enabled type: enabled
budget_tokens: 16000 budget_tokens: 16000
- name: us.anthropic.claude-sonnet-5
max_input_tokens: 1000000
max_output_tokens: 128000
require_max_tokens: true
input_price: 3
output_price: 15
supports_vision: true
supports_function_calling: true
- name: us.anthropic.claude-sonnet-4-6 - name: us.anthropic.claude-sonnet-4-6
max_input_tokens: 200000 max_input_tokens: 200000
max_output_tokens: 8192 max_output_tokens: 8192
@@ -1785,6 +1809,14 @@
output_price: 25 output_price: 25
supports_vision: true supports_vision: true
supports_function_calling: true supports_function_calling: true
- name: anthropic/claude-sonnet-5
max_input_tokens: 1000000
max_output_tokens: 128000
require_max_tokens: true
input_price: 3
output_price: 15
supports_vision: true
supports_function_calling: true
- name: anthropic/claude-sonnet-4.6 - name: anthropic/claude-sonnet-4.6
max_input_tokens: 200000 max_input_tokens: 200000
max_output_tokens: 8192 max_output_tokens: 8192
+3 -3
View File
@@ -39,7 +39,7 @@ switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
if (-not $BinDir) { if (-not $BinDir) {
if ($isWin) { $BinDir = Join-Path $env:LOCALAPPDATA 'coyote\bin' } if ($isWin) { $BinDir = Join-Path $env:LOCALAPPDATA 'coyote\bin' }
else { $home = $env:HOME; if (-not $home) { $home = (Get-Item -Path ~).FullName }; $BinDir = Join-Path $home '.local/bin' } else { $userHome = $env:HOME; if (-not $userHome) { $userHome = (Get-Item -Path ~).FullName }; $BinDir = Join-Path $userHome '.local/bin' }
} }
New-Item -ItemType Directory -Force -Path $BinDir | Out-Null New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
@@ -95,13 +95,13 @@ if ($asset.name -match '\.zip$') {
[System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir)
} elseif ($asset.name -match '\.tar\.gz$' -or $asset.name -match '\.tgz$') { } elseif ($asset.name -match '\.tar\.gz$' -or $asset.name -match '\.tgz$') {
$tar = Get-Command tar -ErrorAction SilentlyContinue $tar = Get-Command tar -ErrorAction SilentlyContinue
if ($tar) { & $tar.FullName -xzf $archive -C $extractDir } if ($tar) { & $tar.Source -xzf $archive -C $extractDir }
else { Fail "Asset is tar archive but 'tar' is not available." } else { Fail "Asset is tar archive but 'tar' is not available." }
} else { } else {
try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) } try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) }
catch { catch {
$tar = Get-Command tar -ErrorAction SilentlyContinue $tar = Get-Command tar -ErrorAction SilentlyContinue
if ($tar) { & $tar.FullName -xf $archive -C $extractDir } else { Fail "Unknown archive format; neither zip nor tar workable." } if ($tar) { & $tar.Source -xf $archive -C $extractDir } else { Fail "Unknown archive format; neither zip nor tar workable." }
} }
} }
Regular → Executable
+13 -22
View File
@@ -133,30 +133,21 @@ else
echo "Error: unsupported OS for this installer: $OS" >&2; exit 1 echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
fi fi
DL_URLS=$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "$JSON" \
| sed -E 's/.*"browser_download_url":[[:space:]]*"//; s/"$//' \
|| true)
ASSET_NAME=""; ASSET_URL="" ASSET_NAME=""; ASSET_URL=""
for candidate in "${ASSET_CANDIDATES[@]}"; do for candidate in "${ASSET_CANDIDATES[@]}"; do
NAME=$(grep -oE '"name":\s*"[^"]+"' "$JSON" | sed 's/"name":\s*"//; s/"$//' | grep -Fx "$candidate" || true) while IFS= read -r url; do
if [[ -n "$NAME" ]]; then [[ -z "$url" ]] && continue
ASSET_NAME="$NAME" if [[ "$url" == */"$candidate" ]]; then
ASSET_URL=$(awk -v pat="$NAME" ' ASSET_NAME="$candidate"
BEGIN{ FS=":"; want=0 } ASSET_URL="$url"
/"name"/ { break
line=$0; fi
gsub(/^\s+|\s+$/,"",line); done <<< "$DL_URLS"
gsub(/"name"\s*:\s*"|"/ ,"", line); [[ -n "$ASSET_URL" ]] && break
want = (line==pat) ? 1 : 0;
next
}
want==1 && /"browser_download_url"/ {
u=$0;
gsub(/^\s+|\s+$/,"",u);
gsub(/.*"browser_download_url"\s*:\s*"|".*/ ,"", u);
print u;
exit
}
' "$JSON")
if [[ -n "$ASSET_URL" ]]; then break; fi
fi
done done
if [[ -z "$ASSET_URL" ]]; then if [[ -z "$ASSET_URL" ]]; then
+19 -3
View File
@@ -39,7 +39,10 @@ use client::ClientConfig;
use inquire::{Select, Text, set_global_render_config}; use inquire::{Select, Text, set_global_render_config};
use log::{LevelFilter, warn}; use log::{LevelFilter, warn};
use log4rs::append::console::ConsoleAppender; use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender; use log4rs::append::rolling_file::RollingFileAppender;
use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
use log4rs::config::{Appender, Logger, Root}; use log4rs::config::{Appender, Logger, Root};
use log4rs::encode::pattern::PatternEncoder; use log4rs::encode::pattern::PatternEncoder;
use oauth::OAuthProvider; use oauth::OAuthProvider;
@@ -585,7 +588,20 @@ fn setup_logger() -> Result<Option<PathBuf>> {
} }
Some(path) => { Some(path) => {
ensure_parent_exists(&path)?; ensure_parent_exists(&path)?;
let file_appender = FileAppender::builder().encoder(encoder.clone()).build(path);
let archive_pattern = path
.with_extension("archived.{}.log")
.to_string_lossy()
.into_owned();
let trigger = SizeTrigger::new(10 * 1024 * 1024);
let roller = FixedWindowRoller::builder()
.build(&archive_pattern, 5)
.unwrap();
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
let file_appender = RollingFileAppender::builder()
.encoder(encoder.clone())
.build(path, Box::new(policy));
match file_appender { match file_appender {
Ok(appender) => { Ok(appender) => {
@@ -608,7 +624,7 @@ fn setup_logger() -> Result<Option<PathBuf>> {
fn init_file_logger( fn init_file_logger(
log_level: LevelFilter, log_level: LevelFilter,
log_filter: Option<String>, log_filter: Option<String>,
file_appender: FileAppender, file_appender: RollingFileAppender,
) -> log4rs::Config { ) -> log4rs::Config {
let root_log_level = if log_filter.is_some() { let root_log_level = if log_filter.is_some() {
LevelFilter::Off LevelFilter::Off
+42 -11
View File
@@ -316,6 +316,7 @@ fn sandbox_exists(name: &str) -> Result<bool> {
fn create_sandbox(name: &str, kit_path: &Path, mixins: &[DiscoveredMixin]) -> Result<()> { fn create_sandbox(name: &str, kit_path: &Path, mixins: &[DiscoveredMixin]) -> Result<()> {
info!("Creating sandbox '{name}'"); info!("Creating sandbox '{name}'");
let args = build_create_args(name, kit_path, mixins)?; let args = build_create_args(name, kit_path, mixins)?;
debug!("sbx {}", args.join(" "));
let status = Command::new(SBX_BINARY) let status = Command::new(SBX_BINARY)
.args(&args) .args(&args)
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
@@ -342,6 +343,8 @@ fn build_create_args(
let mut args = vec![ let mut args = vec![
"create".to_string(), "create".to_string(),
"--name".to_string(),
name.to_string(),
"--kit".to_string(), "--kit".to_string(),
kit_str.to_string(), kit_str.to_string(),
]; ];
@@ -357,8 +360,6 @@ fn build_create_args(
} }
args.push(SANDBOX_AGENT.to_string()); args.push(SANDBOX_AGENT.to_string());
args.push("--name".to_string());
args.push(name.to_string());
args.push(".".to_string()); args.push(".".to_string());
Ok(args) Ok(args)
@@ -369,10 +370,17 @@ fn copy_host_files(name: &str) -> Result<()> {
let home_dir = dirs::home_dir().context("Could not determine home directory")?; let home_dir = dirs::home_dir().context("Could not determine home directory")?;
if config_dir.exists() { if config_dir.exists() {
ensure_sandbox_dir(name, "/home/agent/.config")?; let sandbox_config_dir = "/home/agent/.config/coyote";
let src = format!("{}/", config_dir.display()); ensure_sandbox_dir(name, sandbox_config_dir)?;
let dest = format!("{name}:/home/agent/.config/"); let dest = format!("{name}:{sandbox_config_dir}/");
sbx_cp(&src, &dest)?; for entry in fs::read_dir(&config_dir)
.with_context(|| format!("Failed to read {}", config_dir.display()))?
{
let entry = entry?;
let path = entry.path();
sbx_cp(&path.display().to_string(), &dest)?;
}
chown_agent_recursive(name, sandbox_config_dir)?;
} else { } else {
debug!( debug!(
"Skipping config copy: {} does not exist", "Skipping config copy: {} does not exist",
@@ -390,6 +398,7 @@ fn copy_host_files(name: &str) -> Result<()> {
} }
let dest = format!("{name}:{dest_path}"); let dest = format!("{name}:{dest_path}");
sbx_cp(&password_file.display().to_string(), &dest)?; sbx_cp(&password_file.display().to_string(), &dest)?;
chown_agent_recursive(name, &dest_path)?;
} }
Some(password_file) => { Some(password_file) => {
debug!( debug!(
@@ -505,8 +514,9 @@ fn exec_run(name: &str, kit_path: &Path) -> Result<()> {
let kit_str = kit_path let kit_str = kit_path
.to_str() .to_str()
.ok_or_else(|| anyhow!("Kit path is not valid UTF-8: {}", kit_path.display()))?; .ok_or_else(|| anyhow!("Kit path is not valid UTF-8: {}", kit_path.display()))?;
debug!("sbx run --name {name} --kit {kit_str}");
let status = Command::new(SBX_BINARY) let status = Command::new(SBX_BINARY)
.args(["run", name, "--kit", kit_str]) .args(["run", "--name", name, "--kit", kit_str])
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
@@ -520,6 +530,27 @@ fn exec_run(name: &str, kit_path: &Path) -> Result<()> {
Ok(()) Ok(())
} }
fn chown_agent_recursive(sandbox: &str, path: &str) -> Result<()> {
let path_q = shell_words::quote(path);
let cmd = format!("sudo chown -R agent:agent {path_q}");
debug!("sbx exec {sandbox}: {cmd}");
let status = Command::new(SBX_BINARY)
.args(["exec", sandbox, "sh", "-c", &cmd])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to spawn `sbx exec` to chown copied files")?;
if !status.success() {
bail!("Chowning '{path}' in sandbox failed: sbx exec exited with {status}");
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -621,6 +652,8 @@ mod tests {
args, args,
vec![ vec![
"create".to_string(), "create".to_string(),
"--name".to_string(),
"my-box".to_string(),
"--kit".to_string(), "--kit".to_string(),
"/cache/sbx-kit".to_string(), "/cache/sbx-kit".to_string(),
"--kit".to_string(), "--kit".to_string(),
@@ -628,8 +661,6 @@ mod tests {
"--kit".to_string(), "--kit".to_string(),
dir_b.display().to_string(), dir_b.display().to_string(),
"coyote".to_string(), "coyote".to_string(),
"--name".to_string(),
"my-box".to_string(),
".".to_string(), ".".to_string(),
] ]
); );
@@ -646,11 +677,11 @@ mod tests {
args, args,
vec![ vec![
"create".to_string(), "create".to_string(),
"--name".to_string(),
"box".to_string(),
"--kit".to_string(), "--kit".to_string(),
"/cache/sbx-kit".to_string(), "/cache/sbx-kit".to_string(),
"coyote".to_string(), "coyote".to_string(),
"--name".to_string(),
"box".to_string(),
".".to_string(), ".".to_string(),
] ]
); );
+33 -8
View File
@@ -1,9 +1,11 @@
use crate::config::paths; use crate::config::paths;
use colored::Colorize; use colored::Colorize;
use fancy_regex::Regex; use fancy_regex::Regex;
use std::fs::File; use std::fs::{self, File};
use std::io::{BufRead, BufReader, Seek, SeekFrom}; use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::process; use std::process;
use std::time::Duration;
use tokio::time::sleep;
pub async fn tail_logs(no_color: bool) { pub async fn tail_logs(no_color: bool) {
let re = Regex::new(r"^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+<(?P<opid>[^\s>]+)>\s+\[(?P<level>[A-Z]+)\]\s+(?P<logger>[^:]+):(?P<line>\d+)\s+-\s+(?P<message>.*)$").unwrap(); let re = Regex::new(r"^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+<(?P<opid>[^\s>]+)>\s+\[(?P<level>[A-Z]+)\]\s+(?P<logger>[^:]+):(?P<line>\d+)\s+-\s+(?P<message>.*)$").unwrap();
@@ -16,20 +18,43 @@ pub async fn tail_logs(no_color: bool) {
process::exit(1); process::exit(1);
}; };
let mut lines = reader.lines(); let mut line_buf = String::new();
loop { loop {
if let Some(Ok(line)) = lines.next() { match reader.read_line(&mut line_buf) {
if no_color { Ok(0) => {
println!("{line}"); if file_was_rotated(&file_path, &mut reader) {
} else { let file = File::open(&file_path).expect("Cannot open file");
let colored_line = colorize_log_line(&line, &re); reader = BufReader::new(file);
println!("{colored_line}"); }
sleep(Duration::from_millis(100)).await;
}
Ok(_) => {
let line = line_buf.trim_end();
if no_color {
println!("{line}");
} else {
let colored_line = colorize_log_line(line, &re);
println!("{colored_line}");
}
line_buf.clear();
}
Err(_) => {
line_buf.clear();
sleep(Duration::from_millis(100)).await;
} }
} }
} }
} }
fn file_was_rotated(path: &std::path::Path, reader: &mut BufReader<File>) -> bool {
let current_pos = reader.stream_position().unwrap_or(0);
match fs::metadata(path) {
Ok(metadata) => metadata.len() < current_pos,
Err(_) => true,
}
}
fn colorize_log_line(line: &str, re: &Regex) -> String { fn colorize_log_line(line: &str, re: &Regex) -> String {
if let Some(caps) = re.captures(line).expect("Failed to capture log line") { if let Some(caps) = re.captures(line).expect("Failed to capture log line") {
let level = &caps["level"]; let level = &caps["level"];