Skip to main content

lean_ctx/cli/
common.rs

1pub(crate) fn print_savings(original: usize, sent: usize) {
2    let saved = original.saturating_sub(sent);
3    if original > 0 && saved > 0 {
4        let pct = (saved as f64 / original as f64 * 100.0).round() as usize;
5        println!("[{saved} tok saved ({pct}%)]");
6    }
7}
8
9pub fn load_shell_history_pub() -> Vec<String> {
10    load_shell_history()
11}
12
13pub(crate) fn load_shell_history() -> Vec<String> {
14    let shell = std::env::var("SHELL").unwrap_or_default();
15    let Some(home) = dirs::home_dir() else {
16        return Vec::new();
17    };
18
19    let history_file = if shell.contains("zsh") {
20        home.join(".zsh_history")
21    } else if shell.contains("fish") {
22        home.join(".local/share/fish/fish_history")
23    } else if cfg!(windows) && shell.is_empty() {
24        home.join("AppData")
25            .join("Roaming")
26            .join("Microsoft")
27            .join("Windows")
28            .join("PowerShell")
29            .join("PSReadLine")
30            .join("ConsoleHost_history.txt")
31    } else {
32        home.join(".bash_history")
33    };
34
35    match std::fs::read_to_string(&history_file) {
36        Ok(content) => content
37            .lines()
38            .filter_map(|l| {
39                let trimmed = l.trim();
40                if trimmed.starts_with(':') {
41                    trimmed
42                        .split(';')
43                        .nth(1)
44                        .map(std::string::ToString::to_string)
45                } else {
46                    Some(trimmed.to_string())
47                }
48            })
49            .filter(|l| !l.is_empty())
50            .collect(),
51        Err(_) => Vec::new(),
52    }
53}
54
55pub(crate) fn daemon_fallback_hint() {
56    use std::sync::Once;
57    static HINT: Once = Once::new();
58    HINT.call_once(|| {
59        eprintln!("\x1b[2;33mhint: daemon not running — stats tracked locally (lean-ctx serve -d for full tracking)\x1b[0m");
60    });
61}
62
63pub(crate) fn format_tokens_cli(tokens: u64) -> String {
64    if tokens >= 1_000_000 {
65        format!("{:.1}M", tokens as f64 / 1_000_000.0)
66    } else if tokens >= 1_000 {
67        format!("{:.1}K", tokens as f64 / 1_000.0)
68    } else {
69        format!("{tokens}")
70    }
71}
72
73pub(crate) fn cli_track_read(path: &str, mode: &str, original_tokens: usize, output_tokens: usize) {
74    crate::core::tool_lifecycle::record_file_read(
75        path,
76        mode,
77        original_tokens,
78        output_tokens,
79        false,
80    );
81}
82
83pub(crate) fn cli_track_read_cached(
84    path: &str,
85    mode: &str,
86    original_tokens: usize,
87    output_tokens: usize,
88) {
89    crate::core::tool_lifecycle::record_file_read(path, mode, original_tokens, output_tokens, true);
90}
91
92pub(crate) fn cli_track_search(original_tokens: usize, output_tokens: usize) {
93    crate::core::tool_lifecycle::record_search(original_tokens, output_tokens);
94}
95
96pub(crate) fn cli_track_tree(original_tokens: usize, output_tokens: usize) {
97    crate::core::tool_lifecycle::record_tree(original_tokens, output_tokens);
98}
99
100pub(crate) fn detect_project_root(args: &[String]) -> String {
101    let mut it = args.iter().peekable();
102    while let Some(a) = it.next() {
103        if let Some(v) = a.strip_prefix("--root=") {
104            if !v.trim().is_empty() {
105                return v.to_string();
106            }
107        }
108        if let Some(v) = a.strip_prefix("--project-root=") {
109            if !v.trim().is_empty() {
110                return v.to_string();
111            }
112        }
113        if a == "--root" || a == "--project-root" {
114            if let Some(v) = it.peek() {
115                if !v.starts_with("--") && !v.trim().is_empty() {
116                    return (*v).clone();
117                }
118            }
119        }
120    }
121    std::env::current_dir()
122        .ok()
123        .map_or_else(|| ".".to_string(), |p| p.to_string_lossy().to_string())
124}