Skip to main content

cc_switch/cli/
main.rs

1use crate::cli::completion::{generate_completion, list_aliases_for_completion};
2use crate::cli::{Cli, Commands};
3use crate::config::types::{AddCommandParams, ClaudeSettings, StorageMode};
4use crate::config::{ConfigStorage, Configuration, EnvironmentConfig, validate_alias_name};
5use crate::interactive::{
6    handle_interactive_selection, launch_claude_with_env, read_input, read_sensitive_input,
7};
8use anyhow::{Result, anyhow};
9use clap::Parser;
10use std::fs;
11use std::path::Path;
12
13/// Parse storage mode string to StorageMode enum
14///
15/// # Arguments
16/// * `store_str` - String representation of storage mode ("env" or "config")
17///
18/// # Returns
19/// Result containing StorageMode or error if invalid
20fn parse_storage_mode(store_str: &str) -> Result<StorageMode> {
21    match store_str.to_lowercase().as_str() {
22        "env" => Ok(StorageMode::Env),
23        "config" => Ok(StorageMode::Config),
24        _ => Err(anyhow!(
25            "Invalid storage mode '{}'. Use 'env' or 'config'",
26            store_str
27        )),
28    }
29}
30
31/// Parse a configuration from a JSON file
32///
33/// # Arguments
34/// * `file_path` - Path to the JSON configuration file
35///
36/// # Returns
37/// Result containing a tuple of configuration values
38///
39/// # Errors
40/// Returns error if file cannot be read or parsed
41#[allow(clippy::type_complexity)]
42fn parse_config_from_file(
43    file_path: &str,
44) -> Result<(
45    String,
46    String,
47    String,
48    Option<String>,
49    Option<String>,
50    Option<u32>,
51    Option<u32>,
52    Option<u32>,
53    Option<String>,
54    Option<String>,
55    Option<String>,
56    Option<String>,
57    Option<u32>,
58    Option<String>,
59)> {
60    // Read the file
61    let file_content = fs::read_to_string(file_path)
62        .map_err(|e| anyhow!("Failed to read file '{}': {}", file_path, e))?;
63
64    // Parse JSON
65    let json: serde_json::Value = serde_json::from_str(&file_content)
66        .map_err(|e| anyhow!("Failed to parse JSON from file '{}': {}", file_path, e))?;
67
68    // Extract env section
69    let env = json.get("env").and_then(|v| v.as_object()).ok_or_else(|| {
70        anyhow!(
71            "File '{}' does not contain a valid 'env' section",
72            file_path
73        )
74    })?;
75
76    // Extract alias name from filename (without extension)
77    let path = Path::new(file_path);
78    let alias_name = path
79        .file_stem()
80        .and_then(|s| s.to_str())
81        .ok_or_else(|| anyhow!("Invalid file path: {}", file_path))?
82        .to_string();
83
84    // Extract and map environment variables
85    let token = env
86        .get("ANTHROPIC_AUTH_TOKEN")
87        .and_then(|v| v.as_str())
88        .ok_or_else(|| anyhow!("Missing ANTHROPIC_AUTH_TOKEN in file '{}'", file_path))?
89        .to_string();
90
91    let url = env
92        .get("ANTHROPIC_BASE_URL")
93        .and_then(|v| v.as_str())
94        .ok_or_else(|| anyhow!("Missing ANTHROPIC_BASE_URL in file '{}'", file_path))?
95        .to_string();
96
97    let model = env
98        .get("ANTHROPIC_MODEL")
99        .and_then(|v| v.as_str())
100        .map(|s| s.to_string());
101
102    let small_fast_model = env
103        .get("ANTHROPIC_SMALL_FAST_MODEL")
104        .and_then(|v| v.as_str())
105        .map(|s| s.to_string());
106
107    let max_thinking_tokens = env
108        .get("ANTHROPIC_MAX_THINKING_TOKENS")
109        .and_then(|v| v.as_u64())
110        .map(|u| u as u32);
111
112    let api_timeout_ms = env
113        .get("API_TIMEOUT_MS")
114        .and_then(|v| v.as_u64())
115        .map(|u| u as u32);
116
117    let claude_code_disable_nonessential_traffic = env
118        .get("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC")
119        .and_then(|v| v.as_u64())
120        .map(|u| u as u32);
121
122    let anthropic_default_sonnet_model = env
123        .get("ANTHROPIC_DEFAULT_SONNET_MODEL")
124        .and_then(|v| v.as_str())
125        .map(|s| s.to_string());
126
127    let anthropic_default_opus_model = env
128        .get("ANTHROPIC_DEFAULT_OPUS_MODEL")
129        .and_then(|v| v.as_str())
130        .map(|s| s.to_string());
131
132    let anthropic_default_haiku_model = env
133        .get("ANTHROPIC_DEFAULT_HAIKU_MODEL")
134        .and_then(|v| v.as_str())
135        .map(|s| s.to_string());
136
137    let claude_code_subagent_model = env
138        .get("CLAUDE_CODE_SUBAGENT_MODEL")
139        .and_then(|v| v.as_str())
140        .map(|s| s.to_string());
141
142    let claude_code_disable_nonstreaming_fallback = env
143        .get("CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK")
144        .and_then(|v| v.as_u64())
145        .map(|u| u as u32);
146
147    let claude_code_effort_level = env
148        .get("CLAUDE_CODE_EFFORT_LEVEL")
149        .and_then(|v| v.as_str())
150        .map(|s| s.to_string());
151
152    Ok((
153        alias_name,
154        token,
155        url,
156        model,
157        small_fast_model,
158        max_thinking_tokens,
159        api_timeout_ms,
160        claude_code_disable_nonessential_traffic,
161        anthropic_default_sonnet_model,
162        anthropic_default_opus_model,
163        anthropic_default_haiku_model,
164        claude_code_subagent_model,
165        claude_code_disable_nonstreaming_fallback,
166        claude_code_effort_level,
167    ))
168}
169
170/// Handle adding a configuration with all the new features
171///
172/// # Arguments
173/// * `params` - Parameters for the add command
174/// * `storage` - Mutable reference to config storage
175///
176/// # Errors
177/// Returns error if validation fails or user cancels interactive input
178fn handle_add_command(mut params: AddCommandParams, storage: &mut ConfigStorage) -> Result<()> {
179    // If from-file is provided, parse the file and use those values
180    if let Some(file_path) = &params.from_file {
181        println!("Importing configuration from file: {}", file_path);
182
183        let (
184            file_alias_name,
185            file_token,
186            file_url,
187            file_model,
188            file_small_fast_model,
189            file_max_thinking_tokens,
190            file_api_timeout_ms,
191            file_claude_disable_nonessential_traffic,
192            file_sonnet_model,
193            file_opus_model,
194            file_haiku_model,
195            file_subagent_model,
196            file_disable_nonstreaming_fallback,
197            file_effort_level,
198        ) = parse_config_from_file(file_path)?;
199
200        // Use the file's alias name (ignoring the one provided via command line)
201        params.alias_name = file_alias_name;
202
203        // Override params with file values
204        params.token = Some(file_token);
205        params.url = Some(file_url);
206        params.model = file_model;
207        params.small_fast_model = file_small_fast_model;
208        params.max_thinking_tokens = file_max_thinking_tokens;
209        params.api_timeout_ms = file_api_timeout_ms;
210        params.claude_code_disable_nonessential_traffic = file_claude_disable_nonessential_traffic;
211        params.anthropic_default_sonnet_model = file_sonnet_model;
212        params.anthropic_default_opus_model = file_opus_model;
213        params.anthropic_default_haiku_model = file_haiku_model;
214        params.claude_code_subagent_model = file_subagent_model;
215        params.claude_code_disable_nonstreaming_fallback = file_disable_nonstreaming_fallback;
216        params.claude_code_effort_level = file_effort_level;
217
218        println!(
219            "Configuration '{}' will be imported from file",
220            params.alias_name
221        );
222    }
223
224    // Validate alias name
225    validate_alias_name(&params.alias_name)?;
226
227    // Check if alias already exists
228    if storage.get_configuration(&params.alias_name).is_some() && !params.force {
229        eprintln!("Configuration '{}' already exists.", params.alias_name);
230        eprintln!("Use --force to overwrite or choose a different alias name.");
231        return Ok(());
232    }
233
234    // Cannot use interactive mode with --from-file
235    if params.interactive && params.from_file.is_some() {
236        anyhow::bail!("Cannot use --interactive mode with --from-file");
237    }
238
239    // Determine token value
240    let final_token = if params.interactive {
241        if params.token.is_some() || params.token_arg.is_some() {
242            eprintln!(
243                "Warning: Token provided via flags/arguments will be ignored in interactive mode"
244            );
245        }
246        read_sensitive_input("Enter API token (sk-ant-xxx): ")?
247    } else {
248        match (&params.token, &params.token_arg) {
249            (Some(t), _) => t.clone(),
250            (None, Some(t)) => t.clone(),
251            (None, None) => {
252                anyhow::bail!(
253                    "Token is required. Use -t flag, provide as argument, or use interactive mode with -i"
254                );
255            }
256        }
257    };
258
259    // Determine URL value
260    let final_url = if params.interactive {
261        if params.url.is_some() || params.url_arg.is_some() {
262            eprintln!(
263                "Warning: URL provided via flags/arguments will be ignored in interactive mode"
264            );
265        }
266        read_input("Enter API URL (default: https://api.anthropic.com): ")?
267    } else {
268        match (&params.url, &params.url_arg) {
269            (Some(u), _) => u.clone(),
270            (None, Some(u)) => u.clone(),
271            (None, None) => "https://api.anthropic.com".to_string(),
272        }
273    };
274
275    // Use default URL if empty
276    let final_url = if final_url.is_empty() {
277        "https://api.anthropic.com".to_string()
278    } else {
279        final_url
280    };
281
282    // Determine model value
283    let final_model = if params.interactive {
284        if params.model.is_some() {
285            eprintln!("Warning: Model provided via flags will be ignored in interactive mode");
286        }
287        let model_input = read_input("Enter model name (optional, press enter to skip): ")?;
288        if model_input.is_empty() {
289            None
290        } else {
291            Some(model_input)
292        }
293    } else {
294        params.model
295    };
296
297    // Determine small fast model value
298    let final_small_fast_model = if params.interactive {
299        if params.small_fast_model.is_some() {
300            eprintln!(
301                "Warning: Small fast model provided via flags will be ignored in interactive mode"
302            );
303        }
304        let small_model_input =
305            read_input("Enter small fast model name (optional, press enter to skip): ")?;
306        if small_model_input.is_empty() {
307            None
308        } else {
309            Some(small_model_input)
310        }
311    } else {
312        params.small_fast_model
313    };
314
315    // Determine max thinking tokens value
316    let final_max_thinking_tokens = if params.interactive {
317        if params.max_thinking_tokens.is_some() {
318            eprintln!(
319                "Warning: Max thinking tokens provided via flags will be ignored in interactive mode"
320            );
321        }
322        let tokens_input = read_input(
323            "Enter maximum thinking tokens (optional, press enter to skip, enter 0 to clear): ",
324        )?;
325        if tokens_input.is_empty() {
326            None
327        } else if let Ok(tokens) = tokens_input.parse::<u32>() {
328            if tokens == 0 { None } else { Some(tokens) }
329        } else {
330            eprintln!("Warning: Invalid max thinking tokens value, skipping");
331            None
332        }
333    } else {
334        params.max_thinking_tokens
335    };
336
337    // Determine API timeout value
338    let final_api_timeout_ms = if params.interactive {
339        if params.api_timeout_ms.is_some() {
340            eprintln!(
341                "Warning: API timeout provided via flags will be ignored in interactive mode"
342            );
343        }
344        let timeout_input = read_input(
345            "Enter API timeout in milliseconds (optional, press enter to skip, enter 0 to clear): ",
346        )?;
347        if timeout_input.is_empty() {
348            None
349        } else if let Ok(timeout) = timeout_input.parse::<u32>() {
350            if timeout == 0 { None } else { Some(timeout) }
351        } else {
352            eprintln!("Warning: Invalid API timeout value, skipping");
353            None
354        }
355    } else {
356        params.api_timeout_ms
357    };
358
359    // Determine disable nonessential traffic flag value
360    let final_claude_code_disable_nonessential_traffic = if params.interactive {
361        if params.claude_code_disable_nonessential_traffic.is_some() {
362            eprintln!(
363                "Warning: Disable nonessential traffic flag provided via flags will be ignored in interactive mode"
364            );
365        }
366        let flag_input = read_input(
367            "Enter disable nonessential traffic flag (optional, press enter to skip, enter 0 to clear): ",
368        )?;
369        if flag_input.is_empty() {
370            None
371        } else if let Ok(flag) = flag_input.parse::<u32>() {
372            if flag == 0 { None } else { Some(flag) }
373        } else {
374            eprintln!("Warning: Invalid disable nonessential traffic flag value, skipping");
375            None
376        }
377    } else {
378        params.claude_code_disable_nonessential_traffic
379    };
380
381    // Determine default Sonnet model value
382    let final_anthropic_default_sonnet_model = if params.interactive {
383        if params.anthropic_default_sonnet_model.is_some() {
384            eprintln!(
385                "Warning: Default Sonnet model provided via flags will be ignored in interactive mode"
386            );
387        }
388        let model_input =
389            read_input("Enter default Sonnet model name (optional, press enter to skip): ")?;
390        if model_input.is_empty() {
391            None
392        } else {
393            Some(model_input)
394        }
395    } else {
396        params.anthropic_default_sonnet_model
397    };
398
399    // Determine default Opus model value
400    let final_anthropic_default_opus_model = if params.interactive {
401        if params.anthropic_default_opus_model.is_some() {
402            eprintln!(
403                "Warning: Default Opus model provided via flags will be ignored in interactive mode"
404            );
405        }
406        let model_input =
407            read_input("Enter default Opus model name (optional, press enter to skip): ")?;
408        if model_input.is_empty() {
409            None
410        } else {
411            Some(model_input)
412        }
413    } else {
414        params.anthropic_default_opus_model
415    };
416
417    // Determine default Haiku model value
418    let final_anthropic_default_haiku_model = if params.interactive {
419        if params.anthropic_default_haiku_model.is_some() {
420            eprintln!(
421                "Warning: Default Haiku model provided via flags will be ignored in interactive mode"
422            );
423        }
424        let model_input =
425            read_input("Enter default Haiku model name (optional, press enter to skip): ")?;
426        if model_input.is_empty() {
427            None
428        } else {
429            Some(model_input)
430        }
431    } else {
432        params.anthropic_default_haiku_model
433    };
434
435    // Determine subagent model value
436    let final_claude_code_subagent_model = if params.interactive {
437        if params.claude_code_subagent_model.is_some() {
438            eprintln!(
439                "Warning: Subagent model provided via flags will be ignored in interactive mode"
440            );
441        }
442        let model_input =
443            read_input("Enter subagent model name (optional, press enter to skip): ")?;
444        if model_input.is_empty() {
445            None
446        } else {
447            Some(model_input)
448        }
449    } else {
450        params.claude_code_subagent_model
451    };
452
453    // Determine disable non-streaming fallback flag value
454    let final_claude_code_disable_nonstreaming_fallback = if params.interactive {
455        if params.claude_code_disable_nonstreaming_fallback.is_some() {
456            eprintln!(
457                "Warning: Disable non-streaming fallback flag provided via flags will be ignored in interactive mode"
458            );
459        }
460        let flag_input = read_input(
461            "Enter disable non-streaming fallback flag (optional, press enter to skip, enter 0 to clear): ",
462        )?;
463        if flag_input.is_empty() {
464            None
465        } else if let Ok(flag) = flag_input.parse::<u32>() {
466            if flag == 0 { None } else { Some(flag) }
467        } else {
468            eprintln!("Warning: Invalid disable non-streaming fallback flag value, skipping");
469            None
470        }
471    } else {
472        params.claude_code_disable_nonstreaming_fallback
473    };
474
475    // Determine effort level value
476    let final_claude_code_effort_level = if params.interactive {
477        if params.claude_code_effort_level.is_some() {
478            eprintln!(
479                "Warning: Effort level provided via flags will be ignored in interactive mode"
480            );
481        }
482        let level_input = read_input(
483            "Enter effort level (optional, press enter to skip): ",
484        )?;
485        if level_input.is_empty() {
486            None
487        } else {
488            Some(level_input)
489        }
490    } else {
491        params.claude_code_effort_level
492    };
493
494    // Validate token format with flexible API provider support
495    let is_anthropic_official = final_url.contains("api.anthropic.com");
496    if is_anthropic_official {
497        if !final_token.starts_with("sk-ant-api03-") {
498            eprintln!(
499                "Warning: For official Anthropic API (api.anthropic.com), token should start with 'sk-ant-api03-'"
500            );
501        }
502    } else {
503        // For non-official APIs, provide general guidance
504        if final_token.starts_with("sk-ant-api03-") {
505            eprintln!("Warning: Using official Claude token format with non-official API endpoint");
506        }
507        // Don't validate format for third-party APIs as they may use different formats
508    }
509
510    // Create and add configuration
511    let config = Configuration {
512        alias_name: params.alias_name.clone(),
513        token: final_token,
514        url: final_url,
515        model: final_model,
516        small_fast_model: final_small_fast_model,
517        max_thinking_tokens: final_max_thinking_tokens,
518        api_timeout_ms: final_api_timeout_ms,
519        claude_code_disable_nonessential_traffic: final_claude_code_disable_nonessential_traffic,
520        anthropic_default_sonnet_model: final_anthropic_default_sonnet_model,
521        anthropic_default_opus_model: final_anthropic_default_opus_model,
522        anthropic_default_haiku_model: final_anthropic_default_haiku_model,
523        claude_code_subagent_model: final_claude_code_subagent_model,
524        claude_code_disable_nonstreaming_fallback: final_claude_code_disable_nonstreaming_fallback,
525        claude_code_effort_level: final_claude_code_effort_level,
526        claude_code_experimental_agent_teams: None,
527        claude_code_disable_1m_context: None,
528    };
529
530    storage.add_configuration(config);
531    storage.save()?;
532
533    println!("Configuration '{}' added successfully", params.alias_name);
534    if params.force {
535        println!("(Overwrote existing configuration)");
536    }
537
538    Ok(())
539}
540
541/// Main entry point for the CLI application
542///
543/// Parses command-line arguments and executes the appropriate action:
544/// - Switch configuration with `-c` flag
545/// - Execute subcommands (add, remove, list)
546/// - Show help if no arguments provided
547///
548/// # Errors
549/// Returns error if any operation fails (file I/O, parsing, etc.)
550pub fn run() -> Result<()> {
551    let cli = Cli::parse();
552
553    // Handle --migrate flag: migrate old path to new path and exit
554    if cli.migrate {
555        ConfigStorage::migrate_from_old_path()?;
556        return Ok(());
557    }
558
559    // Handle --list-aliases flag for completion
560    if cli.list_aliases {
561        list_aliases_for_completion()?;
562        return Ok(());
563    }
564
565    // Handle --store flag: set default storage mode and exit
566    if let Some(ref store_str) = cli.store
567        && cli.command.is_none()
568    {
569        // No command provided, so --store is a setter
570        let mode = match parse_storage_mode(store_str) {
571            Ok(mode) => mode,
572            Err(e) => {
573                eprintln!("Error: {}", e);
574                std::process::exit(1);
575            }
576        };
577
578        let mut storage = ConfigStorage::load()?;
579        storage.default_storage_mode = Some(mode.clone());
580        storage.save()?;
581
582        let mode_str = match mode {
583            StorageMode::Env => "env",
584            StorageMode::Config => "config",
585        };
586
587        println!("Default storage mode set to: {}", mode_str);
588        return Ok(());
589    }
590
591    // Handle subcommands
592    if let Some(command) = cli.command {
593        let mut storage = ConfigStorage::load()?;
594
595        match command {
596            Commands::Add {
597                alias_name,
598                token,
599                url,
600                model,
601                small_fast_model,
602                max_thinking_tokens,
603                api_timeout_ms,
604                claude_code_disable_nonessential_traffic,
605                anthropic_default_sonnet_model,
606                anthropic_default_opus_model,
607                anthropic_default_haiku_model,
608                claude_code_subagent_model,
609                claude_code_disable_nonstreaming_fallback,
610                claude_code_effort_level,
611                force,
612                interactive,
613                token_arg,
614                url_arg,
615                from_file,
616            } => {
617                // When from_file is provided, alias_name will be extracted from the file
618                // For other cases, use the provided alias_name or provide a default
619                let final_alias_name = if from_file.is_some() {
620                    // Will be set from file parsing, use a placeholder for now
621                    "placeholder".to_string()
622                } else {
623                    alias_name.unwrap_or_else(|| {
624                        eprintln!("Error: alias_name is required when not using --from-file");
625                        std::process::exit(1);
626                    })
627                };
628
629                let params = AddCommandParams {
630                    alias_name: final_alias_name,
631                    token,
632                    url,
633                    model,
634                    small_fast_model,
635                    max_thinking_tokens,
636                    api_timeout_ms,
637                    claude_code_disable_nonessential_traffic,
638                    anthropic_default_sonnet_model,
639                    anthropic_default_opus_model,
640                    anthropic_default_haiku_model,
641                    claude_code_subagent_model,
642                    claude_code_disable_nonstreaming_fallback,
643                    claude_code_effort_level,
644                    force,
645                    interactive,
646                    token_arg,
647                    url_arg,
648                    from_file,
649                };
650                handle_add_command(params, &mut storage)?;
651            }
652            Commands::Remove { alias_names } => {
653                let mut removed_count = 0;
654                let mut not_found_aliases = Vec::new();
655
656                for alias_name in &alias_names {
657                    if storage.remove_configuration(alias_name) {
658                        removed_count += 1;
659                        println!("Configuration '{alias_name}' removed successfully");
660                    } else {
661                        not_found_aliases.push(alias_name.clone());
662                        println!("Configuration '{alias_name}' not found");
663                    }
664                }
665
666                if removed_count > 0 {
667                    storage.save()?;
668                }
669
670                if !not_found_aliases.is_empty() {
671                    eprintln!(
672                        "Warning: The following configurations were not found: {}",
673                        not_found_aliases.join(", ")
674                    );
675                }
676
677                if removed_count > 0 {
678                    println!("Successfully removed {removed_count} configuration(s)");
679                }
680            }
681            Commands::List { plain } => {
682                if plain {
683                    // Text output when -p flag is used
684                    if storage.configurations.is_empty() {
685                        println!("No configurations stored");
686                    } else {
687                        println!("Stored configurations:");
688                        for (alias_name, config) in &storage.configurations {
689                            let mut info = format!("token={}, url={}", config.token, config.url);
690                            if let Some(model) = &config.model {
691                                info.push_str(&format!(", model={model}"));
692                            }
693                            if let Some(small_fast_model) = &config.small_fast_model {
694                                info.push_str(&format!(", small_fast_model={small_fast_model}"));
695                            }
696                            if let Some(max_thinking_tokens) = config.max_thinking_tokens {
697                                info.push_str(&format!(
698                                    ", max_thinking_tokens={max_thinking_tokens}"
699                                ));
700                            }
701                            if let Some(subagent_model) = &config.claude_code_subagent_model {
702                                info.push_str(&format!(", subagent_model={subagent_model}"));
703                            }
704                            if let Some(flag) = config.claude_code_disable_nonstreaming_fallback {
705                                info.push_str(&format!(
706                                    ", disable_nonstreaming_fallback={flag}"
707                                ));
708                            }
709                            if let Some(effort_level) = &config.claude_code_effort_level {
710                                info.push_str(&format!(", effort_level={effort_level}"));
711                            }
712                            println!("  {alias_name}: {info}");
713                        }
714                    }
715                } else {
716                    // JSON output (default)
717                    println!(
718                        "{}",
719                        serde_json::to_string_pretty(&storage.configurations)
720                            .map_err(|e| anyhow!("Failed to serialize configurations: {}", e))?
721                    );
722                }
723            }
724            Commands::Completion { shell } => {
725                generate_completion(&shell)?;
726            }
727            Commands::Use {
728                alias_name,
729                resume,
730                r#continue,
731                prompt,
732            } => {
733                // Handle special reset aliases
734                if alias_name == "cc" || alias_name == "official" {
735                    println!("Using official Claude configuration");
736
737                    let mut settings =
738                        ClaudeSettings::load(storage.get_claude_settings_dir().map(|s| s.as_str()))?;
739                    settings.remove_anthropic_env();
740                    settings.save(storage.get_claude_settings_dir().map(|s| s.as_str()))?;
741
742                    launch_claude_with_env(
743                        EnvironmentConfig::empty(),
744                        None,
745                        None,
746                        r#continue,
747                    )?;
748                    return Ok(());
749                }
750
751                let config = storage
752                    .configurations
753                    .get(&alias_name)
754                    .ok_or_else(|| anyhow!("Configuration '{}' not found", alias_name))?
755                    .clone();
756
757                let env_config = EnvironmentConfig::from_config(&config);
758                let storage_mode = storage.default_storage_mode.clone().unwrap_or_default();
759
760                // Update settings.json with the configuration
761                let mut settings =
762                    ClaudeSettings::load(storage.get_claude_settings_dir().map(|s| s.as_str()))?;
763                settings.switch_to_config_with_mode(
764                    &config,
765                    storage_mode,
766                    storage.get_claude_settings_dir().map(|s| s.as_str()),
767                )?;
768
769                println!("Switched to configuration '{}'", alias_name);
770                println!("  URL:   {}", config.url);
771                println!(
772                    "  Token: {}",
773                    crate::cli::display_utils::format_token_for_display(&config.token)
774                );
775
776                let prompt_str = if prompt.is_empty() {
777                    None
778                } else {
779                    Some(prompt.join(" "))
780                };
781
782                launch_claude_with_env(
783                    env_config,
784                    prompt_str.as_deref(),
785                    resume.as_deref(),
786                    r#continue,
787                )?;
788            }
789        }
790    } else {
791        // No command provided, show interactive configuration selection
792        let storage = ConfigStorage::load()?;
793        handle_interactive_selection(&storage)?;
794    }
795
796    Ok(())
797}