Skip to main content

cc_audit/run/
client.rs

1//! Client detection and scan mode handling.
2
3use crate::{Cli, ClientType, detect_client, detect_installed_clients};
4use std::path::PathBuf;
5
6/// Scan mode based on CLI options.
7#[derive(Debug, Clone)]
8pub enum ScanMode {
9    /// Scan specific paths provided via CLI.
10    Paths(Vec<PathBuf>),
11    /// Scan all installed AI coding clients.
12    AllClients,
13    /// Scan a specific client.
14    SingleClient(ClientType),
15}
16
17impl ScanMode {
18    /// Determine scan mode from CLI options.
19    pub fn from_cli(cli: &Cli) -> Self {
20        if cli.all_clients {
21            ScanMode::AllClients
22        } else if let Some(client) = cli.client {
23            ScanMode::SingleClient(client)
24        } else {
25            ScanMode::Paths(cli.paths.clone())
26        }
27    }
28}
29
30/// Resolve paths to scan based on CLI options.
31pub fn resolve_scan_paths(cli: &Cli) -> Vec<PathBuf> {
32    let mode = ScanMode::from_cli(cli);
33
34    match mode {
35        ScanMode::Paths(paths) => {
36            if paths.is_empty() {
37                // Default to current directory
38                vec![PathBuf::from(".")]
39            } else {
40                paths
41            }
42        }
43        ScanMode::AllClients => {
44            let clients = detect_installed_clients();
45            if clients.is_empty() {
46                eprintln!("No AI coding clients detected on this system.");
47                return Vec::new();
48            }
49
50            let mut paths = Vec::new();
51            for client in &clients {
52                eprintln!(
53                    "Detected {}: {}",
54                    client.client_type.display_name(),
55                    client.home_dir.display()
56                );
57                paths.extend(client.all_configs());
58            }
59            paths
60        }
61        ScanMode::SingleClient(client_type) => match detect_client(client_type) {
62            Some(client) => {
63                eprintln!(
64                    "Scanning {}: {}",
65                    client.client_type.display_name(),
66                    client.home_dir.display()
67                );
68                client.all_configs()
69            }
70            None => {
71                eprintln!(
72                    "{} is not installed or has no configuration files.",
73                    client_type.display_name()
74                );
75                Vec::new()
76            }
77        },
78    }
79}
80
81/// Determine which AI client a file path belongs to.
82pub fn detect_client_for_path(path: &str) -> Option<String> {
83    for client_type in ClientType::all() {
84        if let Some(home) = client_type.home_dir() {
85            let home_str = home.display().to_string();
86            if path.starts_with(&home_str) {
87                return Some(client_type.display_name().to_string());
88            }
89        }
90    }
91    None
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_scan_mode_from_cli_paths() {
100        let cli = crate::Cli {
101            paths: vec![PathBuf::from("/test/path")],
102            all_clients: false,
103            client: None,
104            ..Default::default()
105        };
106        match ScanMode::from_cli(&cli) {
107            ScanMode::Paths(paths) => assert_eq!(paths, vec![PathBuf::from("/test/path")]),
108            _ => panic!("Expected ScanMode::Paths"),
109        }
110    }
111
112    #[test]
113    fn test_scan_mode_from_cli_all_clients() {
114        let cli = crate::Cli {
115            paths: vec![],
116            all_clients: true,
117            client: None,
118            ..Default::default()
119        };
120        assert!(matches!(ScanMode::from_cli(&cli), ScanMode::AllClients));
121    }
122
123    #[test]
124    fn test_scan_mode_from_cli_single_client() {
125        let cli = crate::Cli {
126            paths: vec![],
127            all_clients: false,
128            client: Some(ClientType::Claude),
129            ..Default::default()
130        };
131        match ScanMode::from_cli(&cli) {
132            ScanMode::SingleClient(client) => assert_eq!(client, ClientType::Claude),
133            _ => panic!("Expected ScanMode::SingleClient"),
134        }
135    }
136}