Add logout command to CLI and TUI (#1932)
## Summary - support `codex logout` via new subcommand and helper that removes the stored `auth.json` - expose a `logout` function in `codex-login` and test it - add `/logout` slash command in the TUI; command list is filtered when not logged in and the handler deletes `auth.json` then exits ## Testing - `just fix` *(fails: failed to get `diffy` from crates.io)* - `cargo test --all-features` *(fails: failed to get `diffy` from crates.io)* ------ https://chatgpt.com/codex/tasks/task_i_68945c3facac832ca83d48499716fb51
This commit is contained in:
@@ -8,6 +8,7 @@ use codex_login::OPENAI_API_KEY_ENV_VAR;
|
|||||||
use codex_login::load_auth;
|
use codex_login::load_auth;
|
||||||
use codex_login::login_with_api_key;
|
use codex_login::login_with_api_key;
|
||||||
use codex_login::login_with_chatgpt;
|
use codex_login::login_with_chatgpt;
|
||||||
|
use codex_login::logout;
|
||||||
|
|
||||||
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
|
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
|
||||||
let config = load_config_or_exit(cli_config_overrides);
|
let config = load_config_or_exit(cli_config_overrides);
|
||||||
@@ -80,6 +81,25 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
|
||||||
|
let config = load_config_or_exit(cli_config_overrides);
|
||||||
|
|
||||||
|
match logout(&config.codex_home) {
|
||||||
|
Ok(true) => {
|
||||||
|
eprintln!("Successfully logged out");
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
eprintln!("Not logged in");
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error logging out: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
|
fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
|
||||||
let cli_overrides = match cli_config_overrides.parse_overrides() {
|
let cli_overrides = match cli_config_overrides.parse_overrides() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use codex_cli::SeatbeltCommand;
|
|||||||
use codex_cli::login::run_login_status;
|
use codex_cli::login::run_login_status;
|
||||||
use codex_cli::login::run_login_with_api_key;
|
use codex_cli::login::run_login_with_api_key;
|
||||||
use codex_cli::login::run_login_with_chatgpt;
|
use codex_cli::login::run_login_with_chatgpt;
|
||||||
|
use codex_cli::login::run_logout;
|
||||||
use codex_cli::proto;
|
use codex_cli::proto;
|
||||||
use codex_common::CliConfigOverrides;
|
use codex_common::CliConfigOverrides;
|
||||||
use codex_exec::Cli as ExecCli;
|
use codex_exec::Cli as ExecCli;
|
||||||
@@ -48,6 +49,9 @@ enum Subcommand {
|
|||||||
/// Manage login.
|
/// Manage login.
|
||||||
Login(LoginCommand),
|
Login(LoginCommand),
|
||||||
|
|
||||||
|
/// Remove stored authentication credentials.
|
||||||
|
Logout(LogoutCommand),
|
||||||
|
|
||||||
/// Experimental: run Codex as an MCP server.
|
/// Experimental: run Codex as an MCP server.
|
||||||
Mcp,
|
Mcp,
|
||||||
|
|
||||||
@@ -106,6 +110,12 @@ enum LoginSubcommand {
|
|||||||
Status,
|
Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
struct LogoutCommand {
|
||||||
|
#[clap(skip)]
|
||||||
|
config_overrides: CliConfigOverrides,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
|
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
|
||||||
cli_main(codex_linux_sandbox_exe).await?;
|
cli_main(codex_linux_sandbox_exe).await?;
|
||||||
@@ -147,6 +157,10 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(Subcommand::Logout(mut logout_cli)) => {
|
||||||
|
prepend_config_flags(&mut logout_cli.config_overrides, cli.config_overrides);
|
||||||
|
run_logout(logout_cli.config_overrides).await;
|
||||||
|
}
|
||||||
Some(Subcommand::Proto(mut proto_cli)) => {
|
Some(Subcommand::Proto(mut proto_cli)) => {
|
||||||
prepend_config_flags(&mut proto_cli.config_overrides, cli.config_overrides);
|
prepend_config_flags(&mut proto_cli.config_overrides, cli.config_overrides);
|
||||||
proto::run_main(proto_cli).await?;
|
proto::run_main(proto_cli).await?;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use serde::Serialize;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
|
use std::fs::remove_file;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -185,6 +186,17 @@ fn get_auth_file(codex_home: &Path) -> PathBuf {
|
|||||||
codex_home.join("auth.json")
|
codex_home.join("auth.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete the auth.json file inside `codex_home` if it exists. Returns `Ok(true)`
|
||||||
|
/// if a file was removed, `Ok(false)` if no auth file was present.
|
||||||
|
pub fn logout(codex_home: &Path) -> std::io::Result<bool> {
|
||||||
|
let auth_file = get_auth_file(codex_home);
|
||||||
|
match remove_file(&auth_file) {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a running login subprocess. The child can be killed by holding
|
/// Represents a running login subprocess. The child can be killed by holding
|
||||||
/// the mutex and calling `kill()`.
|
/// the mutex and calling `kill()`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -494,4 +506,15 @@ mod tests {
|
|||||||
|
|
||||||
assert!(auth.get_token_data().await.is_err());
|
assert!(auth.get_token_data().await.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn logout_removes_auth_file() -> Result<(), std::io::Error> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
login_with_api_key(dir.path(), "sk-test-key")?;
|
||||||
|
assert!(dir.path().join("auth.json").exists());
|
||||||
|
let removed = logout(dir.path())?;
|
||||||
|
assert!(removed);
|
||||||
|
assert!(!dir.path().join("auth.json").exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,6 +328,12 @@ impl App<'_> {
|
|||||||
SlashCommand::Quit => {
|
SlashCommand::Quit => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
SlashCommand::Logout => {
|
||||||
|
if let Err(e) = codex_login::logout(&self.config.codex_home) {
|
||||||
|
tracing::error!("failed to logout: {e}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
SlashCommand::Diff => {
|
SlashCommand::Diff => {
|
||||||
let (is_git_repo, diff_text) = match get_git_diff() {
|
let (is_git_repo, diff_text) = match get_git_diff() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub enum SlashCommand {
|
|||||||
Compact,
|
Compact,
|
||||||
Diff,
|
Diff,
|
||||||
Status,
|
Status,
|
||||||
|
Logout,
|
||||||
Quit,
|
Quit,
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
TestApproval,
|
TestApproval,
|
||||||
@@ -32,6 +33,7 @@ impl SlashCommand {
|
|||||||
SlashCommand::Quit => "Exit the application",
|
SlashCommand::Quit => "Exit the application",
|
||||||
SlashCommand::Diff => "Show git diff (including untracked files)",
|
SlashCommand::Diff => "Show git diff (including untracked files)",
|
||||||
SlashCommand::Status => "Show current session configuration and token usage",
|
SlashCommand::Status => "Show current session configuration and token usage",
|
||||||
|
SlashCommand::Logout => "Log out of Codex",
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
SlashCommand::TestApproval => "Test approval request",
|
SlashCommand::TestApproval => "Test approval request",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user