claude_code_statusline_cli/
lib.rs1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use std::io::{self, Read};
4
5#[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 Config {
19 #[arg(long)]
21 path: bool,
22 #[arg(long)]
24 default: bool,
25 #[arg(long)]
27 validate: bool,
28 },
29 Modules {
31 #[arg(long)]
33 list: bool,
34 #[arg(long)]
36 enabled: bool,
37 },
38}
39
40pub fn run() -> Result<()> {
42 let _cli = Cli::parse();
43 if let Some(cmd) = &_cli.command {
44 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 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 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 let config = match claude_code_statusline_core::Config::load() {
133 Ok(cfg) => cfg,
134 Err(e) => {
135 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 {
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 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 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 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 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 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}