use super::super::*;
use super::MissionControlApp;
impl MissionControlApp {
pub(crate) fn open_login_picker(
&mut self,
ui_state: &mut state::MissionControlState,
terminal_area: ratatui::layout::Rect,
) {
let mut auth_error = None;
let entries = crate::login::providers()
.into_iter()
.map(|provider| {
let status = if provider.id == crate::login::CUSTOM_PROVIDER_LOGIN_ID {
"configure with prompts".to_string()
} else if provider.id == crate::providers::CLAUDE_CODE_PROVIDER {
match crate::login::claude_code_provider_status(&self.config.paths) {
Ok(crate::login::LoginProviderStatus::Configured) => {
"ready via Claude Code auth".to_string()
}
Ok(_) => crate::login::CLAUDE_CODE_LOGIN_STATUS.to_string(),
Err(error) => {
auth_error.get_or_insert_with(|| error.to_string());
"auth store error".to_string()
}
}
} else {
match crate::login::provider_status(&self.config.paths, provider.id) {
Ok(status) => status.label().to_string(),
Err(error) => {
auth_error.get_or_insert_with(|| error.to_string());
"auth store error".to_string()
}
}
};
state::LoginProviderEntry {
id: provider.id.to_string(),
label: provider.label.to_string(),
description: provider.description.to_string(),
status,
}
})
.collect();
ui_state.open_login_picker(
entries,
None,
auth_picker_visible_rows(terminal_area, false),
);
ui_state.status = auth_error
.map(|error| format!("login auth store error: {error}"))
.unwrap_or_else(|| "select login provider".to_string());
}
pub(crate) fn open_logout_picker(
&mut self,
ui_state: &mut state::MissionControlState,
terminal_area: ratatui::layout::Rect,
) {
let entries_result: anyhow::Result<Vec<_>> = crate::login::providers()
.into_iter()
.map(|provider| {
Ok(state::LoginProviderEntry {
id: provider.id.to_string(),
label: provider.label.to_string(),
description: provider.description.to_string(),
status: crate::login::logout_provider_status(&self.config.paths, provider.id)?
.label()
.to_string(),
})
})
.collect();
match entries_result {
Ok(mut entries) => {
if let Ok(settings) = crate::config::read_settings(&self.config.paths) {
entries.extend(settings.custom_providers.into_iter().map(|(id, provider)| {
state::LoginProviderEntry {
id,
label: provider.label,
description: "custom provider metadata".to_string(),
status: "configured".to_string(),
}
}));
}
ui_state.open_logout_picker(
entries,
None,
auth_picker_visible_rows(terminal_area, false),
);
ui_state.status = "select logout provider".to_string();
}
Err(error) => {
let _ = send_critical(&self.events, TuiEvent::Error(error.to_string()));
}
}
}
pub(crate) fn logout_provider(
&mut self,
provider_id: &str,
ui_state: &mut state::MissionControlState,
) {
if self.active_run || self.worker.is_some() {
let _ = send_critical(
&self.events,
TuiEvent::Error("cannot logout while a prompt is running".to_string()),
);
return;
}
if let Ok(settings) = crate::config::read_settings(&self.config.paths)
&& let Some(provider) = settings.custom_providers.get(provider_id)
{
ui_state.open_logout_confirmation(provider_id.to_string(), provider.label.clone());
ui_state.status = format!("confirm removal for custom provider {provider_id}");
return;
}
let provider = match crate::login::validate_logout_provider(provider_id) {
Ok(provider) => provider,
Err(error) => {
let _ = send_critical(&self.events, TuiEvent::Error(error.to_string()));
return;
}
};
match crate::login::logout_provider_status(&self.config.paths, provider.id) {
Ok(crate::login::LogoutProviderStatus::Missing) => {
ui_state.status = format!(
"{} ({}) is not configured; credentials unchanged",
provider.label, provider.id
);
}
Ok(_) => {
ui_state
.open_logout_confirmation(provider.id.to_string(), provider.label.to_string());
ui_state.status =
format!("confirm logout for {} ({})", provider.label, provider.id);
}
Err(error) => {
let _ = send_critical(&self.events, TuiEvent::Error(error.to_string()));
}
}
}
pub(crate) fn remove_logout_provider(
&mut self,
provider_id: &str,
ui_state: &mut state::MissionControlState,
) {
match crate::commands::runtime::remove_logout_target(&self.config.paths, provider_id) {
Ok(removal) => {
if removal.removed() {
crate::commands::runtime::clear_runtime_auth_for_provider(
&mut self.state,
removal.provider_id(),
);
if let Some(config) = &mut self.state.config {
crate::commands::runtime::sync_effective_config_after_logout(
config, &removal,
);
}
crate::commands::runtime::sync_effective_config_after_logout(
&mut self.config,
&removal,
);
ui_state.provider_ready = self.state.auth_state.is_ready();
}
ui_state.status = removal.message();
}
Err(error) => {
let _ = send_critical(&self.events, TuiEvent::Error(error.to_string()));
}
}
}
pub(crate) fn start_login(
&mut self,
provider_id: &str,
ui_state: &mut state::MissionControlState,
) {
if provider_id == crate::login::CUSTOM_PROVIDER_LOGIN_ID || provider_id == "custom" {
if self.active_run || self.worker.is_some() {
let _ = send_critical(
&self.events,
TuiEvent::Error("cannot start login while a prompt is running".to_string()),
);
return;
}
ui_state.start_custom_provider_setup();
ui_state.status = "custom provider setup: enter provider label".to_string();
return;
}
if provider_id == crate::providers::CLAUDE_CODE_PROVIDER {
if self.active_run || self.worker.is_some() {
let _ = send_critical(
&self.events,
TuiEvent::Error("cannot start login while a prompt is running".to_string()),
);
return;
}
ui_state.status = crate::login::claude_code_login_instructions().to_string();
return;
}
if provider_id != crate::providers::OPENAI_CODEX_PROVIDER {
let message = format!(
"unsupported login provider '{provider_id}'; supported providers: openai-codex, claude-code, custom-provider"
);
let _ = send_critical(&self.events, TuiEvent::Error(message));
return;
}
if self.active_run || self.worker.is_some() {
let _ = send_critical(
&self.events,
TuiEvent::Error("cannot start login while a prompt is running".to_string()),
);
return;
}
ui_state.status = "starting OpenAI Codex login…".to_string();
ui_state.start_active_login(provider_id.to_string());
let paths = self.config.paths.clone();
let sender = self.events.clone();
self.active_run = true;
let cancel = Arc::new(AtomicBool::new(false));
let outcome = Arc::new(WorkerOutcomeState::default());
let worker_cancel = Arc::clone(&cancel);
let worker_outcome = Arc::clone(&outcome);
let (manual_tx, manual_rx) = mpsc::channel();
let progress_sender = sender.clone();
let handle = thread::spawn(move || {
if worker_cancel.load(Ordering::SeqCst) {
send_completion(&sender, &worker_outcome, None);
return;
}
let result = crate::login::login_openai_codex_with_controls(
&paths,
Arc::clone(&worker_cancel),
Some(manual_rx),
move |instructions| {
let _ =
send_critical(&progress_sender, TuiEvent::LoginInstructions(instructions));
},
)
.map(|result| result.message);
let event = WorkerFinalEvent::LoginFinished {
provider_id: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
result: result.map_err(|error| error.to_string()),
};
send_completion(&sender, &worker_outcome, Some(event));
});
self.worker = Some(WorkerState {
handle,
cancel,
login_manual: Some(manual_tx),
outcome,
outcome_reconciled: false,
steering: crate::agent::steering::AgentSteering::new(),
accepts_steering: false,
});
}
pub(crate) fn submit_custom_provider_setup_step(
&mut self,
ui_state: &mut state::MissionControlState,
) {
let Some(setup) = ui_state.modals.custom_provider_setup.clone() else {
ui_state.status = "no custom provider setup is active".to_string();
return;
};
match setup.step {
state::CustomProviderSetupStep::Label => {
match crate::config::derive_custom_provider_id(&setup.label) {
Ok(provider_id) => {
if let Some(active) = &mut ui_state.modals.custom_provider_setup {
active.provider_id = Some(provider_id.clone());
active.step = state::CustomProviderSetupStep::BaseUrl;
active.error = None;
}
ui_state.status =
format!("custom provider id will be '{provider_id}'; enter base URL");
}
Err(error) => {
ui_state.set_custom_provider_setup_error(error.to_string());
ui_state.status = error.to_string();
}
}
}
state::CustomProviderSetupStep::BaseUrl => {
match crate::config::normalize_custom_provider_base_url(&setup.base_url) {
Ok(base_url) => {
if let Some(active) = &mut ui_state.modals.custom_provider_setup {
active.base_url = base_url;
active.step = state::CustomProviderSetupStep::ApiKeyEnvVar;
active.error = None;
}
ui_state.status =
"enter API key env var, or leave blank for no auth".to_string();
}
Err(error) => {
ui_state.set_custom_provider_setup_error(error.to_string());
ui_state.status = error.to_string();
}
}
}
state::CustomProviderSetupStep::ApiKeyEnvVar => {
let provider_id = match setup
.provider_id
.or_else(|| crate::config::derive_custom_provider_id(&setup.label).ok())
{
Some(provider_id) => provider_id,
None => {
ui_state.set_custom_provider_setup_error(
"provider label does not derive a valid id".to_string(),
);
return;
}
};
let api_key_env_var =
match crate::config::validate_optional_env_var_name(&setup.api_key_env_var) {
Ok(value) => value,
Err(error) => {
ui_state.set_custom_provider_setup_error(error.to_string());
ui_state.status = error.to_string();
return;
}
};
if let Ok(settings) = crate::config::read_settings(&self.config.paths)
&& let Some(existing) = settings.custom_providers.get(&provider_id)
{
ui_state.open_custom_provider_replacement_confirmation(
provider_id.clone(),
existing.label.clone(),
setup.label.trim().to_string(),
setup.base_url,
api_key_env_var,
);
ui_state.status = format!(
"custom provider '{provider_id}' already exists; press [Enter] to replace or [Esc] to cancel"
);
return;
}
self.configure_custom_provider_from_parts(
&provider_id,
&setup.label,
&setup.base_url,
setup.api_key_env_var.as_str(),
ui_state,
);
}
}
}
pub(crate) fn submit_login_fallback(
&mut self,
input: String,
ui_state: &mut state::MissionControlState,
) {
let Some(worker) = &self.worker else {
ui_state.clear_active_login();
ui_state.status = "no active login to receive fallback input".to_string();
return;
};
if let Some(sender) = &worker.login_manual {
if sender.send(input).is_ok() {
ui_state.status = "manual OAuth redirect submitted; exchanging token…".to_string();
} else {
ui_state.status = "login worker is no longer accepting fallback input".to_string();
}
} else {
ui_state.status = "active worker is not a login flow".to_string();
}
}
pub(crate) fn confirm_custom_provider_replacement(
&mut self,
ui_state: &mut state::MissionControlState,
) {
let Some(confirmation) = ui_state
.modals
.custom_provider_replacement_confirmation
.clone()
else {
ui_state.status = "no custom provider replacement pending".to_string();
return;
};
self.configure_custom_provider_from_parts(
&confirmation.provider_id,
&confirmation.label,
&confirmation.base_url,
confirmation.api_key_env_var.as_deref().unwrap_or(""),
ui_state,
);
}
pub(crate) fn configure_custom_provider_from_parts(
&mut self,
provider_id: &str,
label: &str,
base_url: &str,
api_key_env_var: &str,
ui_state: &mut state::MissionControlState,
) {
match crate::login::configure_custom_provider(
&self.config.paths,
provider_id,
label,
base_url,
api_key_env_var,
) {
Ok(result) => {
let provider = self.config.provider_id().to_string();
let model = self
.config
.model
.clone()
.unwrap_or_else(|| crate::providers::DEFAULT_CODEX_MODEL.to_string());
match crate::config::load_effective_provider_selection(
&self.config.paths,
&provider,
&model,
) {
Ok(refreshed) => {
self.config = refreshed.clone();
self.state.auth_state = refreshed.auth_state();
self.state.model = model.clone();
self.state.config = Some(refreshed.clone());
ui_state.provider = provider.clone();
ui_state.model = model.clone();
ui_state.provider_ready = self.state.auth_state.is_ready();
ui_state.thinking_levels = cached_thinking_levels_for_model(
&self.config.paths,
&provider,
&model,
crate::thinking::capability_scope_for_provider(
&self.config.custom_providers,
&provider,
),
);
ui_state.refresh_thinking_levels(
self.config.thinking_level,
ui_state.thinking_levels.clone(),
);
ui_state.clear_active_login();
ui_state.clear_custom_provider_setup();
ui_state.close_custom_provider_replacement_confirmation();
ui_state.status = result.message;
}
Err(error) => {
ui_state.status = format!(
"custom provider configured, but runtime refresh failed: {error}"
);
}
}
}
Err(error) => {
ui_state.status = error.to_string();
}
}
}
}