mod browser;
mod cloud;
pub(crate) mod config_file;
mod docker;
pub(crate) mod llm;
mod local;
mod searxng;
mod shell;
pub mod ui;
mod wizard;
use clap::Args;
#[derive(Args)]
pub struct SetupArgs {
#[arg(long)]
pub non_interactive: bool,
#[arg(long, conflicts_with = "local")]
pub cloud: bool,
#[arg(long, conflicts_with = "cloud")]
pub local: bool,
#[arg(long)]
pub no_color: bool,
#[arg(long, conflicts_with_all = ["cloud", "local", "reset"])]
pub reset_shell: bool,
#[arg(long, conflicts_with_all = ["cloud", "local", "reset_shell"])]
pub reset: bool,
#[arg(long, requires = "reset")]
pub yes: bool,
}
pub async fn run(args: SetupArgs) {
ui::init_color(args.no_color);
if args.reset_shell {
let res = run_reset_shell();
match res {
Ok(()) => return,
Err(e) => {
eprintln!();
eprintln!(" Reset failed: {}", e);
eprintln!();
std::process::exit(1);
}
}
}
if args.reset {
match run_full_reset(args.yes) {
Ok(()) => return,
Err(e) => {
if matches!(e, ui::SetupError::Cancelled) {
println!();
println!(" Reset cancelled. Nothing was removed.");
println!();
std::process::exit(130);
}
eprintln!();
eprintln!(" Reset failed: {}", e);
eprintln!();
std::process::exit(1);
}
}
}
let result = if args.cloud {
cloud::run().await
} else if args.local {
local::run().await
} else {
wizard::run_wizard().await
};
match result {
Ok(()) => {}
Err(e) => {
if let ui::SetupError::Cancelled = e {
ui::print_cancelled();
std::process::exit(130); } else {
eprintln!();
eprintln!(" Setup failed: {}", e);
eprintln!();
std::process::exit(1);
}
}
}
}
fn run_full_reset(assume_yes: bool) -> Result<(), ui::SetupError> {
use dialoguer::{Confirm, theme::ColorfulTheme};
let cfg_path = config_file::user_config_path().map_err(ui::SetupError::Other)?;
let sentinel = cfg_path.with_file_name(".first-run-hint-shown");
let shell_kind = shell::detect_shell();
println!();
println!(" This will remove:");
println!(" • {}", cfg_path.display());
println!(" • {}", sentinel.display());
if shell_kind != shell::Shell::Unknown {
println!(" • any `# CRW Configuration` blocks in your shell rc");
}
println!();
if !assume_yes {
let confirm = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Proceed?")
.default(false)
.interact()
.map_err(ui::handle_dialoguer_error)?;
if !confirm {
return Err(ui::SetupError::Cancelled);
}
}
let mut removed: Vec<String> = Vec::new();
if cfg_path.exists() {
std::fs::remove_file(&cfg_path)
.map_err(|e| ui::SetupError::Other(format!("remove {}: {}", cfg_path.display(), e)))?;
removed.push(cfg_path.display().to_string());
}
if sentinel.exists() {
std::fs::remove_file(&sentinel)
.map_err(|e| ui::SetupError::Other(format!("remove {}: {}", sentinel.display(), e)))?;
removed.push(sentinel.display().to_string());
}
if shell_kind != shell::Shell::Unknown {
match shell::reset_rc(shell_kind) {
Ok(report) if report.lines_removed > 0 => {
removed.push(format!(
"{} ({} line(s))",
report.rc_path.display(),
report.lines_removed
));
}
Ok(_) => {}
Err(e) => return Err(ui::SetupError::Other(e)),
}
}
println!();
if removed.is_empty() {
println!(" Nothing to clean up — setup state was already empty.");
} else {
println!(" Removed:");
for entry in &removed {
println!(" • {}", entry);
}
}
println!();
println!(" Run `crw setup` any time to reconfigure.");
println!();
Ok(())
}
fn run_reset_shell() -> Result<(), String> {
let shell_kind = shell::detect_shell();
if shell_kind == shell::Shell::Unknown {
return Err("Could not detect your shell. Edit your rc file manually.".into());
}
let report = shell::reset_rc(shell_kind)?;
if report.lines_removed == 0 {
println!(
" No CRW Configuration blocks found in {}",
report.rc_path.display()
);
println!(" Nothing to clean up.");
return Ok(());
}
println!(
" Cleaned {} line(s) from {}",
report.lines_removed,
report.rc_path.display()
);
println!(
" Open a new shell or run `source {}` to apply.",
report.rc_path.display()
);
Ok(())
}