This PR adds the following: * A getAuthStatus method on the mcp server. This returns the auth method currently in use (chatgpt or apikey) or none if the user is not authenticated. It also returns the "preferred auth method" which reflects the `preferred_auth_method` value in the config. * A logout method on the mcp server. If called, it logs out the user and deletes the `auth.json` file — the same behavior in the cli's `/logout` command. * An `authStatusChange` event notification that is sent when the auth status changes due to successful login or logout operations. * Logic to pass command-line config overrides to the mcp server at startup time. This allows use cases like `codex mcp -c preferred_auth_method=apikey`.
236 lines
7.4 KiB
Rust
236 lines
7.4 KiB
Rust
use clap::CommandFactory;
|
||
use clap::Parser;
|
||
use clap_complete::Shell;
|
||
use clap_complete::generate;
|
||
use codex_arg0::arg0_dispatch_or_else;
|
||
use codex_chatgpt::apply_command::ApplyCommand;
|
||
use codex_chatgpt::apply_command::run_apply_command;
|
||
use codex_cli::LandlockCommand;
|
||
use codex_cli::SeatbeltCommand;
|
||
use codex_cli::login::run_login_status;
|
||
use codex_cli::login::run_login_with_api_key;
|
||
use codex_cli::login::run_login_with_chatgpt;
|
||
use codex_cli::login::run_logout;
|
||
use codex_cli::proto;
|
||
use codex_common::CliConfigOverrides;
|
||
use codex_exec::Cli as ExecCli;
|
||
use codex_tui::Cli as TuiCli;
|
||
use std::path::PathBuf;
|
||
|
||
use crate::proto::ProtoCli;
|
||
|
||
/// Codex CLI
|
||
///
|
||
/// If no subcommand is specified, options will be forwarded to the interactive CLI.
|
||
#[derive(Debug, Parser)]
|
||
#[clap(
|
||
author,
|
||
version,
|
||
// If a sub‑command is given, ignore requirements of the default args.
|
||
subcommand_negates_reqs = true,
|
||
// The executable is sometimes invoked via a platform‑specific name like
|
||
// `codex-x86_64-unknown-linux-musl`, but the help output should always use
|
||
// the generic `codex` command name that users run.
|
||
bin_name = "codex"
|
||
)]
|
||
struct MultitoolCli {
|
||
#[clap(flatten)]
|
||
pub config_overrides: CliConfigOverrides,
|
||
|
||
#[clap(flatten)]
|
||
interactive: TuiCli,
|
||
|
||
#[clap(subcommand)]
|
||
subcommand: Option<Subcommand>,
|
||
}
|
||
|
||
#[derive(Debug, clap::Subcommand)]
|
||
enum Subcommand {
|
||
/// Run Codex non-interactively.
|
||
#[clap(visible_alias = "e")]
|
||
Exec(ExecCli),
|
||
|
||
/// Manage login.
|
||
Login(LoginCommand),
|
||
|
||
/// Remove stored authentication credentials.
|
||
Logout(LogoutCommand),
|
||
|
||
/// Experimental: run Codex as an MCP server.
|
||
Mcp,
|
||
|
||
/// Run the Protocol stream via stdin/stdout
|
||
#[clap(visible_alias = "p")]
|
||
Proto(ProtoCli),
|
||
|
||
/// Generate shell completion scripts.
|
||
Completion(CompletionCommand),
|
||
|
||
/// Internal debugging commands.
|
||
Debug(DebugArgs),
|
||
|
||
/// Apply the latest diff produced by Codex agent as a `git apply` to your local working tree.
|
||
#[clap(visible_alias = "a")]
|
||
Apply(ApplyCommand),
|
||
|
||
/// Internal: generate TypeScript protocol bindings.
|
||
#[clap(hide = true)]
|
||
GenerateTs(GenerateTsCommand),
|
||
}
|
||
|
||
#[derive(Debug, Parser)]
|
||
struct CompletionCommand {
|
||
/// Shell to generate completions for
|
||
#[clap(value_enum, default_value_t = Shell::Bash)]
|
||
shell: Shell,
|
||
}
|
||
|
||
#[derive(Debug, Parser)]
|
||
struct DebugArgs {
|
||
#[command(subcommand)]
|
||
cmd: DebugCommand,
|
||
}
|
||
|
||
#[derive(Debug, clap::Subcommand)]
|
||
enum DebugCommand {
|
||
/// Run a command under Seatbelt (macOS only).
|
||
Seatbelt(SeatbeltCommand),
|
||
|
||
/// Run a command under Landlock+seccomp (Linux only).
|
||
Landlock(LandlockCommand),
|
||
}
|
||
|
||
#[derive(Debug, Parser)]
|
||
struct LoginCommand {
|
||
#[clap(skip)]
|
||
config_overrides: CliConfigOverrides,
|
||
|
||
#[arg(long = "api-key", value_name = "API_KEY")]
|
||
api_key: Option<String>,
|
||
|
||
#[command(subcommand)]
|
||
action: Option<LoginSubcommand>,
|
||
}
|
||
|
||
#[derive(Debug, clap::Subcommand)]
|
||
enum LoginSubcommand {
|
||
/// Show login status.
|
||
Status,
|
||
}
|
||
|
||
#[derive(Debug, Parser)]
|
||
struct LogoutCommand {
|
||
#[clap(skip)]
|
||
config_overrides: CliConfigOverrides,
|
||
}
|
||
|
||
#[derive(Debug, Parser)]
|
||
struct GenerateTsCommand {
|
||
/// Output directory where .ts files will be written
|
||
#[arg(short = 'o', long = "out", value_name = "DIR")]
|
||
out_dir: PathBuf,
|
||
|
||
/// Optional path to the Prettier executable to format generated files
|
||
#[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")]
|
||
prettier: Option<PathBuf>,
|
||
}
|
||
|
||
fn main() -> anyhow::Result<()> {
|
||
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
|
||
cli_main(codex_linux_sandbox_exe).await?;
|
||
Ok(())
|
||
})
|
||
}
|
||
|
||
async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()> {
|
||
let cli = MultitoolCli::parse();
|
||
|
||
match cli.subcommand {
|
||
None => {
|
||
let mut tui_cli = cli.interactive;
|
||
prepend_config_flags(&mut tui_cli.config_overrides, cli.config_overrides);
|
||
let usage = codex_tui::run_main(tui_cli, codex_linux_sandbox_exe).await?;
|
||
if !usage.is_zero() {
|
||
println!("{}", codex_core::protocol::FinalOutput::from(usage));
|
||
}
|
||
}
|
||
Some(Subcommand::Exec(mut exec_cli)) => {
|
||
prepend_config_flags(&mut exec_cli.config_overrides, cli.config_overrides);
|
||
codex_exec::run_main(exec_cli, codex_linux_sandbox_exe).await?;
|
||
}
|
||
Some(Subcommand::Mcp) => {
|
||
codex_mcp_server::run_main(codex_linux_sandbox_exe, cli.config_overrides).await?;
|
||
}
|
||
Some(Subcommand::Login(mut login_cli)) => {
|
||
prepend_config_flags(&mut login_cli.config_overrides, cli.config_overrides);
|
||
match login_cli.action {
|
||
Some(LoginSubcommand::Status) => {
|
||
run_login_status(login_cli.config_overrides).await;
|
||
}
|
||
None => {
|
||
if let Some(api_key) = login_cli.api_key {
|
||
run_login_with_api_key(login_cli.config_overrides, api_key).await;
|
||
} else {
|
||
run_login_with_chatgpt(login_cli.config_overrides).await;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
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)) => {
|
||
prepend_config_flags(&mut proto_cli.config_overrides, cli.config_overrides);
|
||
proto::run_main(proto_cli).await?;
|
||
}
|
||
Some(Subcommand::Completion(completion_cli)) => {
|
||
print_completion(completion_cli);
|
||
}
|
||
Some(Subcommand::Debug(debug_args)) => match debug_args.cmd {
|
||
DebugCommand::Seatbelt(mut seatbelt_cli) => {
|
||
prepend_config_flags(&mut seatbelt_cli.config_overrides, cli.config_overrides);
|
||
codex_cli::debug_sandbox::run_command_under_seatbelt(
|
||
seatbelt_cli,
|
||
codex_linux_sandbox_exe,
|
||
)
|
||
.await?;
|
||
}
|
||
DebugCommand::Landlock(mut landlock_cli) => {
|
||
prepend_config_flags(&mut landlock_cli.config_overrides, cli.config_overrides);
|
||
codex_cli::debug_sandbox::run_command_under_landlock(
|
||
landlock_cli,
|
||
codex_linux_sandbox_exe,
|
||
)
|
||
.await?;
|
||
}
|
||
},
|
||
Some(Subcommand::Apply(mut apply_cli)) => {
|
||
prepend_config_flags(&mut apply_cli.config_overrides, cli.config_overrides);
|
||
run_apply_command(apply_cli, None).await?;
|
||
}
|
||
Some(Subcommand::GenerateTs(gen_cli)) => {
|
||
codex_protocol_ts::generate_ts(&gen_cli.out_dir, gen_cli.prettier.as_deref())?;
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Prepend root-level overrides so they have lower precedence than
|
||
/// CLI-specific ones specified after the subcommand (if any).
|
||
fn prepend_config_flags(
|
||
subcommand_config_overrides: &mut CliConfigOverrides,
|
||
cli_config_overrides: CliConfigOverrides,
|
||
) {
|
||
subcommand_config_overrides
|
||
.raw_overrides
|
||
.splice(0..0, cli_config_overrides.raw_overrides);
|
||
}
|
||
|
||
fn print_completion(cmd: CompletionCommand) {
|
||
let mut app = MultitoolCli::command();
|
||
let name = "codex";
|
||
generate(cmd.shell, &mut app, name, &mut std::io::stdout());
|
||
}
|