1use crate::changes;
2use crate::commands;
3use crate::commit;
4use crate::common::CommonParams;
5use crate::llm::get_available_provider_names;
6use crate::log_debug;
7use crate::ui;
8use clap::builder::{Styles, styling::AnsiColor};
9use clap::{Parser, Subcommand, crate_version};
10use colored::Colorize;
11
12const LOG_FILE: &str = "git-iris-debug.log";
13
14#[derive(Parser)]
16#[command(
17 author,
18 version = crate_version!(),
19 about = "Git-Iris: AI-powered Git workflow assistant",
20 long_about = "Git-Iris enhances your Git workflow with AI-assisted commit messages, code reviews, changelogs, and more.",
21 disable_version_flag = true,
22 after_help = get_dynamic_help(),
23 styles = get_styles(),
24)]
25pub struct Cli {
26 #[command(subcommand)]
28 pub command: Option<Commands>,
29
30 #[arg(
32 short = 'l',
33 long = "log",
34 global = true,
35 help = "Log debug messages to a file"
36 )]
37 pub log: bool,
38
39 #[arg(
41 short = 'v',
42 long = "version",
43 global = true,
44 help = "Display the version"
45 )]
46 pub version: bool,
47}
48
49#[derive(Subcommand)]
51#[command(subcommand_negates_reqs = true)]
52#[command(subcommand_precedence_over_arg = true)]
53pub enum Commands {
54 #[command(
57 about = "Generate a commit message using AI",
58 long_about = "Generate a commit message using AI based on the current Git context.",
59 after_help = get_dynamic_help()
60 )]
61 Gen {
62 #[command(flatten)]
63 common: CommonParams,
64
65 #[arg(short, long, help = "Automatically commit with the generated message")]
67 auto_commit: bool,
68
69 #[arg(long, help = "Disable Gitmoji for this commit")]
71 no_gitmoji: bool,
72
73 #[arg(short, long, help = "Print the generated message to stdout and exit")]
75 print: bool,
76
77 #[arg(long, help = "Skip verification steps (pre/post commit hooks)")]
79 no_verify: bool,
80 },
81
82 #[command(
84 about = "Review staged changes using AI",
85 long_about = "Generate a detailed code review of staged changes using AI based on the current Git context."
86 )]
87 Review {
88 #[command(flatten)]
89 common: CommonParams,
90
91 #[arg(short, long, help = "Print the generated review to stdout and exit")]
93 print: bool,
94 },
95
96 #[command(
98 about = "Generate a changelog",
99 long_about = "Generate a changelog between two specified Git references."
100 )]
101 Changelog {
102 #[command(flatten)]
103 common: CommonParams,
104
105 #[arg(long, required = true)]
107 from: String,
108
109 #[arg(long)]
111 to: Option<String>,
112 },
113
114 #[command(
116 about = "Generate release notes",
117 long_about = "Generate comprehensive release notes between two specified Git references."
118 )]
119 ReleaseNotes {
120 #[command(flatten)]
121 common: CommonParams,
122
123 #[arg(long, required = true)]
125 from: String,
126
127 #[arg(long)]
129 to: Option<String>,
130 },
131
132 #[command(about = "Configure Git-Iris settings and providers")]
135 Config {
136 #[command(flatten)]
137 common: CommonParams,
138
139 #[arg(long, help = "Set API key for the specified provider")]
141 api_key: Option<String>,
142
143 #[arg(long, help = "Set model for the specified provider")]
145 model: Option<String>,
146
147 #[arg(long, help = "Set token limit for the specified provider")]
149 token_limit: Option<usize>,
150
151 #[arg(
153 long,
154 help = "Set additional parameters for the specified provider (key=value)"
155 )]
156 param: Option<Vec<String>>,
157 },
158
159 #[command(about = "List available instruction presets")]
161 ListPresets,
162}
163
164fn get_styles() -> Styles {
166 Styles::styled()
167 .header(AnsiColor::Magenta.on_default().bold())
168 .usage(AnsiColor::Cyan.on_default().bold())
169 .literal(AnsiColor::Green.on_default().bold())
170 .placeholder(AnsiColor::Yellow.on_default())
171 .valid(AnsiColor::Blue.on_default().bold())
172 .invalid(AnsiColor::Red.on_default().bold())
173 .error(AnsiColor::Red.on_default().bold())
174}
175
176pub fn parse_args() -> Cli {
178 Cli::parse()
179}
180
181fn get_dynamic_help() -> String {
183 let mut providers = get_available_provider_names();
184 providers.sort(); let providers_list = providers
187 .iter()
188 .map(|p| format!("{}", p.bold()))
189 .collect::<Vec<_>>()
190 .join(" • ");
191
192 format!("\nAvailable LLM Providers: {providers_list}")
193}
194
195pub async fn main() -> anyhow::Result<()> {
197 let cli = parse_args();
198
199 if cli.version {
200 ui::print_version(crate_version!());
201 return Ok(());
202 }
203
204 if cli.log {
205 crate::logger::enable_logging();
206 crate::logger::set_log_file(LOG_FILE)?;
207 } else {
208 crate::logger::disable_logging();
209 }
210
211 if let Some(command) = cli.command {
212 handle_command(command).await
213 } else {
214 let _ = Cli::parse_from(["git-iris", "--help"]);
216 Ok(())
217 }
218}
219
220pub async fn handle_command(command: Commands) -> anyhow::Result<()> {
222 match command {
223 Commands::Gen {
224 common,
225 auto_commit,
226 no_gitmoji,
227 print,
228 no_verify,
229 } => {
230 log_debug!(
231 "Handling 'gen' command with common: {:?}, auto_commit: {}, no_gitmoji: {}, print: {}, no_verify: {}",
232 common,
233 auto_commit,
234 no_gitmoji,
235 print,
236 no_verify
237 );
238
239 ui::print_version(crate_version!());
240 println!();
241
242 commit::handle_gen_command(common, auto_commit, !no_gitmoji, print, !no_verify).await?;
243 }
244 Commands::Config {
245 common,
246 api_key,
247 model,
248 token_limit,
249 param,
250 } => {
251 log_debug!(
252 "Handling 'config' command with common: {:?}, api_key: {:?}, model: {:?}, token_limit: {:?}, param: {:?}",
253 common,
254 api_key,
255 model,
256 token_limit,
257 param
258 );
259 commands::handle_config_command(common, api_key, model, token_limit, param)?;
260 }
261 Commands::ListPresets => {
262 log_debug!("Handling 'list_presets' command");
263 commands::handle_list_presets_command()?;
264 }
265 Commands::Changelog { common, from, to } => {
266 log_debug!(
267 "Handling 'changelog' command with common: {:?}, from: {}, to: {:?}",
268 common,
269 from,
270 to
271 );
272 changes::handle_changelog_command(common, from, to).await?;
273 }
274 Commands::ReleaseNotes { common, from, to } => {
275 log_debug!(
276 "Handling 'release-notes' command with common: {:?}, from: {}, to: {:?}",
277 common,
278 from,
279 to
280 );
281 changes::handle_release_notes_command(common, from, to).await?;
282 }
283 Commands::Review { common, print } => {
284 log_debug!(
285 "Handling 'review' command with common: {:?}, print: {}",
286 common,
287 print
288 );
289 ui::print_version(crate_version!());
290 println!();
291 commit::review::handle_review_command(common, print).await?;
292 }
293 }
294
295 Ok(())
296}