chasm_cli/providers/
cursor.rs1use super::{ChatProvider, ProviderType};
6use crate::models::ChatSession;
7use crate::storage::parse_session_json;
8use anyhow::Result;
9use std::path::PathBuf;
10
11pub struct CursorProvider {
16 storage_path: PathBuf,
18 available: bool,
20}
21
22impl CursorProvider {
23 pub fn discover() -> Option<Self> {
25 let storage_path = Self::find_cursor_storage()?;
26
27 Some(Self {
28 available: storage_path.exists(),
29 storage_path,
30 })
31 }
32
33 fn find_cursor_storage() -> Option<PathBuf> {
35 #[cfg(target_os = "windows")]
36 {
37 let appdata = dirs::data_dir()?;
38 let cursor_path = appdata.join("Cursor").join("User").join("workspaceStorage");
39 if cursor_path.exists() {
40 return Some(cursor_path);
41 }
42 let roaming = std::env::var("APPDATA").ok()?;
44 let roaming_path = PathBuf::from(roaming)
45 .join("Cursor")
46 .join("User")
47 .join("workspaceStorage");
48 if roaming_path.exists() {
49 return Some(roaming_path);
50 }
51 }
52
53 #[cfg(target_os = "macos")]
54 {
55 let home = dirs::home_dir()?;
56 let cursor_path = home
57 .join("Library")
58 .join("Application Support")
59 .join("Cursor")
60 .join("User")
61 .join("workspaceStorage");
62 if cursor_path.exists() {
63 return Some(cursor_path);
64 }
65 }
66
67 #[cfg(target_os = "linux")]
68 {
69 let config = dirs::config_dir()?;
70 let cursor_path = config.join("Cursor").join("User").join("workspaceStorage");
71 if cursor_path.exists() {
72 return Some(cursor_path);
73 }
74 }
75
76 None
77 }
78
79 fn list_workspaces(&self) -> Result<Vec<PathBuf>> {
81 let mut workspaces = Vec::new();
82
83 if self.storage_path.exists() {
84 for entry in std::fs::read_dir(&self.storage_path)? {
85 let entry = entry?;
86 let path = entry.path();
87
88 if path.is_dir() {
89 let chat_path = path.join("chatSessions");
91 if chat_path.exists() {
92 workspaces.push(path);
93 }
94 }
95 }
96 }
97
98 Ok(workspaces)
99 }
100}
101
102impl ChatProvider for CursorProvider {
103 fn provider_type(&self) -> ProviderType {
104 ProviderType::Cursor
105 }
106
107 fn name(&self) -> &str {
108 "Cursor"
109 }
110
111 fn is_available(&self) -> bool {
112 self.available
113 }
114
115 fn sessions_path(&self) -> Option<PathBuf> {
116 Some(self.storage_path.clone())
117 }
118
119 fn list_sessions(&self) -> Result<Vec<ChatSession>> {
120 let mut sessions = Vec::new();
121
122 for workspace in self.list_workspaces()? {
123 let chat_path = workspace.join("chatSessions");
124
125 if chat_path.exists() {
126 for entry in std::fs::read_dir(&chat_path)? {
127 let entry = entry?;
128 let path = entry.path();
129
130 if path.extension().is_some_and(|e| e == "json") {
131 if let Ok(content) = std::fs::read_to_string(&path) {
132 if let Ok(session) = parse_session_json(&content) {
133 sessions.push(session);
134 }
135 }
136 }
137 }
138 }
139 }
140
141 Ok(sessions)
142 }
143
144 fn import_session(&self, session_id: &str) -> Result<ChatSession> {
145 for workspace in self.list_workspaces()? {
147 let session_path = workspace
148 .join("chatSessions")
149 .join(format!("{}.json", session_id));
150
151 if session_path.exists() {
152 let content = std::fs::read_to_string(&session_path)?;
153 let session: ChatSession = serde_json::from_str(&content)?;
154 return Ok(session);
155 }
156 }
157
158 anyhow::bail!("Session not found: {}", session_id)
159 }
160
161 fn export_session(&self, _session: &ChatSession) -> Result<()> {
162 anyhow::bail!("Export to Cursor not yet implemented - use import instead")
165 }
166}