1pub fn cmd_tee(args: &[String]) {
2 let tee_dir = match crate::core::paths::state_dir() {
3 Ok(d) => d.join("tee"),
4 Err(e) => {
5 eprintln!("Cannot determine state directory: {e}");
6 std::process::exit(1);
7 }
8 };
9
10 let action = args.first().map_or("list", std::string::String::as_str);
11 match action {
12 "list" | "ls" => {
13 if !tee_dir.exists() {
14 println!("No tee logs found (~/.lean-ctx/tee/ does not exist)");
15 return;
16 }
17 let mut entries: Vec<_> = std::fs::read_dir(&tee_dir)
18 .unwrap_or_else(|e| {
19 eprintln!("Error: {e}");
20 std::process::exit(1);
21 })
22 .filter_map(std::result::Result::ok)
23 .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("log"))
24 .collect();
25 entries.sort_by_key(std::fs::DirEntry::file_name);
26
27 if entries.is_empty() {
28 println!("No tee logs found.");
29 return;
30 }
31
32 println!("Tee logs ({}):\n", entries.len());
33 for entry in &entries {
34 let size = entry.metadata().map_or(0, |m| m.len());
35 let name = entry.file_name();
36 let size_str = if size > 1024 {
37 format!("{}K", size / 1024)
38 } else {
39 format!("{size}B")
40 };
41 println!(" {:<60} {}", name.to_string_lossy(), size_str);
42 }
43 println!("\nUse 'lean-ctx tee clear' to delete all logs.");
44 }
45 "clear" | "purge" => {
46 if !tee_dir.exists() {
47 println!("No tee logs to clear.");
48 return;
49 }
50 let mut count = 0u32;
51 if let Ok(entries) = std::fs::read_dir(&tee_dir) {
52 for entry in entries.flatten() {
53 if entry.path().extension().and_then(|x| x.to_str()) == Some("log")
54 && std::fs::remove_file(entry.path()).is_ok()
55 {
56 count += 1;
57 }
58 }
59 }
60 println!("Cleared {count} tee log(s) from {}", tee_dir.display());
61 }
62 "show" => {
63 let Some(filename) = args.get(1) else {
64 eprintln!("Usage: lean-ctx tee show <filename>");
65 std::process::exit(1);
66 };
67 let fname = filename.as_str();
68 let basename = std::path::Path::new(fname).file_name().unwrap_or_default();
69 if basename.is_empty()
70 || basename != fname
71 || fname == "."
72 || fname == ".."
73 || fname.contains(std::path::MAIN_SEPARATOR)
74 {
75 eprintln!("Error: filename must be a plain basename (no path separators or '..')");
76 std::process::exit(1);
77 }
78 let path = tee_dir.join(basename);
79 match crate::tools::ctx_read::read_file_lossy(&path.to_string_lossy()) {
80 Ok(content) => print!("{content}"),
81 Err(e) => {
82 eprintln!("Error reading {}: {e}", path.display());
83 std::process::exit(1);
84 }
85 }
86 }
87 "last" => {
88 if !tee_dir.exists() {
89 println!("No tee logs found.");
90 return;
91 }
92 let mut entries: Vec<_> = std::fs::read_dir(&tee_dir)
93 .ok()
94 .into_iter()
95 .flat_map(|d| d.filter_map(std::result::Result::ok))
96 .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("log"))
97 .collect();
98 entries.sort_by_key(|e| {
99 e.metadata()
100 .and_then(|m| m.modified())
101 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
102 });
103 match entries.last() {
104 Some(entry) => {
105 let path = entry.path();
106 println!(
107 "--- {} ---\n",
108 path.file_name().unwrap_or_default().to_string_lossy()
109 );
110 match crate::tools::ctx_read::read_file_lossy(&path.to_string_lossy()) {
111 Ok(content) => print!("{content}"),
112 Err(e) => eprintln!("Error: {e}"),
113 }
114 }
115 None => println!("No tee logs found."),
116 }
117 }
118 _ => {
119 eprintln!("Usage: lean-ctx tee [list|clear|show <file>|last]");
120 std::process::exit(1);
121 }
122 }
123}
124
125pub fn cmd_filter(args: &[String]) {
126 let action = args.first().map_or("list", std::string::String::as_str);
127 match action {
128 "list" | "ls" => {
129 if let Some(engine) = crate::core::filters::FilterEngine::load() {
130 let rules = engine.list_rules();
131 println!("Loaded {} filter rule(s):\n", rules.len());
132 for rule in &rules {
133 println!("{rule}");
134 }
135 } else {
136 println!("No custom filters found.");
137 println!("Create one: lean-ctx filter init");
138 }
139 }
140 "validate" => {
141 let Some(path) = args.get(1) else {
142 eprintln!("Usage: lean-ctx filter validate <file.toml>");
143 std::process::exit(1);
144 };
145 match crate::core::filters::validate_filter_file(path) {
146 Ok(count) => println!("Valid: {count} rule(s) parsed successfully."),
147 Err(e) => {
148 eprintln!("Validation failed: {e}");
149 std::process::exit(1);
150 }
151 }
152 }
153 "init" => match crate::core::filters::create_example_filter() {
154 Ok(path) => {
155 println!("Created example filter: {path}");
156 println!("Edit it to add your custom compression rules.");
157 }
158 Err(e) => {
159 eprintln!("{e}");
160 std::process::exit(1);
161 }
162 },
163 _ => {
164 eprintln!("Usage: lean-ctx filter [list|validate <file>|init]");
165 std::process::exit(1);
166 }
167 }
168}
169
170pub fn cmd_slow_log(args: &[String]) {
171 use crate::core::slow_log;
172
173 let action = args.first().map_or("list", std::string::String::as_str);
174 match action {
175 "list" | "ls" | "" => println!("{}", slow_log::list()),
176 "clear" | "purge" => println!("{}", slow_log::clear()),
177 _ => {
178 eprintln!("Usage: lean-ctx slow-log [list|clear]");
179 std::process::exit(1);
180 }
181 }
182}