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