Skip to main content

fur_cli/commands/status/
core.rs

1use serde_json::Value;
2use std::collections::HashMap;
3use std::fs;
4use std::path::Path;
5
6pub fn load_index_and_conversation(fur_dir: &Path) -> (Value, Value, String) {
7    let index_path = fur_dir.join("index.json");
8    let index: Value = read_json(&index_path);
9
10    let conversation_id = index["active_thread"].as_str().unwrap_or("");
11    let current = index["current_message"].as_str().unwrap_or("").to_string();
12
13    let convo_path = fur_dir
14        .join("threads")
15        .join(format!("{}.json", conversation_id));
16    let conversation: Value = read_json(&convo_path);
17
18    (index, conversation, current)
19}
20
21/// Preload all reachable messages
22pub fn load_conversation_messages(fur_dir: &Path, conversation: &Value) -> HashMap<String, Value> {
23    let mut id_to_message = HashMap::new();
24
25    let mut stack: Vec<String> = conversation["messages"]
26        .as_array()
27        .unwrap_or(&vec![])
28        .iter()
29        .filter_map(|id| id.as_str().map(|s| s.to_string()))
30        .collect();
31
32    let messages_dir = fur_dir.join("messages");
33
34    while let Some(mid) = stack.pop() {
35        let msg_path = messages_dir.join(format!("{}.json", mid));
36
37        if let Some(content) = crate::security::io::read_text_file(&msg_path) {
38            if let Ok(mut obj) = serde_json::from_str::<Value>(&content) {
39                // ─────────────────────────────
40                // Lazy schema upgrade
41                // ─────────────────────────────
42                if crate::commands::jot::upgrade_message_schema(&mut obj) {
43                    // write upgraded message back to disk
44                    if let Ok(serialized) = serde_json::to_string_pretty(&obj) {
45                        let _ = fs::write(&msg_path, serialized);
46                    }
47                }
48
49                // ─────────────────────────────
50                // enqueue children
51                // ─────────────────────────────
52                if let Some(children) = obj["children"].as_array() {
53                    for c in children {
54                        if let Some(cid) = c.as_str() {
55                            stack.push(cid.to_string());
56                        }
57                    }
58                }
59
60                // ─────────────────────────────
61                // enqueue branch blocks
62                // ─────────────────────────────
63                if let Some(blocks) = obj["branches"].as_array() {
64                    for block in blocks {
65                        if let Some(arr) = block.as_array() {
66                            for c in arr {
67                                if let Some(cid) = c.as_str() {
68                                    stack.push(cid.to_string());
69                                }
70                            }
71                        }
72                    }
73                }
74
75                id_to_message.insert(mid.clone(), obj);
76            }
77        }
78    }
79
80    id_to_message
81}
82
83/// If current_message missing, use first in conversation.messages
84pub fn first_message_fallback(conversation: &Value) -> String {
85    conversation["messages"]
86        .as_array()
87        .and_then(|arr| arr.get(0))
88        .and_then(|v| v.as_str())
89        .unwrap_or("")
90        .to_string()
91}
92
93fn read_json(path: &Path) -> Value {
94    use crate::security::io::read_text_file;
95
96    serde_json::from_str(&read_text_file(path).expect("❌ Project locked. Run `fur unlock`."))
97        .unwrap()
98}