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::{styling::AnsiColor, Styles};
9use clap::{crate_version, Parser, Subcommand};
10
11const LOG_FILE: &str = "git-iris-debug.log";
12
13#[derive(Parser)]
15#[command(
16 author,
17 version = crate_version!(),
18 about = "AI-assisted Git commit message generator",
19 long_about = None,
20 disable_version_flag = true,
21 after_help = get_dynamic_help(),
22 styles = get_styles(),
23)]
24pub struct Cli {
25 #[command(subcommand)]
27 pub command: Option<Commands>,
28
29 #[arg(
31 short = 'l',
32 long = "log",
33 global = true,
34 help = "Log debug messages to a file"
35 )]
36 pub log: bool,
37
38 #[arg(
40 short = 'v',
41 long = "version",
42 global = true,
43 help = "Display the version"
44 )]
45 pub version: bool,
46}
47
48#[derive(Subcommand)]
50pub enum Commands {
51 #[command(
53 about = "Generate a commit message using AI",
54 long_about = "Generate a commit message using AI based on the current Git context.",
55 after_help = get_dynamic_help()
56 )]
57 Gen {
58 #[command(flatten)]
59 common: CommonParams,
60
61 #[arg(short, long, help = "Automatically commit with the generated message")]
63 auto_commit: bool,
64
65 #[arg(long, help = "Disable Gitmoji for this commit")]
67 no_gitmoji: bool,
68
69 #[arg(short, long, help = "Print the generated message to stdout and exit")]
71 print: bool,
72
73 #[arg(long, help = "Skip verification steps (pre/post commit hooks)")]
75 no_verify: bool,
76 },
77 #[command(about = "Configure the AI-assisted Git commit message generator")]
79 Config {
80 #[command(flatten)]
81 common: CommonParams,
82
83 #[arg(long, help = "Set API key for the specified provider")]
85 api_key: Option<String>,
86
87 #[arg(long, help = "Set model for the specified provider")]
89 model: Option<String>,
90
91 #[arg(long, help = "Set token limit for the specified provider")]
93 token_limit: Option<usize>,
94
95 #[arg(
97 long,
98 help = "Set additional parameters for the specified provider (key=value)"
99 )]
100 param: Option<Vec<String>>,
101 },
102
103 #[command(about = "List available instruction presets")]
105 ListPresets,
106
107 #[command(
109 about = "Generate a changelog",
110 long_about = "Generate a changelog between two specified Git references."
111 )]
112 Changelog {
113 #[command(flatten)]
114 common: CommonParams,
115
116 #[arg(long, required = true)]
118 from: String,
119
120 #[arg(long)]
122 to: Option<String>,
123 },
124 #[command(
126 about = "Generate release notes",
127 long_about = "Generate comprehensive release notes between two specified Git references."
128 )]
129 ReleaseNotes {
130 #[command(flatten)]
131 common: CommonParams,
132
133 #[arg(long, required = true)]
135 from: String,
136
137 #[arg(long)]
139 to: Option<String>,
140 },
141}
142
143fn get_styles() -> Styles {
145 Styles::styled()
146 .header(AnsiColor::Magenta.on_default().bold())
147 .usage(AnsiColor::Cyan.on_default().bold())
148 .literal(AnsiColor::Green.on_default().bold())
149 .placeholder(AnsiColor::Yellow.on_default())
150 .valid(AnsiColor::Blue.on_default().bold())
151 .invalid(AnsiColor::Red.on_default().bold())
152 .error(AnsiColor::Red.on_default().bold())
153}
154
155pub fn parse_args() -> Cli {
157 Cli::parse()
158}
159
160fn get_dynamic_help() -> String {
162 let providers = get_available_provider_names().join(", ");
163 format!("Available providers: {providers}")
164}
165
166pub async fn main() -> anyhow::Result<()> {
168 let cli = parse_args();
169
170 if cli.version {
171 ui::print_version(crate_version!());
172 return Ok(());
173 }
174
175 if cli.log {
176 crate::logger::enable_logging();
177 crate::logger::set_log_file(LOG_FILE)?;
178 } else {
179 crate::logger::disable_logging();
180 }
181
182 if let Some(command) = cli.command {
183 handle_command(command).await
184 } else {
185 let _ = Cli::parse_from(["git-iris", "--help"]);
187 Ok(())
188 }
189}
190
191pub async fn handle_command(command: Commands) -> anyhow::Result<()> {
193 match command {
194 Commands::Gen {
195 common,
196 auto_commit,
197 no_gitmoji,
198 print,
199 no_verify,
200 } => {
201 log_debug!(
202 "Handling 'gen' command with common: {:?}, auto_commit: {}, no_gitmoji: {}, print: {}, no_verify: {}",
203 common, auto_commit, no_gitmoji, print, no_verify
204 );
205
206 ui::print_version(crate_version!());
207 println!();
208
209 commit::handle_gen_command(common, auto_commit, !no_gitmoji, print, !no_verify).await?;
210 }
211 Commands::Config {
212 common,
213 api_key,
214 model,
215 token_limit,
216 param,
217 } => {
218 log_debug!(
219 "Handling 'config' command with common: {:?}, api_key: {:?}, model: {:?}, token_limit: {:?}, param: {:?}",
220 common, api_key, model, token_limit, param
221 );
222 commands::handle_config_command(common, api_key, model, token_limit, param)?;
223 }
224 Commands::ListPresets => {
225 log_debug!("Handling 'list_presets' command");
226 commands::handle_list_presets_command()?;
227 }
228 Commands::Changelog { common, from, to } => {
229 log_debug!(
230 "Handling 'changelog' command with common: {:?}, from: {}, to: {:?}",
231 common,
232 from,
233 to
234 );
235 changes::handle_changelog_command(common, from, to).await?;
236 }
237 Commands::ReleaseNotes { common, from, to } => {
238 log_debug!(
239 "Handling 'release-notes' command with common: {:?}, from: {}, to: {:?}",
240 common,
241 from,
242 to
243 );
244 changes::handle_release_notes_command(common, from, to).await?;
245 }
246 }
247
248 Ok(())
249}