gigi-cli 1.0.0

Gigi — A Claude Code-like AI coding assistant CLI in Rust
use anyhow::Result;
use colored::*;

use crate::agent::Agent;

// =============================================================================
// /commit — Generate a commit message and commit staged changes
// =============================================================================

pub async fn handle(agent: &mut Agent, args: &str) -> Result<()> {
    println!("{}", "Generating commit message...".dimmed());

    // Get the git diff of staged changes (or all changes if nothing staged)
    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() {
        // Try unstaged changes
        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(());
    }

    // Ask the AI to generate a commit message
    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()
    }
}