118 lines
3.8 KiB
Rust
118 lines
3.8 KiB
Rust
//! Tauri command surface — every JS-callable function lives here.
|
||
|
||
use std::path::PathBuf;
|
||
|
||
use crate::paths::{list_wsl_distros, resolve_roots, ResolvedRoots};
|
||
use crate::settings::{save as save_settings, Caps, Settings};
|
||
use crate::state::SharedState;
|
||
use crate::tier::{detect_in_with_diagnostics, PlanTier};
|
||
use crate::usage::{build_snapshot, UsageSnapshot};
|
||
use crate::watch::refresh_and_emit;
|
||
|
||
#[tauri::command]
|
||
pub async fn get_snapshot(state: tauri::State<'_, SharedState>) -> Result<UsageSnapshot, String> {
|
||
let mut events = state.collect_events();
|
||
events.sort_by_key(|e| e.ts);
|
||
let caps = state.settings.read().caps.clone();
|
||
Ok(build_snapshot(&events, &caps, chrono::Utc::now()))
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn get_settings(state: tauri::State<'_, SharedState>) -> Result<Settings, String> {
|
||
Ok(state.settings.read().clone())
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn set_settings(
|
||
state: tauri::State<'_, SharedState>,
|
||
app: tauri::AppHandle,
|
||
new: Settings,
|
||
) -> Result<(), String> {
|
||
{
|
||
*state.settings.write() = new.clone();
|
||
}
|
||
save_settings(&new).map_err(|e| e.to_string())?;
|
||
|
||
// If roots-related settings changed, force a re-resolve + rescan.
|
||
{
|
||
let mut roots = state.roots.write();
|
||
roots.clear();
|
||
}
|
||
refresh_and_emit(&app, state.inner(), None)
|
||
.await
|
||
.map_err(|e| e.to_string())?;
|
||
Ok(())
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn list_distros() -> Result<Vec<String>, String> {
|
||
list_wsl_distros().map_err(|e| e.to_string())
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn get_roots(state: tauri::State<'_, SharedState>) -> Result<ResolvedRoots, String> {
|
||
let s = state.settings.read().clone();
|
||
Ok(resolve_roots(s.wsl_distro_override.as_deref(), s.include_native))
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn force_rescan(
|
||
state: tauri::State<'_, SharedState>,
|
||
app: tauri::AppHandle,
|
||
) -> Result<(), String> {
|
||
// Wipe caches so every file is reparsed from offset 0.
|
||
state.files.write().clear();
|
||
state.seen_ids.write().clear();
|
||
refresh_and_emit(&app, state.inner(), None)
|
||
.await
|
||
.map_err(|e| e.to_string())
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn quit_app(app: tauri::AppHandle) -> Result<(), String> {
|
||
app.exit(0);
|
||
Ok(())
|
||
}
|
||
|
||
#[derive(serde::Serialize)]
|
||
pub struct TierInfo {
|
||
/// The classified tier — `Pro`, `Max5x`, `Max20x`, etc.
|
||
pub tier: PlanTier,
|
||
/// Human-readable label for the UI ("Max 5×" / "Pro" / "Unknown").
|
||
pub label: String,
|
||
/// The caps that this tier maps to. The user's current Settings may
|
||
/// differ if they tuned manually.
|
||
pub recommended_caps: Caps,
|
||
/// Diagnostic: the candidate `.claude/` directories that were probed.
|
||
/// Useful for users on novel setups (multi-distro, custom $HOME, etc.).
|
||
pub searched: Vec<String>,
|
||
}
|
||
|
||
#[tauri::command]
|
||
pub async fn detect_plan_tier(
|
||
state: tauri::State<'_, SharedState>,
|
||
) -> Result<TierInfo, String> {
|
||
// Resolve roots fresh — `state.roots` may be empty if this command is
|
||
// invoked before the cold-start refresh finishes (which IS what happens
|
||
// when the user opens Settings shortly after launch).
|
||
let s = state.settings.read().clone();
|
||
let resolved =
|
||
resolve_roots(s.wsl_distro_override.as_deref(), s.include_native);
|
||
|
||
let mut candidates: Vec<PathBuf> = resolved
|
||
.roots
|
||
.iter()
|
||
.filter_map(|r| r.parent().map(PathBuf::from))
|
||
.collect();
|
||
if let Some(home) = dirs::home_dir() {
|
||
candidates.push(home.join(".claude"));
|
||
}
|
||
// Dedupe (canonicalize is unreliable on UNC paths, so compare raw).
|
||
candidates.sort();
|
||
candidates.dedup();
|
||
|
||
let (tier, diagnostics) = detect_in_with_diagnostics(&candidates);
|
||
let label = tier.label();
|
||
let recommended_caps = tier.caps();
|
||
Ok(TierInfo { tier, label, recommended_caps, searched: diagnostics })
|
||
}
|