claude_code_statusline_cli/
lib.rs

1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use std::io::{self, Read};
4
5/// Command line interface arguments structure (placeholder for future subcommands)
6#[derive(Parser)]
7#[command(name = env!("CARGO_PKG_NAME"))]
8#[command(version = env!("CARGO_PKG_VERSION"))]
9#[command(about = env!("CARGO_PKG_DESCRIPTION"))]
10struct Cli {
11    #[command(subcommand)]
12    command: Option<Command>,
13}
14
15#[derive(Subcommand)]
16enum Command {
17    /// Inspect or validate configuration
18    Config {
19        /// Print config file path
20        #[arg(long)]
21        path: bool,
22        /// Print default config (TOML)
23        #[arg(long)]
24        default: bool,
25        /// Validate current config
26        #[arg(long)]
27        validate: bool,
28    },
29    /// Inspect module registry
30    Modules {
31        /// List all available modules
32        #[arg(long)]
33        list: bool,
34        /// List enabled modules for current config
35        #[arg(long)]
36        enabled: bool,
37    },
38}
39
40/// Run the claude-code-statusline CLI: read stdin JSON, render status line, write stdout.
41pub fn run() -> Result<()> {
42    let _cli = Cli::parse();
43    if let Some(cmd) = &_cli.command {
44        // Minimal subscriber for subcommands
45        let _ = tracing_subscriber::fmt()
46            .with_env_filter("error")
47            .with_writer(std::io::stderr)
48            .try_init();
49
50        match cmd {
51            Command::Config {
52                path,
53                default,
54                validate,
55            } => {
56                if *path {
57                    let path = claude_code_statusline_core::config_path();
58                    println!("{}", path.display());
59                    return Ok(());
60                }
61                if *default {
62                    let toml =
63                        toml::to_string_pretty(&claude_code_statusline_core::Config::default())
64                            .unwrap_or_else(|_| "".into());
65                    println!("{toml}");
66                    return Ok(());
67                }
68                if *validate {
69                    match claude_code_statusline_core::Config::load() {
70                        Ok(cfg) => match cfg.validate() {
71                            Ok(()) => {
72                                println!("OK");
73                            }
74                            Err(e) => {
75                                eprintln!("Config validation error: {e}");
76                                println!("INVALID");
77                            }
78                        },
79                        Err(e) => {
80                            eprintln!("Config error: {e}");
81                            println!("INVALID");
82                        }
83                    }
84                    return Ok(());
85                }
86                // If no flags, show help
87                println!("Use --path | --default | --validate");
88                return Ok(());
89            }
90            Command::Modules { list, enabled } => {
91                if *list {
92                    let reg = claude_code_statusline_core::modules::Registry::with_defaults();
93                    for name in reg.list() {
94                        println!("{name}");
95                    }
96                    return Ok(());
97                }
98                if *enabled {
99                    let cfg = claude_code_statusline_core::Config::load().unwrap_or_default();
100                    let names = claude_code_statusline_core::parser::extract_modules_from_format(
101                        &cfg.format,
102                    );
103                    let reg = claude_code_statusline_core::modules::Registry::with_defaults();
104                    for name in names {
105                        if name == "character" {
106                            continue;
107                        }
108                        // Only consider registered modules
109                        if !reg.list().contains(&name.as_str()) {
110                            continue;
111                        }
112                        let is_enabled = match name.as_str() {
113                            "directory" => !cfg.directory.disabled,
114                            "claude_model" => !cfg.claude_model.disabled,
115                            "git_branch" => !cfg.git_branch.disabled,
116                            "git_status" => !cfg.git_status.disabled,
117                            _ => true,
118                        };
119                        if is_enabled {
120                            println!("{name}");
121                        }
122                    }
123                    return Ok(());
124                }
125                println!("Use --list | --enabled");
126                return Ok(());
127            }
128        }
129    }
130
131    // Load configuration with graceful error handling
132    let config = match claude_code_statusline_core::Config::load() {
133        Ok(cfg) => cfg,
134        Err(e) => {
135            // Initialize minimal subscriber to show errors (stderr)
136            let _ = tracing_subscriber::fmt()
137                .with_env_filter("error")
138                .with_writer(std::io::stderr)
139                .try_init();
140            tracing::error!(error = %e, "Config error");
141            eprintln!("Config error: {e}");
142            let msg = claude_code_statusline_core::messages::MSG_FAILED_INVALID_CONFIG;
143            print!("{msg}");
144            io::Write::flush(&mut io::stdout())?;
145            return Ok(());
146        }
147    };
148
149    // Initialize tracing subscriber based on config.debug
150    {
151        let level = if config.debug { "debug" } else { "error" };
152        let _ = tracing_subscriber::fmt()
153            .with_env_filter(level)
154            .with_target(false)
155            .with_writer(std::io::stderr)
156            .try_init();
157    }
158
159    // Initialize debug logger
160    let logger = claude_code_statusline_core::debug::DebugLogger::new(config.debug);
161    logger.log_execution_start();
162    logger.log_config(config.debug, config.command_timeout);
163
164    // Config validation and non-fatal warnings
165    if let Err(e) = config.validate() {
166        tracing::error!(error = %e, "Config validation error");
167        eprintln!("Config validation error: {e}");
168        print!("Failed to build status line due to invalid config");
169        io::Write::flush(&mut io::stdout())?;
170        return Ok(());
171    }
172    for w in config.collect_warnings() {
173        tracing::warn!("{w}");
174    }
175
176    // Read JSON input from stdin
177    let mut buffer = String::new();
178    if io::stdin().read_to_string(&mut buffer).is_err() || buffer.trim().is_empty() {
179        let msg = claude_code_statusline_core::messages::MSG_FAILED_EMPTY_INPUT;
180        print!("{msg}");
181        io::Write::flush(&mut io::stdout())?;
182        return Ok(());
183    }
184    logger.log_input(&buffer);
185
186    // Parse JSON input
187    let input = match claude_code_statusline_core::parse_claude_input(&buffer) {
188        Ok(i) => i,
189        Err(e) => {
190            tracing::error!(error = %e, "Failed to parse JSON");
191            eprintln!("Failed to parse JSON: {e}");
192            let msg = claude_code_statusline_core::messages::MSG_FAILED_INVALID_JSON;
193            print!("{msg}");
194            io::Write::flush(&mut io::stdout())?;
195            return Ok(());
196        }
197    };
198    logger.log_success(&input.model.display_name, &input.cwd);
199
200    // Render via engine
201    let engine = claude_code_statusline_core::Engine::new(config);
202    match engine.render(&input) {
203        Ok(out) => {
204            print!("{out}");
205            io::Write::flush(&mut io::stdout())?;
206        }
207        Err(e) => {
208            tracing::error!(error = %e, "Render error");
209            eprintln!("Render error: {e}");
210        }
211    }
212
213    Ok(())
214}