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