use clap::Parser;
use serde_json::Value;
use std::fs;
use std::path::Path;
#[derive(Parser)]
pub struct SaveArgs {
#[arg(short, long)]
pub out: Option<String>,
}
pub fn run_save(args: SaveArgs) {
let fur_dir = Path::new(".fur");
let index_path = fur_dir.join("index.json");
if !index_path.exists() {
eprintln!("🚨 .fur/ not found. Run `fur new` first.");
return;
}
let index: Value =
serde_json::from_str(&fs::read_to_string(&index_path).expect("❌ Cannot read index.json"))
.unwrap();
let conversation_id = match index["active_thread"].as_str() {
Some(id) => id,
None => {
eprintln!("⚠️ No active conversation.");
return;
}
};
let convo_path = fur_dir
.join("threads")
.join(format!("{}.json", conversation_id));
let conversation: Value = serde_json::from_str(
&fs::read_to_string(&convo_path).expect("❌ Cannot read conversation"),
)
.unwrap();
let title = conversation["title"].as_str().unwrap_or("Untitled");
let safe_title = title.replace(" ", "_");
let output_path = args.out.unwrap_or_else(|| format!("{}.frs", safe_title));
let mut out = String::new();
out.push_str(&format!("new \"{}\"\n", title));
if let Some(tags) = conversation["tags"].as_array() {
if !tags.is_empty() {
let tags_str = tags
.iter()
.filter_map(|t| t.as_str())
.map(|t| format!("\"{}\"", t))
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!("tags = [{}]\n\n", tags_str));
}
}
for msg_id in conversation["messages"].as_array().unwrap_or(&vec![]) {
if let Some(mid) = msg_id.as_str() {
out.push_str(&render_message(mid, 0, fur_dir));
}
}
fs::write(&output_path, out).expect("❌ Could not write .frs file");
println!("💾 Saved conversation \"{}\" to {}", title, output_path);
}
fn render_message(msg_id: &str, indent: usize, fur_dir: &Path) -> String {
let msg_path = fur_dir.join("messages").join(format!("{}.json", msg_id));
let content = match fs::read_to_string(&msg_path) {
Ok(c) => c,
Err(_) => return String::new(),
};
let msg: Value = serde_json::from_str(&content).unwrap();
let mut out = String::new();
let pad = " ".repeat(indent);
let avatar = msg["avatar"].as_str().unwrap_or("anon");
if let Some(text) = msg["text"].as_str() {
out.push_str(&format!("{}jot {} \"{}\"\n", pad, avatar, text));
} else if let Some(file) = msg["markdown"].as_str() {
out.push_str(&format!("{}jot {} --file \"{}\"\n", pad, avatar, file));
} else if let Some(att) = msg["attachment"].as_str() {
out.push_str(&format!("{}jot {} --img \"{}\"\n", pad, avatar, att));
}
if let Some(branches) = msg["branches"].as_array() {
for block in branches {
if let Some(arr) = block.as_array() {
out.push_str(&format!("{}branch {{\n", pad));
for child in arr {
if let Some(cid) = child.as_str() {
out.push_str(&render_message(cid, indent + 1, fur_dir));
}
}
out.push_str(&format!("{}}}\n", pad));
}
}
}
out
}