fur_cli/commands/
fork.rs

1use std::fs;
2use std::path::Path;
3use serde_json::{Value, json};
4use uuid::Uuid;
5use chrono::Utc;
6
7pub fn run_fork_from_active(title: Option<String>) {
8    let index_path = Path::new(".fur").join("index.json");
9    let index_data: Value = serde_json::from_str(&fs::read_to_string(index_path).unwrap()).unwrap();
10    let active_conversation = index_data["active_thread"]
11        .as_str()
12        .expect("No active conversation set");
13
14    run_fork(active_conversation, title);
15}
16
17pub fn run_fork(conversation_id: &str, title: Option<String>) {
18    let fur_dir = Path::new(".fur");
19    let threads_dir = fur_dir.join("threads");
20    let index_path = fur_dir.join("index.json");
21
22    let old_path = threads_dir.join(format!("{}.json", conversation_id));
23    if !old_path.exists() {
24        eprintln!("❌ Thread ID {} does not exist at path {:?}", conversation_id, old_path);
25        return;
26    }
27
28    // Read old conversation
29    let old_data: Value = serde_json::from_str(
30        &fs::read_to_string(&old_path).unwrap()
31    ).unwrap();
32
33    let old_title = old_data["title"].as_str().unwrap_or("Untitled");
34    let new_id = Uuid::new_v4().to_string();
35    let timestamp = Utc::now().to_rfc3339();
36
37    // Pick fork title
38    let fork_title: String;
39    let used_custom_title = title.is_some();
40
41    fork_title = match title {
42        Some(custom) => custom,
43        None => format!("Fork of {}", old_title),
44    };
45
46    let messages = old_data["messages"].clone();
47
48    let new_conversation = json!({
49        "id": new_id,
50        "title": fork_title,
51        "created_at": timestamp,
52        "forked_from": conversation_id,
53        "messages": messages
54    });
55
56    let new_path = threads_dir.join(format!("{}.json", new_id));
57    fs::write(&new_path, serde_json::to_string_pretty(&new_conversation).unwrap()).unwrap();
58
59    // Update index.json
60    let mut index_data: Value = serde_json::from_str(
61        &fs::read_to_string(&index_path).unwrap()
62    ).unwrap();
63
64    index_data["active_thread"] = json!(new_id);
65    index_data["threads"].as_array_mut().unwrap().push(json!(new_id));
66
67    fs::write(index_path, serde_json::to_string_pretty(&index_data).unwrap()).unwrap();
68
69
70    if used_custom_title {
71        println!(
72            "🌱 Created fork \"{}\" from {} -- {} → {}",
73            fork_title, old_title, conversation_id, new_id
74        );
75    } else {
76        println!(
77            "🌱 Forked conversation from {} -- {} → {}",
78            old_title, conversation_id, new_id
79        );
80    }
81}