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