use crate::error::Result;
use serde_json::{Value, json};
use std::path::PathBuf;
use std::fs;
pub struct HooksManager;
impl HooksManager {
pub fn install_hooks(level: Option<&str>) -> Result<()> {
let hooks_config = Self::get_hooks_config();
match level {
Some("console") | None => {
println!("Add this to your Claude Code settings:");
println!("{}", serde_json::to_string_pretty(&hooks_config)?);
Ok(())
}
Some("user") => {
let settings_path = Self::get_user_settings_path()?;
Self::merge_hooks_into_settings(settings_path, hooks_config)
}
Some("project") => {
let settings_path = Self::get_project_settings_path()?;
Self::merge_hooks_into_settings(settings_path, hooks_config)
}
_ => {
println!("Invalid level. Use: user, project, or console");
Ok(())
}
}
}
pub fn remove_hooks(level: &str) -> Result<()> {
let settings_path = match level {
"user" => Self::get_user_settings_path()?,
"project" => Self::get_project_settings_path()?,
_ => {
println!("Invalid level. Use: user or project");
return Ok(());
}
};
Self::remove_hooks_from_settings(settings_path)
}
pub fn show_hooks_status() -> Result<()> {
println!("🔧 Git-Warp Claude Code Integration Status");
println!("==========================================");
match Self::get_user_settings_path() {
Ok(path) => {
if path.exists() {
println!("✅ User settings: {}", path.display());
Self::show_hooks_for_path(&path)?;
} else {
println!("❌ User settings: Not found");
}
}
Err(_) => println!("❌ User settings: Unable to locate"),
}
match Self::get_project_settings_path() {
Ok(path) => {
if path.exists() {
println!("✅ Project settings: {}", path.display());
Self::show_hooks_for_path(&path)?;
} else {
println!("❌ Project settings: Not found");
}
}
Err(_) => println!("❌ Project settings: Unable to locate"),
}
println!("\n📖 Integration Guide:");
println!(" warp hooks-install user # Install for all projects");
println!(" warp hooks-install project # Install for current project only");
println!(" warp hooks-install console # Show JSON to copy manually");
Ok(())
}
fn get_hooks_config() -> Value {
json!({
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) && mkdir -p \"$ROOT/.claude/git-warp\" && echo \"{\\\"status\\\":\\\"processing\\\",\\\"last_activity\\\":\\\"$(date -Iseconds)\\\"}\" > \"$ROOT/.claude/git-warp/status\""
}],
"git_warp_hook_id": "agent_status_userpromptsubmit"
}],
"Stop": [{
"hooks": [{
"type": "command",
"command": "ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) && mkdir -p \"$ROOT/.claude/git-warp\" && echo \"{\\\"status\\\":\\\"waiting\\\",\\\"last_activity\\\":\\\"$(date -Iseconds)\\\"}\" > \"$ROOT/.claude/git-warp/status\""
}],
"git_warp_hook_id": "agent_status_stop"
}],
"PreToolUse": [{
"hooks": [{
"type": "command",
"command": "ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) && mkdir -p \"$ROOT/.claude/git-warp\" && echo \"{\\\"status\\\":\\\"working\\\",\\\"last_activity\\\":\\\"$(date -Iseconds)\\\"}\" > \"$ROOT/.claude/git-warp/status\""
}],
"git_warp_hook_id": "agent_status_pretooluse"
}],
"PostToolUse": [{
"hooks": [{
"type": "command",
"command": "ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) && mkdir -p \"$ROOT/.claude/git-warp\" && echo \"{\\\"status\\\":\\\"processing\\\",\\\"last_activity\\\":\\\"$(date -Iseconds)\\\"}\" > \"$ROOT/.claude/git-warp/status\""
}],
"git_warp_hook_id": "agent_status_posttooluse"
}],
"SubagentStop": [{
"hooks": [{
"type": "command",
"command": "ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) && mkdir -p \"$ROOT/.claude/git-warp\" && echo \"{\\\"status\\\":\\\"subagent_complete\\\",\\\"last_activity\\\":\\\"$(date -Iseconds)\\\"}\" > \"$ROOT/.claude/git-warp/status\""
}],
"git_warp_hook_id": "agent_status_subagent_stop"
}]
}
})
}
fn get_user_settings_path() -> Result<PathBuf> {
let home = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
Ok(home.join(".claude").join("settings.json"))
}
fn get_project_settings_path() -> Result<PathBuf> {
let current_dir = std::env::current_dir()?;
Ok(current_dir.join(".claude").join("settings.json"))
}
fn merge_hooks_into_settings(settings_path: PathBuf, hooks_config: Value) -> Result<()> {
if let Some(parent) = settings_path.parent() {
fs::create_dir_all(parent)?;
}
let mut settings: Value = if settings_path.exists() {
let content = fs::read_to_string(&settings_path)?;
serde_json::from_str(&content)?
} else {
json!({})
};
if let Some(hooks) = hooks_config.get("hooks") {
settings["hooks"] = hooks.clone();
}
let content = serde_json::to_string_pretty(&settings)?;
fs::write(&settings_path, content)?;
println!("Hooks installed to: {}", settings_path.display());
Ok(())
}
fn remove_hooks_from_settings(settings_path: PathBuf) -> Result<()> {
if !settings_path.exists() {
println!("Settings file not found: {}", settings_path.display());
return Ok(());
}
let content = fs::read_to_string(&settings_path)?;
let mut settings: Value = serde_json::from_str(&content)?;
if let Some(hooks) = settings.get_mut("hooks") {
if let Some(hooks_obj) = hooks.as_object_mut() {
for (_, hook_array) in hooks_obj.iter_mut() {
if let Some(array) = hook_array.as_array_mut() {
array.retain(|hook| {
!hook.get("git_warp_hook_id")
.and_then(|id| id.as_str())
.unwrap_or("")
.starts_with("agent_status_")
});
}
}
}
}
let content = serde_json::to_string_pretty(&settings)?;
fs::write(&settings_path, content)?;
println!("Hooks removed from: {}", settings_path.display());
Ok(())
}
fn show_hooks_for_path(path: &PathBuf) -> Result<()> {
if path.exists() {
let content = fs::read_to_string(path)?;
let settings: Value = serde_json::from_str(&content)?;
let mut found_hooks = false;
if let Some(hooks) = settings.get("hooks") {
if let Some(hooks_obj) = hooks.as_object() {
for (hook_type, hook_array) in hooks_obj {
if let Some(array) = hook_array.as_array() {
let git_warp_hooks: Vec<_> = array.iter()
.filter(|hook| {
hook.get("git_warp_hook_id")
.and_then(|id| id.as_str())
.unwrap_or("")
.starts_with("agent_status_")
})
.collect();
if !git_warp_hooks.is_empty() {
if !found_hooks {
println!(" ✓ Hooks installed:");
found_hooks = true;
}
println!(" {}: {} git-warp hook(s)", hook_type, git_warp_hooks.len());
}
}
}
}
}
if !found_hooks {
println!(" No git-warp hooks installed");
}
} else {
println!(" No settings file found");
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_hooks_config_generation() {
let config = HooksManager::get_hooks_config();
assert!(config.get("hooks").is_some());
let hooks = &config["hooks"];
assert!(hooks.get("UserPromptSubmit").is_some());
assert!(hooks.get("Stop").is_some());
assert!(hooks.get("PreToolUse").is_some());
assert!(hooks.get("PostToolUse").is_some());
assert!(hooks.get("SubagentStop").is_some());
}
}