chuggin 0.12.5

A terminal harness for persistent projects and continuous local model refinement
mod action_watch;
mod code_index;
mod command_jobs;
mod command_output;
mod command_session;
mod command_watch;
mod dev_tools;
mod events;
mod menu;
mod migration;
mod model;
mod nudge;
mod project;
mod prompts;
mod provider;
mod repetition;
mod run_control;
mod runner;
mod setup;
mod stall_diagnostic;
mod symbols;
mod ui;
mod web_tools;
mod workspace;

use anyhow::Result;
use clap::{Parser, Subcommand};
use std::{
    path::PathBuf,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
};

#[derive(Parser)]
#[command(
    version,
    about = "Persistent projects and continuous local model refinement"
)]
struct Cli {
    #[command(subcommand)]
    command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
    /// Interactive project setup without starting the loop.
    Setup,
    /// Edit shared Ollama and model defaults.
    Settings {
        #[arg(long)]
        show: bool,
    },
    /// Write a project configuration without interactive goal drafting.
    Init {
        #[arg(long, default_value = "chuggin.json")]
        config: PathBuf,
        #[arg(long)]
        repo: PathBuf,
        #[arg(long)]
        goal: String,
    },
    /// Run a bounded experiment, or use --forever.
    Run {
        #[arg(long, default_value = "chuggin.json")]
        config: PathBuf,
        #[arg(long, default_value_t = 1)]
        cycles: u64,
        #[arg(long)]
        forever: bool,
    },
    /// Preview or apply a legacy workspace migration (also available in the menu).
    Migrate {
        #[arg(long, default_value = "chuggin.json")]
        config: PathBuf,
        #[arg(long)]
        apply: bool,
        #[arg(long)]
        clear_staging: bool,
    },
    /// Inspect saved progress.
    Status {
        #[arg(long)]
        config: Option<PathBuf>,
    },
}
fn main() -> Result<()> {
    let cli = Cli::parse();
    let stopped = Arc::new(AtomicBool::new(false));
    let running = Arc::new(AtomicBool::new(false));
    let flag = stopped.clone();
    let active = running.clone();
    ctrlc::set_handler(move || {
        if !active.load(Ordering::SeqCst) || flag.swap(true, Ordering::SeqCst) {
            ui::restore();
            project::kill_active_check();
            eprintln!("Stopped. Run chuggin again to resume from saved progress.");
            std::process::exit(130);
        }
        events::log("Stop requested: finishing this loop, then saving. In the dashboard, press R to resume; Ctrl-C again stops immediately.".into());
    })?;
    match cli.command {
        None => menu::home(stopped, running),
        Some(Command::Setup) => {
            setup::wizard()?;
            Ok(())
        }
        Some(Command::Settings { show }) => setup::configure(show),
        Some(Command::Init { config, repo, goal }) => runner::init(&config, &repo, &goal),
        Some(Command::Migrate {
            config,
            apply,
            clear_staging,
        }) => {
            if apply {
                runner::migration_apply(&config, clear_staging)
            } else {
                events::log(runner::migration_preview(&config)?);
                Ok(())
            }
        }
        Some(Command::Status { config }) => {
            let path = config
                .or(setup::find_project()?)
                .unwrap_or(PathBuf::from("chuggin.json"));
            runner::status(&path)
        }
        Some(Command::Run {
            config,
            cycles,
            forever,
        }) => {
            running.store(true, Ordering::SeqCst);
            let config = if config == std::path::Path::new("chuggin.json") && !config.exists() {
                setup::find_project()?.unwrap_or(config)
            } else {
                config
            };
            runner::run(&config, if forever { None } else { Some(cycles) }, stopped)
        }
    }
}