1use crate::cli::MemoryCommands;
4use crate::config::{default_actor, resolve_db_path, resolve_project_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 = resolve_project_path(&storage, None)?;
77
78 let id = format!("mem_{}", &uuid::Uuid::new_v4().to_string()[..12]);
80
81 storage.save_memory(&id, &project_path, key, value, category, &actor)?;
82
83 if crate::is_silent() {
84 println!("{key}");
85 return Ok(());
86 }
87
88 if json {
89 let output = MemorySaveOutput {
90 key: key.to_string(),
91 category: category.to_string(),
92 project_path,
93 };
94 println!("{}", serde_json::to_string(&output)?);
95 } else {
96 println!("Saved memory: {key} [{category}]");
97 }
98
99 Ok(())
100}
101
102fn get(key: &str, db_path: Option<&PathBuf>, json: bool) -> Result<()> {
103 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
104 .ok_or(Error::NotInitialized)?;
105
106 if !db_path.exists() {
107 return Err(Error::NotInitialized);
108 }
109
110 let storage = SqliteStorage::open(&db_path)?;
111 let project_path = resolve_project_path(&storage, None)?;
112
113 let memory = storage
114 .get_memory(&project_path, key)?
115 .ok_or_else(|| Error::Other(format!("Memory not found: {key}")))?;
116
117 if json {
118 let output = MemoryGetOutput {
119 key: memory.key,
120 value: memory.value,
121 category: memory.category,
122 };
123 println!("{}", serde_json::to_string(&output)?);
124 } else {
125 println!("{}: {}", memory.key, memory.value);
126 }
127
128 Ok(())
129}
130
131fn list(category: Option<&str>, db_path: Option<&PathBuf>, json: bool) -> Result<()> {
132 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
133 .ok_or(Error::NotInitialized)?;
134
135 if !db_path.exists() {
136 return Err(Error::NotInitialized);
137 }
138
139 let storage = SqliteStorage::open(&db_path)?;
140 let project_path = resolve_project_path(&storage, None)?;
141
142 let memories = storage.list_memory(&project_path, category)?;
143
144 if crate::is_csv() {
145 println!("key,category,value");
146 for m in &memories {
147 println!("{},{},{}", m.key, m.category, crate::csv_escape(&m.value));
148 }
149 } else if json {
150 let items: Vec<MemoryItem> = memories
151 .iter()
152 .map(|m| MemoryItem {
153 key: m.key.clone(),
154 value: m.value.clone(),
155 category: m.category.clone(),
156 })
157 .collect();
158 let output = MemoryListOutput {
159 count: items.len(),
160 items,
161 };
162 println!("{}", serde_json::to_string(&output)?);
163 } else if memories.is_empty() {
164 println!("No memory items found.");
165 } else {
166 println!("Memory items ({} found):", memories.len());
167 println!();
168 for mem in &memories {
169 let cat_icon = match mem.category.as_str() {
170 "command" => "$",
171 "config" => "⚙",
172 "note" => "📝",
173 _ => "•",
174 };
175 println!("{} {} [{}]", cat_icon, mem.key, mem.category);
176 let display_value = if mem.value.len() > 80 {
178 format!("{}...", &mem.value[..80])
179 } else {
180 mem.value.clone()
181 };
182 println!(" {display_value}");
183 println!();
184 }
185 }
186
187 Ok(())
188}
189
190fn delete(key: &str, db_path: Option<&PathBuf>, actor: Option<&str>, json: bool) -> Result<()> {
191 let db_path = resolve_db_path(db_path.map(|p| p.as_path()))
192 .ok_or(Error::NotInitialized)?;
193
194 if !db_path.exists() {
195 return Err(Error::NotInitialized);
196 }
197
198 let mut storage = SqliteStorage::open(&db_path)?;
199 let actor = actor.map(ToString::to_string).unwrap_or_else(default_actor);
200 let project_path = resolve_project_path(&storage, None)?;
201
202 storage.delete_memory(&project_path, key, &actor)?;
203
204 if json {
205 let output = serde_json::json!({
206 "key": key,
207 "deleted": true
208 });
209 println!("{output}");
210 } else {
211 println!("Deleted memory: {key}");
212 }
213
214 Ok(())
215}