1use crate::cli::MemoryCommands;
4use crate::config::{current_project_path, default_actor, resolve_db_path};
5use crate::error::{Error, Result};
6use crate::storage::SqliteStorage;
7use serde::Serialize;
8use std::path::PathBuf;
9
10#[derive(Serialize)]
12struct MemorySaveOutput {
13 key: String,
14 category: String,
15 project_path: String,
16}
17
18#[derive(Serialize)]
20struct MemoryGetOutput {
21 key: String,
22 value: String,
23 category: String,
24}
25
26#[derive(Serialize)]
28struct MemoryListOutput {
29 items: Vec<MemoryItem>,
30 count: usize,
31}
32
33#[derive(Serialize)]
34struct MemoryItem {
35 key: String,
36 value: String,
37 category: String,
38}
39
40pub fn execute(
42 command: &MemoryCommands,
43 db_path: Option<&PathBuf>,
44 actor: Option<&str>,
45 json: bool,
46) -> Result<()> {
47 match command {
48 MemoryCommands::Save {
49 key,
50 value,
51 category,
52 } => save(key, value, category, db_path, actor, json),
53 MemoryCommands::Get { key } => get(key, db_path, json),
54 MemoryCommands::List { category } => list(category.as_deref(), db_path, json),
55 MemoryCommands::Delete { key } => delete(key, db_path, actor, json),
56 }
57}
58
59fn save(
60 key: &str,
61 value: &str,
62 category: &str,
63 db_path: Option<&PathBuf>,
64 actor: Option<&str>,
65 json: bool,
66) -> Result<()> {
67 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
68 .ok_or(Error::NotInitialized)?;
69
70 if !db_path.exists() {
71 return Err(Error::NotInitialized);
72 }
73
74 let mut storage = SqliteStorage::open(&db_path)?;
75 let actor = actor.map(ToString::to_string).unwrap_or_else(default_actor);
76 let project_path = current_project_path()
77 .map(|p| p.to_string_lossy().to_string())
78 .ok_or_else(|| Error::Other("Could not determine project path".to_string()))?;
79
80 let id = format!("mem_{}", &uuid::Uuid::new_v4().to_string()[..12]);
82
83 storage.save_memory(&id, &project_path, key, value, category, &actor)?;
84
85 if crate::is_silent() {
86 println!("{key}");
87 return Ok(());
88 }
89
90 if json {
91 let output = MemorySaveOutput {
92 key: key.to_string(),
93 category: category.to_string(),
94 project_path,
95 };
96 println!("{}", serde_json::to_string(&output)?);
97 } else {
98 println!("Saved memory: {key} [{category}]");
99 }
100
101 Ok(())
102}
103
104fn get(key: &str, db_path: Option<&PathBuf>, json: bool) -> Result<()> {
105 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
106 .ok_or(Error::NotInitialized)?;
107
108 if !db_path.exists() {
109 return Err(Error::NotInitialized);
110 }
111
112 let storage = SqliteStorage::open(&db_path)?;
113 let project_path = current_project_path()
114 .map(|p| p.to_string_lossy().to_string())
115 .ok_or_else(|| Error::Other("Could not determine project path".to_string()))?;
116
117 let memory = storage
118 .get_memory(&project_path, key)?
119 .ok_or_else(|| Error::Other(format!("Memory not found: {key}")))?;
120
121 if json {
122 let output = MemoryGetOutput {
123 key: memory.key,
124 value: memory.value,
125 category: memory.category,
126 };
127 println!("{}", serde_json::to_string(&output)?);
128 } else {
129 println!("{}: {}", memory.key, memory.value);
130 }
131
132 Ok(())
133}
134
135fn list(category: Option<&str>, db_path: Option<&PathBuf>, json: bool) -> Result<()> {
136 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
137 .ok_or(Error::NotInitialized)?;
138
139 if !db_path.exists() {
140 return Err(Error::NotInitialized);
141 }
142
143 let storage = SqliteStorage::open(&db_path)?;
144 let project_path = current_project_path()
145 .map(|p| p.to_string_lossy().to_string())
146 .ok_or_else(|| Error::Other("Could not determine project path".to_string()))?;
147
148 let memories = storage.list_memory(&project_path, category)?;
149
150 if crate::is_csv() {
151 println!("key,category,value");
152 for m in &memories {
153 println!("{},{},{}", m.key, m.category, crate::csv_escape(&m.value));
154 }
155 } else if json {
156 let items: Vec<MemoryItem> = memories
157 .iter()
158 .map(|m| MemoryItem {
159 key: m.key.clone(),
160 value: m.value.clone(),
161 category: m.category.clone(),
162 })
163 .collect();
164 let output = MemoryListOutput {
165 count: items.len(),
166 items,
167 };
168 println!("{}", serde_json::to_string(&output)?);
169 } else if memories.is_empty() {
170 println!("No memory items found.");
171 } else {
172 println!("Memory items ({} found):", memories.len());
173 println!();
174 for mem in &memories {
175 let cat_icon = match mem.category.as_str() {
176 "command" => "$",
177 "config" => "⚙",
178 "note" => "📝",
179 _ => "•",
180 };
181 println!("{} {} [{}]", cat_icon, mem.key, mem.category);
182 let display_value = if mem.value.len() > 80 {
184 format!("{}...", &mem.value[..80])
185 } else {
186 mem.value.clone()
187 };
188 println!(" {display_value}");
189 println!();
190 }
191 }
192
193 Ok(())
194}
195
196fn delete(key: &str, db_path: Option<&PathBuf>, actor: Option<&str>, json: bool) -> Result<()> {
197 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
198 .ok_or(Error::NotInitialized)?;
199
200 if !db_path.exists() {
201 return Err(Error::NotInitialized);
202 }
203
204 let mut storage = SqliteStorage::open(&db_path)?;
205 let actor = actor.map(ToString::to_string).unwrap_or_else(default_actor);
206 let project_path = current_project_path()
207 .map(|p| p.to_string_lossy().to_string())
208 .ok_or_else(|| Error::Other("Could not determine project path".to_string()))?;
209
210 storage.delete_memory(&project_path, key, &actor)?;
211
212 if json {
213 let output = serde_json::json!({
214 "key": key,
215 "deleted": true
216 });
217 println!("{output}");
218 } else {
219 println!("Deleted memory: {key}");
220 }
221
222 Ok(())
223}