1use crate::commands::timeline::TimelineArgs;
2use crate::commands::tree::TreeArgs;
3use crate::commands::{timeline, tree};
4use crate::frs::{parser, persist_frs};
5use colored::*;
6use std::fs;
7
8pub fn run_frs(path: &str) {
14 let raw = fs::read_to_string(path)
15 .unwrap_or_else(|_| panic!("❌ Could not read .frs file: {}", path));
16
17 let lines: Vec<String> = raw
18 .lines()
19 .map(|l| l.trim().to_string())
20 .filter(|l| !l.is_empty() && !l.starts_with('#'))
21 .collect();
22
23 let conversation = parser::parse_frs(path);
24 let mut stored = false;
25
26 for (lineno, line) in lines.iter().enumerate() {
27 if line == "store" {
29 if !stored {
30 let tid = persist_frs(&conversation);
31 println!("✔️ Thread persisted at line {} → {}", lineno + 1, &tid[..8]);
32 stored = true;
33 } else {
34 eprintln!(
35 "{}",
36 format!(
37 "⚠️ Ignoring extra `store` at line {} — already persisted",
38 lineno + 1
39 )
40 .yellow()
41 .bold()
42 );
43 }
44 continue;
45 }
46
47 if line.starts_with("status") {
49 with_ephemeral(stored, &conversation, |tid_override| {
50 let args = crate::commands::status::StatusArgs {
51 conversation_override: tid_override,
52 };
53 crate::commands::status::run_status(args);
54 });
55 continue;
56 }
57
58 if line.starts_with("timeline") {
60 let parts: Vec<&str> = line.split_whitespace().collect();
61 let mut args = TimelineArgs {
62 verbose: false,
63 contents: false,
64 out: None,
65 conversation_override: None,
66 };
67 for (i, p) in parts.iter().enumerate() {
68 if *p == "--out" {
69 args.out = parts.get(i + 1).map(|s| s.to_string());
70 }
71 if *p == "--contents" {
72 args.contents = true;
73 }
74 }
75
76 with_ephemeral(stored, &conversation, |tid_override| {
77 let mut args = args.clone();
78 args.conversation_override = tid_override;
79 timeline::run_timeline(args);
80 });
81 continue;
82 }
83
84 if line.starts_with("printed") {
86 let parts: Vec<&str> = line.split_whitespace().collect();
87 let mut out: Option<String> = None;
88 let mut verbose = false;
89
90 for (i, p) in parts.iter().enumerate() {
91 if *p == "--out" {
92 out = parts.get(i + 1).map(|s| s.to_string());
93 }
94 if *p == "--verbose" || *p == "-v" {
95 verbose = true;
96 }
97 }
98
99 with_ephemeral(stored, &conversation, |tid_override| {
100 let index_path = std::path::Path::new(".fur").join("index.json");
102 let mut index_json: serde_json::Value =
103 serde_json::from_str(&std::fs::read_to_string(&index_path).unwrap()).unwrap();
104
105 let original_active = index_json["active_thread"].as_str().map(|s| s.to_string());
106
107 if let Some(tid) = &tid_override {
108 index_json["active_thread"] = tid.clone().into();
109 std::fs::write(
110 &index_path,
111 serde_json::to_string_pretty(&index_json).unwrap(),
112 )
113 .unwrap();
114 }
115
116 crate::commands::printed::run_printed(out.clone(), verbose);
118
119 if let Some(orig) = original_active {
121 index_json["active_thread"] = orig.into();
122 std::fs::write(
123 &index_path,
124 serde_json::to_string_pretty(&index_json).unwrap(),
125 )
126 .unwrap();
127 }
128 });
129
130 continue;
131 }
132
133 if line.starts_with("tree") {
135 let args = TreeArgs {
136 conversation_override: None,
137 };
138 with_ephemeral(stored, &conversation, |tid_override| {
139 let mut args = args.clone();
140 args.conversation_override = tid_override;
141 tree::run_tree(args);
142 });
143 continue;
144 }
145
146 }
148
149 if !stored {
150 eprintln!(
151 "{}",
152 "⚠️ Script finished without a `store` — nothing persisted.".yellow()
153 );
154 }
155}
156
157fn with_ephemeral<F>(stored: bool, conversation: &crate::frs::ast::Thread, mut f: F)
159where
160 F: FnMut(Option<String>),
161{
162 if !stored {
163 let tid = crate::frs::persist::persist_ephemeral(conversation);
164 f(Some(tid.clone()));
165 crate::frs::persist::cleanup_ephemeral(&tid);
166 } else {
167 f(None);
168 }
169}