claude-usage-widget/src-tauri/src/state.rs
megaproxy db9a10a4c2 Replace cap-based estimation with PTY-driven 'claude /usage' parser
The widget now spawns 'claude' via portable-pty, sends /usage, parses the
three rendered bars (Current session / Current week all / Current week
Sonnet), and shows the real percentages in the ring + weekly bars. A
background task refreshes every 5 minutes; the title-bar refresh button
forces an immediate fetch.

Drops the cap-tuning UI and tier card from Settings; adds a 'claude command'
override (e.g. 'wsl.exe -- claude' for Windows-host widgets reading WSL
credentials) and a refresh-interval setting. Fixes title-bar buttons getting
swallowed as drag attempts via data-tauri-drag-region="false".
2026-05-09 01:40:44 +01:00

63 lines
2 KiB
Rust

//! Process-global mutable state.
use parking_lot::{Mutex, RwLock};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;
use crate::cli_usage::CliUsage;
use crate::jsonl::UsageEvent;
use crate::settings::Settings;
use crate::watch::WatcherHandle;
/// Per-file parse cache. We resume reading from `byte_offset` on the next
/// poll, so we never re-parse already-seen lines.
pub struct FileCache {
pub mtime_ns: i128,
pub size: u64,
pub byte_offset: u64,
pub messages: Vec<UsageEvent>,
}
pub struct AppState {
pub roots: RwLock<Vec<PathBuf>>,
pub files: RwLock<HashMap<PathBuf, FileCache>>,
pub seen_ids: RwLock<HashSet<String>>,
pub settings: RwLock<Settings>,
/// Latest /usage result. Refreshed periodically by a background task,
/// or on-demand via the `refresh_cli_usage` command.
pub cli_usage: RwLock<Option<CliUsage>>,
/// Boxed so we can keep the watcher alive across the whole app lifetime
/// without polluting Tauri's setup hook.
pub watcher: Mutex<Option<WatcherHandle>>,
}
pub type SharedState = Arc<AppState>;
impl AppState {
pub fn new(settings: Settings) -> SharedState {
Arc::new(Self {
roots: RwLock::new(Vec::new()),
files: RwLock::new(HashMap::new()),
seen_ids: RwLock::new(HashSet::new()),
settings: RwLock::new(settings),
cli_usage: RwLock::new(None),
watcher: Mutex::new(None),
})
}
pub fn set_watcher(&self, w: WatcherHandle) {
*self.watcher.lock() = Some(w);
}
/// Snapshot all events across all cached files in one allocation.
/// Caller is responsible for sorting if needed.
pub fn collect_events(&self) -> Vec<UsageEvent> {
let files = self.files.read();
let mut out = Vec::with_capacity(files.values().map(|f| f.messages.len()).sum());
for f in files.values() {
out.extend(f.messages.iter().cloned());
}
out
}
}