use anyhow::Result;
use colored::*;
use crate::agent::Agent;
pub async fn handle(agent: &mut Agent, args: &str) -> Result<()> {
println!("{}", "Generating commit message...".dimmed());
let diff_result = agent
.tools
.execute(
"bash",
serde_json::json!({
"command": "git diff --cached --stat && echo '---DIFF---' && git diff --cached"
}),
)
.await?;
let diff_content = extract_stdout_stderr(&diff_result.content);
if diff_content.contains("nothing to commit") || diff_content.trim().is_empty() {
let unstaged = agent
.tools
.execute(
"bash",
serde_json::json!({
"command": "git diff --stat"
}),
)
.await?;
let unstaged_content = extract_stdout_stderr(&unstaged.content);
if unstaged_content.trim().is_empty() {
println!("{}", "No changes to commit.".yellow());
return Ok(());
}
println!(
"{}",
"No staged changes. Stage your changes first with 'git add'.".yellow()
);
return Ok(());
}
let prompt = if args.is_empty() {
format!(
"Based on the following git diff, write a concise and descriptive commit message \
following conventional commits format (e.g., feat:, fix:, refactor:). \
Only output the commit message, nothing else.\n\n{}",
diff_content
)
} else {
format!(
"Write a git commit message for the following changes. \
The user wants to emphasize: {}\n\nDiff:\n{}",
args, diff_content
)
};
agent.run_turn(&prompt).await?;
let commit_message = agent
.session
.messages
.last()
.and_then(|m| m.text())
.unwrap_or("")
.trim()
.to_string();
if commit_message.is_empty() {
println!("{}", "Error: No commit message generated by the AI.".red());
return Ok(());
}
let clean_message = commit_message
.trim_matches('`')
.trim_matches('"')
.trim_matches('\'')
.trim();
print!("Would you like to commit with this message? (y/N): ");
use std::io::Write;
let _ = std::io::stdout().flush();
let mut user_input = String::new();
if std::io::stdin().read_line(&mut user_input).is_ok() {
let response = user_input.trim().to_lowercase();
if response == "y" || response == "yes" {
println!("{}", "Committing changes...".dimmed());
let output = std::process::Command::new("git")
.args(["commit", "-m", clean_message])
.output();
match output {
Ok(out) => {
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
if out.status.success() {
println!("{}", "✓ Committed successfully!".green());
if !stdout.trim().is_empty() {
println!("{}", stdout.trim());
}
} else {
println!("{}", "✗ Git commit failed:".red());
if !stdout.trim().is_empty() {
println!("{}", stdout.trim());
}
if !stderr.trim().is_empty() {
println!("{}", stderr.trim().red());
}
}
}
Err(e) => {
println!("{}", format!("✗ Failed to execute git command: {}", e).red());
}
}
} else {
println!("{}", "Commit cancelled.".yellow());
}
}
Ok(())
}
fn extract_stdout_stderr(content: &str) -> String {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(content) {
let stdout = val["stdout"].as_str().unwrap_or("");
let stderr = val["stderr"].as_str().unwrap_or("");
if !stdout.is_empty() && !stderr.is_empty() {
format!("{}\n{}", stdout, stderr)
} else if !stdout.is_empty() {
stdout.to_string()
} else {
stderr.to_string()
}
} else {
content.to_string()
}
}