cc_switch/
utils.rs

1use anyhow::{Context, Result};
2use std::io::{self, Write};
3use std::path::PathBuf;
4use std::process::{Command, Stdio};
5use std::thread;
6use std::time::Duration;
7
8/// Get the path to the configuration storage file
9///
10/// Returns `~/.claude/cc_auto_switch_setting.json`
11///
12/// # Errors
13/// Returns error if home directory cannot be found
14pub fn get_config_storage_path() -> Result<PathBuf> {
15    let home_dir = dirs::home_dir().context("Could not find home directory")?;
16    Ok(home_dir.join(".claude").join("cc_auto_switch_setting.json"))
17}
18
19/// Get the path to the Claude settings file
20///
21/// Returns the path to settings.json, using custom directory if configured
22/// Defaults to `~/.claude/settings.json`
23///
24/// # Errors
25/// Returns error if home directory cannot be found or path is invalid
26pub fn get_claude_settings_path(custom_dir: Option<&str>) -> Result<PathBuf> {
27    if let Some(dir) = custom_dir {
28        let custom_path = PathBuf::from(dir);
29        if custom_path.is_absolute() {
30            Ok(custom_path.join("settings.json"))
31        } else {
32            // If relative path, resolve from home directory
33            let home_dir = dirs::home_dir().context("Could not find home directory")?;
34            Ok(home_dir.join(custom_path).join("settings.json"))
35        }
36    } else {
37        // Default path
38        let home_dir = dirs::home_dir().context("Could not find home directory")?;
39        Ok(home_dir.join(".claude").join("settings.json"))
40    }
41}
42
43/// Read input from stdin with a prompt
44///
45/// # Arguments
46/// * `prompt` - The prompt to display to the user
47///
48/// # Returns
49/// The user's input as a String
50pub fn read_input(prompt: &str) -> Result<String> {
51    print!("{prompt}");
52    io::stdout().flush().context("Failed to flush stdout")?;
53    let mut input = String::new();
54    io::stdin()
55        .read_line(&mut input)
56        .context("Failed to read input")?;
57    Ok(input.trim().to_string())
58}
59
60/// Read sensitive input (token) with a prompt (without echoing)
61///
62/// # Arguments
63/// * `prompt` - The prompt to display to the user
64///
65/// # Returns
66/// The user's input as a String
67pub fn read_sensitive_input(prompt: &str) -> Result<String> {
68    print!("{prompt}");
69    io::stdout().flush().context("Failed to flush stdout")?;
70    let mut input = String::new();
71    io::stdin()
72        .read_line(&mut input)
73        .context("Failed to read input")?;
74    Ok(input.trim().to_string())
75}
76
77/// Validate alias name
78///
79/// # Arguments
80/// * `alias_name` - The alias name to validate
81///
82/// # Returns
83/// Ok(()) if valid, Err with message if invalid
84pub fn validate_alias_name(alias_name: &str) -> Result<()> {
85    if alias_name.is_empty() {
86        anyhow::bail!("Alias name cannot be empty");
87    }
88    if alias_name == "cc" {
89        anyhow::bail!("Alias name 'cc' is reserved and cannot be used");
90    }
91    if alias_name.chars().any(|c| c.is_whitespace()) {
92        anyhow::bail!("Alias name cannot contain whitespace");
93    }
94    Ok(())
95}
96
97/// Execute claude command with or without --dangerously-skip-permissions
98///
99/// # Arguments
100/// * `skip_permissions` - Whether to add --dangerously-skip-permissions flag
101pub fn execute_claude_command(skip_permissions: bool) -> Result<()> {
102    let mut command = Command::new("claude");
103    if skip_permissions {
104        command.arg("--dangerously-skip-permissions");
105    }
106
107    command
108        .stdin(Stdio::inherit())
109        .stdout(Stdio::inherit())
110        .stderr(Stdio::inherit());
111
112    let mut child = command.spawn().with_context(
113        || "Failed to launch Claude CLI. Make sure 'claude' command is available in PATH",
114    )?;
115
116    let status = child
117        .wait()
118        .with_context(|| "Failed to wait for Claude CLI process")?;
119
120    if !status.success() {
121        anyhow::bail!("Claude CLI exited with error status: {}", status);
122    }
123
124    Ok(())
125}
126
127/// Launch Claude CLI with proper delay
128pub fn launch_claude() -> Result<()> {
129    println!("\nWaiting 0.5 seconds before launching Claude...");
130    thread::sleep(Duration::from_millis(500));
131
132    println!("Launching Claude CLI...");
133    let mut child = Command::new("claude")
134        .arg("--dangerously-skip-permissions")
135        .stdin(Stdio::inherit())
136        .stdout(Stdio::inherit())
137        .stderr(Stdio::inherit())
138        .spawn()
139        .with_context(
140            || "Failed to launch Claude CLI. Make sure 'claude' command is available in PATH",
141        )?;
142
143    let status = child.wait()?;
144
145    if !status.success() {
146        anyhow::bail!("Claude CLI exited with error status: {}", status);
147    }
148
149    Ok(())
150}