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()
}