1use std::sync::Arc;
10
11use rpi_tui::{ArminComponent, Container, EarendilAnnouncementComponent, Spacer};
12
13use crate::config;
14
15pub fn add_armin(chat: &Arc<Container>) {
18 chat.add_child(Arc::new(ArminComponent::new()));
19 chat.add_child(Arc::new(Spacer::new(1)));
20}
21
22pub fn add_earendil(chat: &Arc<Container>) {
26 chat.add_child(Arc::new(EarendilAnnouncementComponent::new()));
27 chat.add_child(Arc::new(Spacer::new(1)));
28 let _ = mark_earendil_seen();
29}
30
31pub fn earendil_seen_path() -> Option<std::path::PathBuf> {
36 config::agent_dir().ok().map(|d| d.join(".earendil_seen"))
37}
38
39pub fn earendil_seen() -> bool {
41 earendil_seen_path().map(|p| p.exists()).unwrap_or(false)
42}
43
44fn mark_earendil_seen() -> std::io::Result<()> {
47 let path = earendil_seen_path().ok_or_else(|| {
48 std::io::Error::new(std::io::ErrorKind::NotFound, "no home dir for .rpi/agent")
49 })?;
50 if let Some(parent) = path.parent() {
51 std::fs::create_dir_all(parent)?;
52 }
53 std::fs::write(&path, b"1")
54}
55
56pub fn setup_done_path() -> Option<std::path::PathBuf> {
59 config::agent_dir().ok().map(|d| d.join(".setup_done"))
60}
61
62pub fn setup_done() -> bool {
64 setup_done_path().map(|p| p.exists()).unwrap_or(false)
65}
66
67pub fn mark_setup_done() -> std::io::Result<()> {
69 let path = setup_done_path().ok_or_else(|| {
70 std::io::Error::new(std::io::ErrorKind::NotFound, "no home dir for .rpi/agent")
71 })?;
72 if let Some(parent) = path.parent() {
73 std::fs::create_dir_all(parent)?;
74 }
75 std::fs::write(&path, b"1")
76}
77
78pub fn maybe_first_time_setup(chat: &Arc<Container>) -> bool {
84 use rpi_tui::Component;
85 use rpi_tui::{DynamicBorder, Spacer, Text};
86 if setup_done() {
87 return false;
88 }
89 let accent = rpi_tui::theme().colors.accent;
90 let muted = rpi_tui::theme().colors.muted;
91 let border = DynamicBorder::with_color(accent);
92 let mut lines: Vec<String> = Vec::new();
94 lines.extend(border.render(80));
95 lines.push(format!(
96 " {} Welcome to rpi!",
97 accent.fg(&bold("Welcome to rpi!"))
98 ));
99 lines.push(format!(
100 " {} Pick a theme with /theme (dark/light/monochrome).",
101 muted.fg("Pick a theme with /theme (dark/light/monochrome).")
102 ));
103 lines.push(format!(
104 " {} Type /help for commands.",
105 muted.fg("Type /help for commands.")
106 ));
107 lines.extend(border.render(80));
108 for line in lines {
109 chat.add_child(Arc::new(Text::new(line, 1, 0)));
110 }
111 chat.add_child(Arc::new(Spacer::new(1)));
112 add_earendil(chat);
113 let _ = mark_setup_done();
114 true
115}
116
117fn bold(s: &str) -> String {
118 format!("\x1b[1m{s}\x1b[22m")
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_add_armin_pushes_component() {
127 let chat = Arc::new(Container::new());
128 let before = chat.child_count();
129 add_armin(&chat);
130 assert_eq!(chat.child_count(), before + 2); }
132
133 #[test]
134 fn test_add_earendil_pushes_component() {
135 let chat = Arc::new(Container::new());
136 let before = chat.child_count();
137 add_earendil(&chat);
138 assert_eq!(chat.child_count(), before + 2);
139 }
140
141 #[test]
142 fn test_sentinel_paths_under_agent_dir() {
143 let _guard = crate::config::test_support::env_lock().lock().unwrap();
149 let prev = std::env::var_os(crate::config::CONFIG_DIR_ENV);
150 let tmp = tempfile::TempDir::new().unwrap();
151 std::env::set_var(crate::config::CONFIG_DIR_ENV, tmp.path());
152 let agent = crate::config::agent_dir().unwrap();
153 assert_eq!(agent.as_path(), tmp.path());
154 if let Some(p) = earendil_seen_path() {
155 assert!(p.ends_with(".earendil_seen"));
156 assert_eq!(p.parent().unwrap(), agent);
157 }
158 if let Some(p) = setup_done_path() {
159 assert!(p.ends_with(".setup_done"));
160 assert_eq!(p.parent().unwrap(), agent);
161 }
162 match prev {
163 Some(v) => std::env::set_var(crate::config::CONFIG_DIR_ENV, v),
164 None => std::env::remove_var(crate::config::CONFIG_DIR_ENV),
165 }
166 }
167}