use std::path::Path;
use fairlead_core::config::{Config, HooksTarget};
use super::{
agent_file, git_install, install, installed, lefthook_file, lefthook_manifest, package_runner,
parse, read, stages, wants_push, Agent,
};
const AGENTS: [(Agent, &str, &str, &[&str], &str); 3] = [
(
Agent::Claude,
"Claude Code",
".claude",
&[".claude/settings.json", ".claude/settings.local.json"],
"",
),
(
Agent::Codex,
"Codex",
".codex",
&[".codex/hooks.json"],
" --codex",
),
(
Agent::Gemini,
"Gemini CLI",
".gemini",
&[".gemini/settings.json"],
" --gemini",
),
];
pub struct AgentHooks {
agent: Agent,
pub name: &'static str,
pub install: String,
pub stages: Vec<&'static str>,
}
fn word(stage: &str) -> &'static str {
match stage {
"Bash" | "run_shell_command" => "shell guard",
"PostToolUse" | "AfterTool" => "brief nudge",
"Stop" | "AfterAgent" => "stop gate",
"SessionStart" => "session start",
_ => "edit guard",
}
}
pub fn agents(root: &Path) -> Vec<AgentHooks> {
AGENTS
.iter()
.filter(|(agent, _, dir, _, _)| *agent == Agent::Claude || root.join(dir).is_dir())
.map(|&(agent, name, _, files, flag)| {
let mut stages: Vec<&'static str> = Vec::new();
for rel in files {
let file = root.join(rel);
let found = read(&file)
.ok()
.flatten()
.and_then(|text| parse(&file, &text).ok())
.map(|settings| installed(&settings))
.unwrap_or_default();
for stage in found.iter().map(|s| word(s)) {
if !stages.contains(&stage) {
stages.push(stage);
}
}
}
AgentHooks {
agent,
name,
install: format!("fairlead hooks install{flag}"),
stages,
}
})
.collect()
}
pub fn install_agents(root: &Path, config: &Config) -> Result<Vec<String>, String> {
let git_dir = fairlead_guard::git::git_dir(root).ok_or("not inside a git repository")?;
let name = match config.hooks.claude {
HooksTarget::Shared => "settings.json",
HooksTarget::Local => "settings.local.json",
};
let mut said = Vec::new();
for found in agents(root).iter().filter(|a| a.stages.is_empty()) {
let (file, manifest) = agent_file(root, &git_dir, found.agent, name);
said.extend(install(
&file,
&manifest,
stages(config),
package_runner(root),
found.agent,
)?);
}
Ok(said)
}
pub fn install_git_quietly(root: &Path, config: &Config) -> Result<Vec<String>, String> {
let git_dir = fairlead_guard::git::git_dir(root).ok_or("not inside a git repository")?;
let lefthook = lefthook_file(root);
let manifest = lefthook_manifest(&git_dir, &lefthook);
git_install(
&lefthook,
&manifest,
&git_dir,
package_runner(root),
wants_push(config),
)
}