use std::path::PathBuf;
use std::sync::Arc;
use clap::{Parser, Subcommand};
use serde::Deserialize;
#[derive(Parser)]
#[command(name = "retcon")]
#[command(about = "Reconstruct clean git history from messy branches")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Prompt,
Execute {
plan: PathBuf,
#[arg(long)]
agent: Option<String>,
#[arg(long)]
build_command: Option<String>,
#[arg(long)]
test_command: Option<String>,
#[arg(long = "skip", value_name = "STEP")]
skip: Vec<SkipStep>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
enum SkipStep {
Build,
Test,
}
#[derive(Debug, Default, Deserialize)]
struct Config {
#[serde(default)]
agent: Option<String>,
}
fn load_config() -> Config {
let Some(home) = dirs::home_dir() else {
return Config::default();
};
let path = home.join(".retcon").join("config.toml");
std::fs::read_to_string(&path)
.ok()
.and_then(|s| toml::from_str(&s).ok())
.unwrap_or_default()
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let config_file = load_config();
match cli.command {
Command::Prompt => {
print!("{}", retcon::prompt());
}
Command::Execute {
plan,
agent,
build_command,
test_command,
skip,
} => {
let config = retcon::ExecuteConfig {
build_command: if skip.contains(&SkipStep::Build) {
None
} else {
Some(
build_command
.unwrap_or_else(|| "cargo check --all --workspace".to_string()),
)
},
test_command: if skip.contains(&SkipStep::Test) {
None
} else {
Some(test_command.unwrap_or_else(|| "cargo test --all --workspace".to_string()))
},
agent: agent.or(config_file.agent),
};
let (observer, hooks) = retcon::tui::new();
retcon::execute_with_hooks(&plan, &config, &hooks, Some(Arc::new(observer))).await?;
}
}
Ok(())
}