Skip to main content

tracevault_cli/hooks/
mod.rs

1use std::path::Path;
2use tracevault_core::streaming::StreamEventRequest;
3
4// Adapter modules contain stubs for future stream command integration.
5#[allow(dead_code)]
6pub mod claude_code;
7#[allow(dead_code)]
8pub mod cursor;
9
10#[allow(dead_code)]
11pub trait HookAdapter: Send + Sync {
12    fn tool_name(&self) -> &str;
13    fn parse_event(&self, raw: &str) -> Result<StreamEventRequest, String>;
14    fn parse_transcript(&self, path: &Path) -> Result<Vec<serde_json::Value>, String>;
15}
16
17#[derive(Debug, Clone, Copy)]
18pub enum DetectedTool {
19    ClaudeCode,
20    Cursor,
21}
22
23impl DetectedTool {
24    pub fn name(&self) -> &str {
25        match self {
26            DetectedTool::ClaudeCode => "claude-code",
27            DetectedTool::Cursor => "cursor",
28        }
29    }
30
31    #[allow(dead_code)]
32    pub fn adapter(&self) -> Box<dyn HookAdapter> {
33        match self {
34            DetectedTool::ClaudeCode => Box::new(claude_code::ClaudeCodeAdapter),
35            DetectedTool::Cursor => Box::new(cursor::CursorAdapter),
36        }
37    }
38}
39
40pub fn detect_tools(cwd: &Path) -> Vec<DetectedTool> {
41    let mut tools = vec![];
42    if cwd.join(".claude").exists() {
43        tools.push(DetectedTool::ClaudeCode);
44    }
45    if cwd.join(".cursor").exists() {
46        tools.push(DetectedTool::Cursor);
47    }
48    tools
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use std::fs;
55
56    #[test]
57    fn detect_tools_claude_only() {
58        let dir = tempfile::tempdir().unwrap();
59        fs::create_dir(dir.path().join(".claude")).unwrap();
60        let tools = detect_tools(dir.path());
61        assert_eq!(tools.len(), 1);
62        assert!(matches!(tools[0], DetectedTool::ClaudeCode));
63    }
64
65    #[test]
66    fn detect_tools_neither() {
67        let dir = tempfile::tempdir().unwrap();
68        assert!(detect_tools(dir.path()).is_empty());
69    }
70
71    #[test]
72    fn detect_tools_both() {
73        let dir = tempfile::tempdir().unwrap();
74        fs::create_dir(dir.path().join(".claude")).unwrap();
75        fs::create_dir(dir.path().join(".cursor")).unwrap();
76        assert_eq!(detect_tools(dir.path()).len(), 2);
77    }
78}