|  | 
|  | 1 | +use std::env; | 
|  | 2 | +use tracing::debug; | 
|  | 3 | + | 
|  | 4 | +/// Detects if the current process is being run by Claude Code | 
|  | 5 | +fn is_claude_ai() -> bool { | 
|  | 6 | +    match env::var("CLAUDECODE") { | 
|  | 7 | +        Ok(_) => { | 
|  | 8 | +            debug!("Claude AI detected via CLAUDECODE environment variable"); | 
|  | 9 | +            true | 
|  | 10 | +        } | 
|  | 11 | +        Err(_) => false, | 
|  | 12 | +    } | 
|  | 13 | +} | 
|  | 14 | + | 
|  | 15 | +/// Detects if the current process is being run by Repl.it | 
|  | 16 | +fn is_replit_ai() -> bool { | 
|  | 17 | +    match env::var("REPL_ID") { | 
|  | 18 | +        Ok(_) => { | 
|  | 19 | +            debug!("Repl.it AI detected via REPL_ID environment variable"); | 
|  | 20 | +            true | 
|  | 21 | +        } | 
|  | 22 | +        Err(_) => false, | 
|  | 23 | +    } | 
|  | 24 | +} | 
|  | 25 | + | 
|  | 26 | +/// Detects if the current process is being run by Cursor AI | 
|  | 27 | +fn is_cursor_ai() -> bool { | 
|  | 28 | +    let pager_matches = env::var("PAGER") | 
|  | 29 | +        .map(|v| v == "head -n 10000 | cat") | 
|  | 30 | +        .unwrap_or(false); | 
|  | 31 | +    let has_cursor_trace_id = env::var("CURSOR_TRACE_ID").is_ok(); | 
|  | 32 | +    let has_composer_no_interaction = env::var("COMPOSER_NO_INTERACTION").is_ok(); | 
|  | 33 | + | 
|  | 34 | +    let is_cursor = pager_matches && has_cursor_trace_id && has_composer_no_interaction; | 
|  | 35 | + | 
|  | 36 | +    if is_cursor { | 
|  | 37 | +        debug!( | 
|  | 38 | +            "Cursor AI detected via PAGER, CURSOR_TRACE_ID, and COMPOSER_NO_INTERACTION environment variables" | 
|  | 39 | +        ); | 
|  | 40 | +    } | 
|  | 41 | + | 
|  | 42 | +    is_cursor | 
|  | 43 | +} | 
|  | 44 | + | 
|  | 45 | +/// Detects if the current process is being run by an AI agent | 
|  | 46 | +#[napi] | 
|  | 47 | +pub fn is_ai_agent() -> bool { | 
|  | 48 | +    let is_ai = is_claude_ai() || is_replit_ai() || is_cursor_ai(); | 
|  | 49 | + | 
|  | 50 | +    if is_ai { | 
|  | 51 | +        debug!("AI agent detected"); | 
|  | 52 | +    } | 
|  | 53 | + | 
|  | 54 | +    is_ai | 
|  | 55 | +} | 
|  | 56 | + | 
|  | 57 | +#[cfg(test)] | 
|  | 58 | +mod tests { | 
|  | 59 | +    use super::*; | 
|  | 60 | + | 
|  | 61 | +    fn clear_ai_env_vars() { | 
|  | 62 | +        let ai_vars = [ | 
|  | 63 | +            "CLAUDECODE", | 
|  | 64 | +            "REPL_ID", | 
|  | 65 | +            "PAGER", | 
|  | 66 | +            "CURSOR_TRACE_ID", | 
|  | 67 | +            "COMPOSER_NO_INTERACTION", | 
|  | 68 | +        ]; | 
|  | 69 | +        for var in &ai_vars { | 
|  | 70 | +            unsafe { | 
|  | 71 | +                std::env::remove_var(var); | 
|  | 72 | +            } | 
|  | 73 | +        } | 
|  | 74 | +    } | 
|  | 75 | + | 
|  | 76 | +    #[test] | 
|  | 77 | +    fn test_ai_agent_detection() { | 
|  | 78 | +        // Save original environment state | 
|  | 79 | +        let original_claudecode = env::var("CLAUDECODE").ok(); | 
|  | 80 | +        let original_repl_id = env::var("REPL_ID").ok(); | 
|  | 81 | +        let original_pager = env::var("PAGER").ok(); | 
|  | 82 | +        let original_cursor_trace_id = env::var("CURSOR_TRACE_ID").ok(); | 
|  | 83 | +        let original_composer_no_interaction = env::var("COMPOSER_NO_INTERACTION").ok(); | 
|  | 84 | + | 
|  | 85 | +        // Start with clean environment | 
|  | 86 | +        clear_ai_env_vars(); | 
|  | 87 | + | 
|  | 88 | +        // Test no AI detection | 
|  | 89 | +        assert!( | 
|  | 90 | +            !is_claude_ai(), | 
|  | 91 | +            "Should not detect Claude AI without CLAUDECODE" | 
|  | 92 | +        ); | 
|  | 93 | +        assert!( | 
|  | 94 | +            !is_replit_ai(), | 
|  | 95 | +            "Should not detect Repl.it AI without REPL_ID" | 
|  | 96 | +        ); | 
|  | 97 | +        assert!( | 
|  | 98 | +            !is_cursor_ai(), | 
|  | 99 | +            "Should not detect Cursor AI without all variables" | 
|  | 100 | +        ); | 
|  | 101 | +        assert!(!is_ai_agent(), "Should not detect any AI agent"); | 
|  | 102 | + | 
|  | 103 | +        // Test Claude AI detection | 
|  | 104 | +        unsafe { | 
|  | 105 | +            env::set_var("CLAUDECODE", "1"); | 
|  | 106 | +        } | 
|  | 107 | +        assert!(is_claude_ai(), "Should detect Claude AI with CLAUDECODE"); | 
|  | 108 | +        assert!(is_ai_agent(), "Main function should detect Claude AI"); | 
|  | 109 | +        unsafe { | 
|  | 110 | +            env::remove_var("CLAUDECODE"); | 
|  | 111 | +        } | 
|  | 112 | + | 
|  | 113 | +        // Test Repl.it AI detection | 
|  | 114 | +        unsafe { | 
|  | 115 | +            env::set_var("REPL_ID", "some-repl-id"); | 
|  | 116 | +        } | 
|  | 117 | +        assert!(is_replit_ai(), "Should detect Repl.it AI with REPL_ID"); | 
|  | 118 | +        assert!(is_ai_agent(), "Main function should detect Repl.it AI"); | 
|  | 119 | +        unsafe { | 
|  | 120 | +            env::remove_var("REPL_ID"); | 
|  | 121 | +        } | 
|  | 122 | + | 
|  | 123 | +        // Test Cursor AI detection with wrong PAGER | 
|  | 124 | +        unsafe { | 
|  | 125 | +            env::set_var("PAGER", "wrong-value"); | 
|  | 126 | +            env::set_var("CURSOR_TRACE_ID", "trace-123"); | 
|  | 127 | +            env::set_var("COMPOSER_NO_INTERACTION", "1"); | 
|  | 128 | +        } | 
|  | 129 | +        assert!( | 
|  | 130 | +            !is_cursor_ai(), | 
|  | 131 | +            "Should not detect Cursor AI with wrong PAGER value" | 
|  | 132 | +        ); | 
|  | 133 | + | 
|  | 134 | +        // Test Cursor AI detection with correct PAGER | 
|  | 135 | +        unsafe { | 
|  | 136 | +            env::set_var("PAGER", "head -n 10000 | cat"); | 
|  | 137 | +        } | 
|  | 138 | +        assert!( | 
|  | 139 | +            is_cursor_ai(), | 
|  | 140 | +            "Should detect Cursor AI with correct PAGER and all variables" | 
|  | 141 | +        ); | 
|  | 142 | +        assert!(is_ai_agent(), "Main function should detect Cursor AI"); | 
|  | 143 | +        clear_ai_env_vars(); | 
|  | 144 | + | 
|  | 145 | +        // Test multiple AI agents | 
|  | 146 | +        unsafe { | 
|  | 147 | +            env::set_var("CLAUDECODE", "1"); | 
|  | 148 | +            env::set_var("REPL_ID", "some-repl-id"); | 
|  | 149 | +        } | 
|  | 150 | +        assert!( | 
|  | 151 | +            is_ai_agent(), | 
|  | 152 | +            "Should detect AI when multiple agents are present" | 
|  | 153 | +        ); | 
|  | 154 | + | 
|  | 155 | +        // Restore original environment | 
|  | 156 | +        clear_ai_env_vars(); | 
|  | 157 | +        if let Some(val) = original_claudecode { | 
|  | 158 | +            unsafe { | 
|  | 159 | +                env::set_var("CLAUDECODE", val); | 
|  | 160 | +            } | 
|  | 161 | +        } | 
|  | 162 | +        if let Some(val) = original_repl_id { | 
|  | 163 | +            unsafe { | 
|  | 164 | +                env::set_var("REPL_ID", val); | 
|  | 165 | +            } | 
|  | 166 | +        } | 
|  | 167 | +        if let Some(val) = original_pager { | 
|  | 168 | +            unsafe { | 
|  | 169 | +                env::set_var("PAGER", val); | 
|  | 170 | +            } | 
|  | 171 | +        } | 
|  | 172 | +        if let Some(val) = original_cursor_trace_id { | 
|  | 173 | +            unsafe { | 
|  | 174 | +                env::set_var("CURSOR_TRACE_ID", val); | 
|  | 175 | +            } | 
|  | 176 | +        } | 
|  | 177 | +        if let Some(val) = original_composer_no_interaction { | 
|  | 178 | +            unsafe { | 
|  | 179 | +                env::set_var("COMPOSER_NO_INTERACTION", val); | 
|  | 180 | +            } | 
|  | 181 | +        } | 
|  | 182 | +    } | 
|  | 183 | +} | 
0 commit comments