Skip to main content

cc_switch/cli/
main.rs

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