diff --git a/README.md b/README.md index 48ffca2..ccb2a35 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,15 @@ It renders an abtop-style dashboard with VibeMode quota windows, warning alerts, reset timers, remaining credits/requests, and optional local Codex/Claude agent context from `abtop --status-json`. +API cache behavior: + +- Successful live `/v1/me` responses are cached for 30 seconds by default. +- Set `cache_ttl_secs = 120` in `config.toml` to change the cache TTL. +- `--no-cache` always performs a live fetch and does not read or write the API + cache, including stale fallback. +- In monitor mode, pressing `r` clears the current account/endpoint cache entry + before forcing the next refresh. + Desktop notifications: ```bash diff --git a/README.ru.md b/README.ru.md index d345836..7fd92f7 100644 --- a/README.ru.md +++ b/README.ru.md @@ -264,6 +264,15 @@ vimit --monitor --threshold 5h=80:95,7d=90 vimit --fail-on warning --threshold 24h=85:98 ``` +Поведение API cache: + +- Успешные live-ответы `/v1/me` кэшируются на 30 секунд по умолчанию. +- В `config.toml` можно задать `cache_ttl_secs = 120`. +- `--no-cache` всегда делает live-запрос и не читает/не пишет API cache, + включая stale fallback. +- В monitor mode клавиша `r` очищает cache entry текущего аккаунта/endpoint и + затем запускает refresh. + Desktop-уведомления: ```bash diff --git a/src/cli/args.rs b/src/cli/args.rs index 69dd5c5..c8d04e1 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -54,6 +54,7 @@ pub struct Args { pub vpn: bool, pub auto_failover: bool, pub no_cache: bool, + pub cache_ttl_secs: u64, pub trend: bool, pub trend_days: u64, pub update: bool, @@ -95,6 +96,7 @@ where vpn: false, auto_failover: true, no_cache: false, + cache_ttl_secs: super::cache::DEFAULT_TTL_SECS, trend: false, trend_days: 30, update: false, @@ -338,6 +340,7 @@ OPTIONS: --api-key-env API key environment variable [default: VIBEMODE_API_KEY] --vpn Switch to fallback endpoint (r-api.vibemod.pro) --no-failover Disable automatic api/r-api endpoint failover + --no-cache Always fetch live data; do not read or write API cache -V, --version Print version -h, --help Print help @@ -384,6 +387,13 @@ mod tests { assert!(!args.auto_failover); } + #[test] + fn no_cache_flag_is_parsed() { + let args = parse_args(["--no-cache".to_string()]).unwrap(); + assert!(args.no_cache); + assert_eq!(args.cache_ttl_secs, crate::cli::cache::DEFAULT_TTL_SECS); + } + #[test] fn demo_and_mock_are_mutually_exclusive() { let error = parse_args([ diff --git a/src/cli/cache.rs b/src/cli/cache.rs index 762bc73..c75016b 100644 --- a/src/cli/cache.rs +++ b/src/cli/cache.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use std::time::{Duration, Instant}; const TABLE: TableDefinition<&[u8], &[u8]> = TableDefinition::new("api_cache"); -const DEFAULT_TTL_SECS: u64 = 30; +pub const DEFAULT_TTL_SECS: u64 = 30; pub struct CacheEntry { pub cache_key: String, @@ -43,7 +43,6 @@ impl CacheStore { })) } - #[allow(dead_code)] pub fn set_ttl(&mut self, secs: u64) { self.ttl = Duration::from_secs(secs); } @@ -62,7 +61,7 @@ impl CacheStore { .map(|d| d.as_secs()) .unwrap_or(0); let age_secs = now_secs.saturating_sub(cached_at_secs); - if Duration::from_secs(age_secs) > self.ttl { + if self.ttl.is_zero() || Duration::from_secs(age_secs) > self.ttl { return None; } let payload = parsed.get("payload")?.clone(); @@ -116,7 +115,6 @@ impl CacheStore { tx.commit().map_err(|e| format!("cache commit failed: {e}")) } - #[allow(dead_code)] pub fn ttl(&self) -> Duration { self.ttl } diff --git a/src/cli/config.rs b/src/cli/config.rs index 0bf2d49..88211e8 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -26,6 +26,7 @@ pub struct Config { pub danger: Option, pub threshold: Option, pub auto_failover: Option, + pub cache_ttl_secs: Option, } impl Config { @@ -120,6 +121,9 @@ impl Config { danger_threshold: self.danger(), window_thresholds: self.window_thresholds()?, auto_failover: self.auto_failover.unwrap_or(true), + cache_ttl_secs: self + .cache_ttl_secs + .unwrap_or(super::cache::DEFAULT_TTL_SECS), }) } } @@ -141,6 +145,7 @@ pub struct MergedConfig { pub danger_threshold: f64, pub window_thresholds: HashMap, pub auto_failover: bool, + pub cache_ttl_secs: u64, } fn default_config_path() -> Option { @@ -194,6 +199,7 @@ mod tests { preset = "compact" notify = true fail_on = "warning" + cache_ttl_secs = 120 "#; let config: Config = toml::from_str(toml_str).unwrap(); assert_eq!(config.theme(), Theme::Dracula); @@ -204,6 +210,7 @@ mod tests { assert!(matches!(config.preset(), Preset::Compact)); assert_eq!(config.notify, Some(true)); assert_eq!(config.fail_on(), FailOn::Warning); + assert_eq!(config.cache_ttl_secs, Some(120)); } #[test] @@ -246,6 +253,7 @@ mod tests { assert!(!merged.demo); assert!(!merged.monitor); assert!(merged.auto_failover); + assert_eq!(merged.cache_ttl_secs, super::super::cache::DEFAULT_TTL_SECS); } #[test] diff --git a/src/cli/init.rs b/src/cli/init.rs index 85f4fe7..b689176 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -52,6 +52,9 @@ pub fn run_init() -> Result { # Poll interval in seconds (0 = single run) # watch = 0 + +# API cache TTL in seconds; set to 0 to expire cached responses immediately. +# cache_ttl_secs = 30 "#; print!(" creating config.toml... "); io::stdout().flush().unwrap(); diff --git a/src/cli/mcp.rs b/src/cli/mcp.rs index 4719ce7..e30e4be 100644 --- a/src/cli/mcp.rs +++ b/src/cli/mcp.rs @@ -113,6 +113,7 @@ impl VimitMcpServer { vpn: false, auto_failover: true, no_cache, + cache_ttl_secs: cli::cache::DEFAULT_TTL_SECS, trend: false, trend_days: 30, update: false, diff --git a/src/main.rs b/src/main.rs index 0569adb..f55a9bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,7 +181,11 @@ fn real_main() -> Result { } let trends = TrendStore::open()?; - let cache = CacheStore::open()?; + let mut cache = CacheStore::open()?; + if let Some(store) = cache.as_mut() { + store.set_ttl(args.cache_ttl_secs); + } + let cache_ref = if args.no_cache { None } else { cache.as_ref() }; let mut notifier = Notifier::new(args.notify); let http = ng::HttpClient::new(ng::USER_AGENT)?; let mut router = ng::Router::new( @@ -213,7 +217,7 @@ fn real_main() -> Result { &account_configs, initial_idx, trends.as_ref(), - cache.as_ref(), + cache_ref, ); } @@ -225,7 +229,7 @@ fn real_main() -> Result { &mut notifier, &http, trends.as_ref(), - cache.as_ref(), + cache_ref, Some(&mut router), )?; if args.watch == 0 { @@ -320,6 +324,7 @@ fn merge_args_with_config(args: Args, merged: &MergedConfig) -> Args { vpn: args.vpn, auto_failover: args.auto_failover && merged.auto_failover, no_cache: args.no_cache, + cache_ttl_secs: merged.cache_ttl_secs, trend: args.trend, trend_days: args.trend_days, update: args.update,