Skip to main content

roboticus_cli/cli/
memory.rs

1use super::*;
2
3pub async fn cmd_memory(
4    url: &str,
5    tier: &str,
6    session_id: Option<&str>,
7    query: Option<&str>,
8    limit: Option<i64>,
9    json: bool,
10) -> Result<(), Box<dyn std::error::Error>> {
11    let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
12    let (OK, ACTION, WARN, DETAIL, ERR) = icons();
13    let c = RoboticusClient::new(url)?;
14    match tier {
15        "working" => {
16            let sid = session_id.ok_or("--session required for working memory. Use 'roboticus sessions list' to find session IDs.")?;
17            let data = c
18                .get(&format!("/api/memory/working/{sid}"))
19                .await
20                .map_err(|e| {
21                    RoboticusClient::check_connectivity_hint(&*e);
22                    e
23                })?;
24            if json {
25                println!("{}", serde_json::to_string_pretty(&data)?);
26                return Ok(());
27            }
28            heading("Working Memory");
29            let entries = data["entries"].as_array();
30            match entries {
31                Some(arr) if !arr.is_empty() => {
32                    let widths = [12, 14, 36, 10];
33                    table_header(&["ID", "Type", "Content", "Importance"], &widths);
34                    for e in arr {
35                        table_row(
36                            &[
37                                format!(
38                                    "{MONO}{}{RESET}",
39                                    truncate_id(e["id"].as_str().unwrap_or(""), 9)
40                                ),
41                                e["entry_type"].as_str().unwrap_or("").to_string(),
42                                truncate_id(e["content"].as_str().unwrap_or(""), 33),
43                                e["importance"].to_string(),
44                            ],
45                            &widths,
46                        );
47                    }
48                    eprintln!();
49                    eprintln!("    {DIM}{} entries{RESET}", arr.len());
50                }
51                _ => empty_state("No working memory entries"),
52            }
53        }
54        "episodic" => {
55            let lim = limit.unwrap_or(20);
56            let data = c
57                .get(&format!("/api/memory/episodic?limit={lim}"))
58                .await
59                .map_err(|e| {
60                    RoboticusClient::check_connectivity_hint(&*e);
61                    e
62                })?;
63            if json {
64                println!("{}", serde_json::to_string_pretty(&data)?);
65                return Ok(());
66            }
67            heading("Episodic Memory");
68            let entries = data["entries"].as_array();
69            match entries {
70                Some(arr) if !arr.is_empty() => {
71                    let widths = [12, 16, 36, 10];
72                    table_header(&["ID", "Classification", "Content", "Importance"], &widths);
73                    for e in arr {
74                        table_row(
75                            &[
76                                format!(
77                                    "{MONO}{}{RESET}",
78                                    truncate_id(e["id"].as_str().unwrap_or(""), 9)
79                                ),
80                                e["classification"].as_str().unwrap_or("").to_string(),
81                                truncate_id(e["content"].as_str().unwrap_or(""), 33),
82                                e["importance"].to_string(),
83                            ],
84                            &widths,
85                        );
86                    }
87                    eprintln!();
88                    eprintln!("    {DIM}{} entries (limit: {lim}){RESET}", arr.len());
89                }
90                _ => empty_state("No episodic memory entries"),
91            }
92        }
93        "semantic" => {
94            let category = session_id.unwrap_or("general");
95            let data = c
96                .get(&format!("/api/memory/semantic/{category}"))
97                .await
98                .map_err(|e| {
99                    RoboticusClient::check_connectivity_hint(&*e);
100                    e
101                })?;
102            if json {
103                println!("{}", serde_json::to_string_pretty(&data)?);
104                return Ok(());
105            }
106            heading(&format!("Semantic Memory [{category}]"));
107            let entries = data["entries"].as_array();
108            match entries {
109                Some(arr) if !arr.is_empty() => {
110                    let widths = [20, 34, 12];
111                    table_header(&["Key", "Value", "Confidence"], &widths);
112                    for e in arr {
113                        table_row(
114                            &[
115                                format!("{ACCENT}{}{RESET}", e["key"].as_str().unwrap_or("")),
116                                truncate_id(e["value"].as_str().unwrap_or(""), 31),
117                                format!("{:.2}", e["confidence"].as_f64().unwrap_or(0.0)),
118                            ],
119                            &widths,
120                        );
121                    }
122                    eprintln!();
123                    eprintln!("    {DIM}{} entries{RESET}", arr.len());
124                }
125                _ => empty_state("No semantic memory entries in this category"),
126            }
127        }
128        "search" => {
129            let q = query.ok_or("--query/-q required for memory search")?;
130            let data = c
131                .get(&format!("/api/memory/search?q={}", urlencoding(q)))
132                .await
133                .map_err(|e| {
134                    RoboticusClient::check_connectivity_hint(&*e);
135                    e
136                })?;
137            if json {
138                println!("{}", serde_json::to_string_pretty(&data)?);
139                return Ok(());
140            }
141            heading(&format!("Memory Search: \"{q}\""));
142            let results = data["results"].as_array();
143            match results {
144                Some(arr) if !arr.is_empty() => {
145                    for (i, r) in arr.iter().enumerate() {
146                        let fallback = r.to_string();
147                        let text = r.as_str().unwrap_or(&fallback);
148                        eprintln!("    {DIM}{:>3}.{RESET} {text}", i + 1);
149                    }
150                    eprintln!();
151                    eprintln!("    {DIM}{} results{RESET}", arr.len());
152                }
153                _ => empty_state("No results found"),
154            }
155        }
156        _ => {
157            return Err(format!(
158                "unknown memory tier: {tier}. Use: working, episodic, semantic, search"
159            )
160            .into());
161        }
162    }
163    eprintln!();
164    Ok(())
165}