ripl-tui 0.3.3

ripl — a living, breathing TUI framework for AI chat in the shell.
Documentation
use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;

use crate::providers::Message;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionCache {
    pub conversation: Vec<Message>,
    pub provider: Option<String>,
    pub model: Option<String>,
}

pub fn load() -> Option<SessionCache> {
    let path = session_path();
    let raw = fs::read_to_string(path).ok()?;
    serde_json::from_str(&raw).ok()
}

pub fn save(cache: &SessionCache) {
    let path = session_path();
    if let Some(dir) = path.parent() {
        let _ = fs::create_dir_all(dir);
    }
    // Filter system messages — scaffold context is reloaded fresh on each launch.
    let filtered = SessionCache {
        conversation: cache
            .conversation
            .iter()
            .filter(|m| m.role != crate::providers::Role::System)
            .cloned()
            .collect(),
        provider: cache.provider.clone(),
        model: cache.model.clone(),
    };
    if let Ok(raw) = serde_json::to_string_pretty(&filtered) {
        let _ = fs::write(path, raw);
    }
}

fn session_path() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
    let dir = PathBuf::from(home).join(".ripl").join("sessions");
    let hash = project_hash();
    dir.join(format!("{}.json", hash))
}

fn project_hash() -> u64 {
    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let mut hasher = DefaultHasher::new();
    cwd.to_string_lossy().hash(&mut hasher);
    hasher.finish()
}