xbp 10.30.1

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct CursorInventory {
    #[serde(default)]
    pub supported: bool,
    #[serde(default)]
    pub path: String,
    #[serde(default)]
    pub exists: bool,
    #[serde(default)]
    pub user_dir_exists: bool,
    #[serde(default)]
    pub global_storage_exists: bool,
    #[serde(default)]
    pub workspace_storage_entries: usize,
    #[serde(default)]
    pub extensions_entries: usize,
    #[serde(default)]
    pub logs_exists: bool,
    #[serde(default)]
    pub note: Option<String>,
}

pub fn collect_cursor_inventory(explicit_root: Option<&Path>) -> CursorInventory {
    let root = explicit_root
        .map(Path::to_path_buf)
        .or_else(default_cursor_root);

    if !cfg!(windows) {
        return CursorInventory {
            supported: false,
            path: root
                .as_ref()
                .map(|path| path.display().to_string())
                .unwrap_or_default(),
            exists: root.as_ref().map(|path| path.exists()).unwrap_or(false),
            user_dir_exists: false,
            global_storage_exists: false,
            workspace_storage_entries: 0,
            extensions_entries: 0,
            logs_exists: false,
            note: Some("Cursor scanning is only implemented on Windows right now.".to_string()),
        };
    }

    let Some(root) = root else {
        return CursorInventory {
            supported: true,
            path: String::new(),
            exists: false,
            user_dir_exists: false,
            global_storage_exists: false,
            workspace_storage_entries: 0,
            extensions_entries: 0,
            logs_exists: false,
            note: Some("Unable to resolve %APPDATA%\\Cursor.".to_string()),
        };
    };

    let user_dir = root.join("User");
    let workspace_storage_dir = user_dir.join("workspaceStorage");
    let global_storage_dir = user_dir.join("globalStorage");
    let extensions_dir = root.join("extensions");
    let logs_dir = root.join("logs");

    CursorInventory {
        supported: true,
        path: root.display().to_string(),
        exists: root.exists(),
        user_dir_exists: user_dir.exists(),
        global_storage_exists: global_storage_dir.exists(),
        workspace_storage_entries: count_dir_entries(&workspace_storage_dir),
        extensions_entries: count_dir_entries(&extensions_dir),
        logs_exists: logs_dir.exists(),
        note: None,
    }
}

fn default_cursor_root() -> Option<PathBuf> {
    dirs::data_dir().map(|path| path.join("Cursor"))
}

fn count_dir_entries(path: &Path) -> usize {
    fs::read_dir(path)
        .ok()
        .into_iter()
        .flat_map(|entries| entries.flatten())
        .count()
}