stax 0.50.2

Fast stacked Git branches and PRs
Documentation
use crate::commands::generate;
use crate::config::Config;
use anyhow::Result;
use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Confirm, Select};
use std::fs;
use std::io::IsTerminal;

pub fn run(reset_ai: bool, no_prompt: bool, yes: bool, set_ai: bool) -> Result<()> {
    if reset_ai {
        return reset_ai_defaults(no_prompt, yes);
    }
    if set_ai {
        return set_ai_interactive();
    }

    let path = Config::path()?;

    println!("{}", "Config path:".blue().bold());
    println!("  {}\n", path.display());

    if path.exists() {
        let content = fs::read_to_string(&path)?;
        println!("{}", "Contents:".blue().bold());
        println!("{}", content);
    } else {
        println!("{}", "Config file does not exist yet.".yellow());
        println!("Run any stax command to create a default config.");
    }

    println!();
    println!("{}", "Submit stack links setting:".blue().bold());
    println!("  [submit]");
    println!(r#"  stack_links = "comment"  # "comment" | "body" | "both" | "off""#);
    println!(r#"  # Example: stack_links = "body""#);

    println!();
    println!("{}", "Per-feature AI overrides:".blue().bold());
    println!(r#"  [ai.generate]  # PR body generation (stax generate, stax submit --ai-body)"#);
    println!(r#"  [ai.standup]   # standup summaries (stax standup --summary)"#);
    println!(r#"  [ai.resolve]   # conflict resolution (stax resolve)"#);
    println!(r#"  [ai.lane]      # interactive AI lanes (stax lane)"#);
    println!(r#"  # Each accepts optional `agent` and `model` keys."#);
    println!(r#"  # Omitted keys fall back to [ai] global defaults."#);

    Ok(())
}

fn set_ai_interactive() -> Result<()> {
    const FEATURES: &[(&str, &str)] = &[
        (
            "global",
            "Global default  (used when no feature override is set)",
        ),
        (
            "generate",
            "generate        (PR body — stax generate, stax submit --ai-body)",
        ),
        (
            "standup",
            "standup         (standup summary — stax standup --summary)",
        ),
        (
            "resolve",
            "resolve         (conflict resolution — stax resolve)",
        ),
        (
            "lane",
            "lane            (interactive coding agent — stax lane)",
        ),
    ];

    let items: Vec<&str> = FEATURES.iter().map(|(_, label)| *label).collect();

    let selection = Select::with_theme(&ColorfulTheme::default())
        .with_prompt("Configure AI agent/model for which feature?")
        .items(&items)
        .default(0)
        .interact()?;

    let (feature, _) = FEATURES[selection];
    let mut config = Config::load()?;
    generate::prompt_for_feature_ai(&mut config, feature)?;

    Ok(())
}

fn reset_ai_defaults(no_prompt: bool, yes: bool) -> Result<()> {
    let path = Config::path()?;
    let mut config = Config::load()?;

    if !yes && std::io::stdin().is_terminal() {
        let confirmed = Confirm::with_theme(&ColorfulTheme::default())
            .with_prompt("Clear saved AI agent/model defaults from config?")
            .default(true)
            .interact()?;

        if !confirmed {
            println!("{}", "Cancelled.".yellow());
            return Ok(());
        }
    }

    let had_saved_defaults = config.clear_ai_defaults();
    config.save()?;

    if had_saved_defaults {
        println!(
            "  {} Cleared saved AI defaults in {}",
            "".green().bold(),
            path.display()
        );
    } else {
        println!(
            "  {} No saved AI defaults were set in {}",
            "".green().bold(),
            path.display()
        );
    }

    if no_prompt {
        println!(
            "  {} Skipped reconfiguration because --no-prompt was set.",
            "Tip:".dimmed()
        );
        return Ok(());
    }

    if !std::io::stdin().is_terminal() {
        println!(
            "  {} Not running in an interactive terminal, so stax did not re-prompt.",
            "Tip:".dimmed()
        );
        return Ok(());
    }

    println!(
        "  {} Re-select the AI agent/model to save new defaults.",
        "Next:".dimmed()
    );
    let _ = generate::prompt_for_agent_and_model(&mut config, false)?;

    println!(
        "  {} Updated defaults will be used by future interactive AI flows.",
        "Tip:".dimmed()
    );

    Ok(())
}