1use std::fs::{self, File};
2use std::io::{self, Write};
3use std::path::Path;
4use serde_json::{json, Value};
5use uuid::Uuid;
6use colored::*;
7
8use crate::schema::{make_index_metadata, make_thread_metadata};
9use crate::frs::avatars::{load_avatars, save_avatars, get_random_emoji_for_name};
10
11fn init_fur_dir(fur_dir: &Path) -> io::Result<()> {
12 fs::create_dir_all(fur_dir.join("threads"))?;
13 fs::create_dir_all(fur_dir.join("messages"))?;
14
15 let mut f = File::create(fur_dir.join("index.json"))?;
16 let initial_index = make_index_metadata();
17 f.write_all(serde_json::to_string_pretty(&initial_index)?.as_bytes())?;
18 Ok(())
19}
20
21pub fn onboarding_interactive() -> (String, String) {
22 println!("\n{}", "== Main Avatar ==".bright_magenta().bold());
24 println!(
25 "{}",
26 "This is YOU (or your team). The default voice in this thread.\n\
27 Whenever you jot without specifying an avatar, it will be attributed here."
28 .bright_cyan()
29 );
30 print!("{}", "Main avatar name [me]: ");
31 io::stdout().flush().unwrap();
32 let mut main_in = String::new();
33 io::stdin().read_line(&mut main_in).unwrap();
34 let mut main_name = main_in.trim().to_string();
35 if main_name.is_empty() {
36 main_name = "me".to_string();
37 } else if main_name == "main" {
38 println!(
39 "{}",
40 "[WARN] 'main' is reserved as a pointer. Using 'me' instead."
41 .yellow()
42 .bold()
43 );
44 main_name = "me".to_string();
45 }
46 println!(
47 "{}",
48 format!("[OK] Main avatar set: {}", main_name)
49 .bright_green()
50 .bold()
51 );
52
53 println!("\n{}", "== Secondary Avatar ==".bright_magenta().bold());
55 println!(
56 "{}",
57 "You can't have a conversation with one person.\n\
58 Let's log at least one other avatar. This could be an AI, your boss, your therapist, or karen_from_hr."
59 .bright_cyan()
60 );
61 print!("{}", "Another avatar [ai]: ");
62 io::stdout().flush().unwrap();
63 let mut other_in = String::new();
64 io::stdin().read_line(&mut other_in).unwrap();
65 let other_name = {
66 let trimmed = other_in.trim();
67 if trimmed.is_empty() {
68 "ai".to_string()
69 } else {
70 trimmed.to_string()
71 }
72 };
73 println!(
74 "{}",
75 format!("[OK] Other avatar set: {}", other_name)
76 .bright_green()
77 .bold()
78 );
79
80 (main_name, other_name)
81}
82
83pub fn onboarding_auto(main: &str, other: &str) {
85 let mut avatars = load_avatars();
86 avatars["main"] = json!(main);
87 avatars[main] = json!("🦊");
88 avatars[other] = json!(get_random_emoji_for_name(other));
89 save_avatars(&avatars);
90}
91
92
93
94fn run_new_internal(
95 name: String,
96 auto: bool,
97 main_avatar: Option<String>,
98 other_avatar: Option<String>,
99) {
100 let fur_dir = Path::new(".fur");
101
102 if !fur_dir.exists() {
103 init_fur_dir(fur_dir).expect("Failed to create .fur structure");
104 println!("{}", "[INIT] .fur/ directory created".bright_green().bold());
105
106 if auto {
107 onboarding_auto(
108 main_avatar.as_deref().unwrap_or("me"),
109 other_avatar.as_deref().unwrap_or("ai"),
110 );
111 } else {
112 let (main, other) = onboarding_interactive();
113 onboarding_auto(&main, &other);
114 }
115
116 println!(
117 "\n{}",
118 "Ready! Use:\n fur jot <your message>\n fur jot <other avatar> <their message>"
119 .bright_cyan()
120 );
121 }
122
123 let thread_id = Uuid::new_v4().to_string();
124 let thread_meta = make_thread_metadata(&name, &thread_id);
125
126 let thread_path = fur_dir.join("threads").join(format!("{}.json", thread_id));
128 fs::write(&thread_path, serde_json::to_string_pretty(&thread_meta).unwrap())
129 .expect("Could not write thread file");
130
131 let index_path = fur_dir.join("index.json");
133 let mut index: Value = serde_json::from_str(&fs::read_to_string(&index_path).unwrap()).unwrap();
134
135 if let Some(arr) = index["threads"].as_array_mut() {
136 arr.push(json!(thread_id.clone()));
137 } else {
138 index["threads"] = json!([thread_id.clone()]);
139 }
140 index["active_thread"] = json!(thread_id.clone());
141 index["current_message"] = Value::Null;
142
143 fs::write(&index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
144
145 println!(
146 "{}",
147 format!("[NEW] Thread created: {} — \"{}\"", &thread_id[..8], name)
148 .bright_green()
149 .bold()
150 );
151}
152
153pub fn run_new(name: String) {
155 run_new_internal(name, false, None, None);
156}
157
158