agcodex_cli/
login.rs

1use agcodex_common::CliConfigOverrides;
2use agcodex_core::config::Config;
3use agcodex_core::config::ConfigOverrides;
4use agcodex_login::AuthMode;
5use agcodex_login::CLIENT_ID;
6use agcodex_login::CodexAuth;
7use agcodex_login::OPENAI_API_KEY_ENV_VAR;
8use agcodex_login::ServerOptions;
9use agcodex_login::login_with_api_key;
10use agcodex_login::logout;
11use agcodex_login::run_login_server;
12use std::env;
13use std::path::PathBuf;
14
15pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> {
16    let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string());
17    let server = run_login_server(opts)?;
18
19    eprintln!(
20        "Starting local login server on http://localhost:{}.\nIf your browser did not open, navigate to this URL to authenticate:\n\n{}",
21        server.actual_port, server.auth_url,
22    );
23
24    server.block_until_done().await
25}
26
27pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
28    let config = load_config_or_exit(cli_config_overrides);
29
30    match login_with_chatgpt(config.codex_home).await {
31        Ok(_) => {
32            eprintln!("Successfully logged in");
33            std::process::exit(0);
34        }
35        Err(e) => {
36            eprintln!("Error logging in: {e}");
37            std::process::exit(1);
38        }
39    }
40}
41
42pub async fn run_login_with_api_key(
43    cli_config_overrides: CliConfigOverrides,
44    api_key: String,
45) -> ! {
46    let config = load_config_or_exit(cli_config_overrides);
47
48    match login_with_api_key(&config.codex_home, &api_key) {
49        Ok(_) => {
50            eprintln!("Successfully logged in");
51            std::process::exit(0);
52        }
53        Err(e) => {
54            eprintln!("Error logging in: {e}");
55            std::process::exit(1);
56        }
57    }
58}
59
60pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
61    let config = load_config_or_exit(cli_config_overrides);
62
63    match CodexAuth::from_codex_home(&config.codex_home, config.preferred_auth_method) {
64        Ok(Some(auth)) => match auth.mode {
65            AuthMode::ApiKey => match auth.get_token().await {
66                Ok(api_key) => {
67                    eprintln!("Logged in using an API key - {}", safe_format_key(&api_key));
68
69                    if let Ok(env_api_key) = env::var(OPENAI_API_KEY_ENV_VAR)
70                        && env_api_key == api_key
71                    {
72                        eprintln!(
73                            "   API loaded from OPENAI_API_KEY environment variable or .env file"
74                        );
75                    }
76                    std::process::exit(0);
77                }
78                Err(e) => {
79                    eprintln!("Unexpected error retrieving API key: {e}");
80                    std::process::exit(1);
81                }
82            },
83            AuthMode::ChatGPT => {
84                eprintln!("Logged in using ChatGPT");
85                std::process::exit(0);
86            }
87        },
88        Ok(None) => {
89            eprintln!("Not logged in");
90            std::process::exit(1);
91        }
92        Err(e) => {
93            eprintln!("Error checking login status: {e}");
94            std::process::exit(1);
95        }
96    }
97}
98
99pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
100    let config = load_config_or_exit(cli_config_overrides);
101
102    match logout(&config.codex_home) {
103        Ok(true) => {
104            eprintln!("Successfully logged out");
105            std::process::exit(0);
106        }
107        Ok(false) => {
108            eprintln!("Not logged in");
109            std::process::exit(0);
110        }
111        Err(e) => {
112            eprintln!("Error logging out: {e}");
113            std::process::exit(1);
114        }
115    }
116}
117
118fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
119    let cli_overrides = match cli_config_overrides.parse_overrides() {
120        Ok(v) => v,
121        Err(e) => {
122            eprintln!("Error parsing -c overrides: {e}");
123            std::process::exit(1);
124        }
125    };
126
127    let config_overrides = ConfigOverrides::default();
128    match Config::load_with_cli_overrides(cli_overrides, config_overrides) {
129        Ok(config) => config,
130        Err(e) => {
131            eprintln!("Error loading configuration: {e}");
132            std::process::exit(1);
133        }
134    }
135}
136
137fn safe_format_key(key: &str) -> String {
138    if key.len() <= 13 {
139        return "***".to_string();
140    }
141    let prefix = &key[..8];
142    let suffix = &key[key.len() - 5..];
143    format!("{prefix}***{suffix}")
144}
145
146#[cfg(test)]
147mod tests {
148    use super::safe_format_key;
149
150    #[test]
151    fn formats_long_key() {
152        let key = "sk-proj-1234567890ABCDE";
153        assert_eq!(safe_format_key(key), "sk-proj-***ABCDE");
154    }
155
156    #[test]
157    fn short_key_returns_stars() {
158        let key = "sk-proj-12345";
159        assert_eq!(safe_format_key(key), "***");
160    }
161}