mod config;
#[cfg(test)]
mod tests;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use config::AGENTS;
fn home_dir() -> Result<PathBuf> {
directories::UserDirs::new()
.map(|u| u.home_dir().to_path_buf())
.context("cannot determine home directory")
}
fn hook_command() -> Result<String> {
let exe = std::env::current_exe()?;
Ok(format!("{} hook", exe.display()))
}
fn tilde(path: &Path, home: &Path) -> String {
match path.strip_prefix(home) {
Ok(rest) => format!("~/{}", rest.display()),
Err(_) => path.display().to_string(),
}
}
fn confirm(name: &str, path: &str) -> Result<bool> {
eprint!("Install Lade hook for {name} in {path}? [y/N]: ");
io::stderr().flush()?;
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
let answer = answer.trim().to_ascii_lowercase();
Ok(answer == "y" || answer == "yes")
}
fn report(results: Vec<String>) {
if results.is_empty() {
return;
}
eprintln!("Agent hooks:");
for result in results {
eprintln!(" {}", result);
}
}
pub fn install(may_prompt: bool) -> Result<()> {
let command = hook_command()?;
let home = home_dir()?;
let mut results = Vec::new();
for agent in AGENTS {
if !agent.home_dir(&home).is_dir() {
continue;
}
let path = agent.config_path(&home);
let existing = fs::read_to_string(&path).unwrap_or_default();
if agent.has_hook(&existing)? {
results.push(format!("{}: hook already present", agent.name()));
continue;
}
if !may_prompt {
results.push(format!(
"{}: detected — re-run `lade install` in a terminal to add its hook",
agent.name()
));
continue;
}
if confirm(agent.name(), &tilde(&path, &home))? {
fs::write(&path, agent.merge(&existing, &command)?)?;
results.push(format!(
"{}: hook installed in {}",
agent.name(),
tilde(&path, &home)
));
} else {
results.push(format!("{}: skipped", agent.name()));
}
}
report(results);
Ok(())
}
pub fn uninstall() -> Result<()> {
let home = home_dir()?;
let mut results = Vec::new();
for agent in AGENTS {
let path = agent.config_path(&home);
let existing = match fs::read_to_string(&path) {
Ok(content) => content,
Err(_) => continue,
};
if !agent.has_hook(&existing)? {
continue;
}
fs::write(&path, agent.remove(&existing)?)?;
results.push(format!(
"{}: hook removed from {}",
agent.name(),
tilde(&path, &home)
));
}
report(results);
Ok(())
}