Skip to main content

sc/cli/commands/
memory.rs

1//! Memory command implementations (project-level persistent storage).
2
3use 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/// Output for memory save.
11#[derive(Serialize)]
12struct MemorySaveOutput {
13    key: String,
14    category: String,
15    project_path: String,
16}
17
18/// Output for memory get.
19#[derive(Serialize)]
20struct MemoryGetOutput {
21    key: String,
22    value: String,
23    category: String,
24}
25
26/// Output for memory list.
27#[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
40/// Execute memory commands.
41pub 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    // Generate ID
79    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            // Truncate long values
177            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}