feat: implement hooks system for project configuration
- Add HooksEditor component for managing project hooks - Add ProjectSettings component for project-specific configurations - Create hooksManager utility for hook operations - Add hooks type definitions - Update backend commands to support hooks functionality - Integrate hooks into main app, agent execution, and Claude sessions - Update API and utilities to handle hooks data
This commit is contained in:
@@ -2165,7 +2165,7 @@ pub async fn get_recently_modified_files(
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Tracks multiple session messages at once (batch operation)
|
||||
/// Track session messages from the frontend for checkpointing
|
||||
#[tauri::command]
|
||||
pub async fn track_session_messages(
|
||||
state: tauri::State<'_, crate::checkpoint::state::CheckpointState>,
|
||||
@@ -2174,17 +2174,148 @@ pub async fn track_session_messages(
|
||||
project_path: String,
|
||||
messages: Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
let mgr = state
|
||||
log::info!(
|
||||
"Tracking {} messages for session {}",
|
||||
messages.len(),
|
||||
session_id
|
||||
);
|
||||
|
||||
let manager = state
|
||||
.get_or_create_manager(
|
||||
session_id,
|
||||
project_id,
|
||||
std::path::PathBuf::from(project_path),
|
||||
session_id.clone(),
|
||||
project_id.clone(),
|
||||
PathBuf::from(&project_path),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| format!("Failed to get checkpoint manager: {}", e))?;
|
||||
|
||||
for m in messages {
|
||||
mgr.track_message(m).await.map_err(|e| e.to_string())?;
|
||||
for message in messages {
|
||||
manager
|
||||
.track_message(message)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to track message: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets hooks configuration from settings at specified scope
|
||||
#[tauri::command]
|
||||
pub async fn get_hooks_config(scope: String, project_path: Option<String>) -> Result<serde_json::Value, String> {
|
||||
log::info!("Getting hooks config for scope: {}, project: {:?}", scope, project_path);
|
||||
|
||||
let settings_path = match scope.as_str() {
|
||||
"user" => {
|
||||
get_claude_dir()
|
||||
.map_err(|e| e.to_string())?
|
||||
.join("settings.json")
|
||||
},
|
||||
"project" => {
|
||||
let path = project_path.ok_or("Project path required for project scope")?;
|
||||
PathBuf::from(path).join(".claude").join("settings.json")
|
||||
},
|
||||
"local" => {
|
||||
let path = project_path.ok_or("Project path required for local scope")?;
|
||||
PathBuf::from(path).join(".claude").join("settings.local.json")
|
||||
},
|
||||
_ => return Err("Invalid scope".to_string())
|
||||
};
|
||||
|
||||
if !settings_path.exists() {
|
||||
log::info!("Settings file does not exist at {:?}, returning empty hooks", settings_path);
|
||||
return Ok(serde_json::json!({}));
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&settings_path)
|
||||
.map_err(|e| format!("Failed to read settings: {}", e))?;
|
||||
|
||||
let settings: serde_json::Value = serde_json::from_str(&content)
|
||||
.map_err(|e| format!("Failed to parse settings: {}", e))?;
|
||||
|
||||
Ok(settings.get("hooks").cloned().unwrap_or(serde_json::json!({})))
|
||||
}
|
||||
|
||||
/// Updates hooks configuration in settings at specified scope
|
||||
#[tauri::command]
|
||||
pub async fn update_hooks_config(
|
||||
scope: String,
|
||||
hooks: serde_json::Value,
|
||||
project_path: Option<String>
|
||||
) -> Result<String, String> {
|
||||
log::info!("Updating hooks config for scope: {}, project: {:?}", scope, project_path);
|
||||
|
||||
let settings_path = match scope.as_str() {
|
||||
"user" => {
|
||||
get_claude_dir()
|
||||
.map_err(|e| e.to_string())?
|
||||
.join("settings.json")
|
||||
},
|
||||
"project" => {
|
||||
let path = project_path.ok_or("Project path required for project scope")?;
|
||||
let claude_dir = PathBuf::from(path).join(".claude");
|
||||
fs::create_dir_all(&claude_dir)
|
||||
.map_err(|e| format!("Failed to create .claude directory: {}", e))?;
|
||||
claude_dir.join("settings.json")
|
||||
},
|
||||
"local" => {
|
||||
let path = project_path.ok_or("Project path required for local scope")?;
|
||||
let claude_dir = PathBuf::from(path).join(".claude");
|
||||
fs::create_dir_all(&claude_dir)
|
||||
.map_err(|e| format!("Failed to create .claude directory: {}", e))?;
|
||||
claude_dir.join("settings.local.json")
|
||||
},
|
||||
_ => return Err("Invalid scope".to_string())
|
||||
};
|
||||
|
||||
// Read existing settings or create new
|
||||
let mut settings = if settings_path.exists() {
|
||||
let content = fs::read_to_string(&settings_path)
|
||||
.map_err(|e| format!("Failed to read settings: {}", e))?;
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| format!("Failed to parse settings: {}", e))?
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
|
||||
// Update hooks section
|
||||
settings["hooks"] = hooks;
|
||||
|
||||
// Write back with pretty formatting
|
||||
let json_string = serde_json::to_string_pretty(&settings)
|
||||
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
|
||||
|
||||
fs::write(&settings_path, json_string)
|
||||
.map_err(|e| format!("Failed to write settings: {}", e))?;
|
||||
|
||||
Ok("Hooks configuration updated successfully".to_string())
|
||||
}
|
||||
|
||||
/// Validates a hook command by dry-running it
|
||||
#[tauri::command]
|
||||
pub async fn validate_hook_command(command: String) -> Result<serde_json::Value, String> {
|
||||
log::info!("Validating hook command syntax");
|
||||
|
||||
// Validate syntax without executing
|
||||
let mut cmd = std::process::Command::new("bash");
|
||||
cmd.arg("-n") // Syntax check only
|
||||
.arg("-c")
|
||||
.arg(&command);
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
Ok(serde_json::json!({
|
||||
"valid": true,
|
||||
"message": "Command syntax is valid"
|
||||
}))
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
Ok(serde_json::json!({
|
||||
"valid": false,
|
||||
"message": format!("Syntax error: {}", stderr)
|
||||
}))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(format!("Failed to validate command: {}", e))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user