use anyhow::{Context, Result};
use clap::Args;
use dialoguer::Confirm;
use dialoguer::theme::ColorfulTheme;
use systemprompt_cloud::{ProfilePath, SessionKey, SessionStore};
use systemprompt_models::Profile;
use super::types::LogoutOutput;
use crate::CliConfig;
use crate::paths::ResolvedPaths;
use crate::shared::CommandResult;
#[derive(Debug, Args)]
pub struct LogoutArgs {
#[arg(long, help = "Profile name to log out (defaults to active session)")]
pub profile: Option<String>,
#[arg(short = 'y', long, help = "Skip confirmation prompt")]
pub yes: bool,
#[arg(long, help = "Remove all sessions")]
pub all: bool,
}
pub fn execute(args: &LogoutArgs, config: &CliConfig) -> Result<CommandResult<LogoutOutput>> {
let paths = ResolvedPaths::discover();
let sessions_dir = paths.sessions_dir();
let mut store = SessionStore::load_or_create(&sessions_dir)?;
if store.is_empty() {
return Ok(CommandResult::text(LogoutOutput {
action: "none".to_string(),
target: "all".to_string(),
message: "No sessions to remove".to_string(),
})
.with_title("Logout"));
}
if args.all {
return remove_all_sessions(&store, &sessions_dir, args, config);
}
let session_key = resolve_target_key(args, &paths, &store)?;
let display_name = session_key.to_string();
if !args.yes && config.is_interactive() {
let confirmed = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!("Remove session for '{}'?", display_name))
.default(false)
.interact()?;
if !confirmed {
return Ok(CommandResult::text(LogoutOutput {
action: "cancelled".to_string(),
target: display_name,
message: "Operation cancelled".to_string(),
})
.with_title("Logout"));
}
}
let removed = store.remove_session(&session_key);
if removed.is_some() {
store.save(&sessions_dir)?;
Ok(CommandResult::text(LogoutOutput {
action: "removed".to_string(),
target: display_name.clone(),
message: format!("Session removed for '{}'", display_name),
})
.with_title("Logout"))
} else {
Ok(CommandResult::text(LogoutOutput {
action: "not_found".to_string(),
target: display_name.clone(),
message: format!("No session found for '{}'", display_name),
})
.with_title("Logout"))
}
}
fn remove_all_sessions(
store: &SessionStore,
sessions_dir: &std::path::Path,
args: &LogoutArgs,
config: &CliConfig,
) -> Result<CommandResult<LogoutOutput>> {
let count = store.len();
if !args.yes {
if !config.is_interactive() {
anyhow::bail!("--yes is required in non-interactive mode for --all");
}
let confirmed = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!("Remove all {} session(s)?", count))
.default(false)
.interact()?;
if !confirmed {
return Ok(CommandResult::text(LogoutOutput {
action: "cancelled".to_string(),
target: "all".to_string(),
message: "Operation cancelled".to_string(),
})
.with_title("Logout"));
}
}
let new_store = SessionStore::new();
new_store.save(sessions_dir)?;
Ok(CommandResult::text(LogoutOutput {
action: "removed_all".to_string(),
target: "all".to_string(),
message: format!("Removed {} session(s)", count),
})
.with_title("Logout"))
}
fn resolve_target_key(
args: &LogoutArgs,
paths: &ResolvedPaths,
store: &SessionStore,
) -> Result<SessionKey> {
if let Some(ref profile_name) = args.profile {
let target_dir = paths.profiles_dir().join(profile_name);
let profile_config_path = ProfilePath::Config.resolve(&target_dir);
if !profile_config_path.exists() {
anyhow::bail!(
"Profile '{}' not found.\n\nAvailable profiles can be listed with: systemprompt \
admin session list",
profile_name
);
}
let content = std::fs::read_to_string(&profile_config_path)
.with_context(|| format!("Failed to read profile '{}'", profile_name))?;
let profile = Profile::parse(&content, &profile_config_path)
.with_context(|| format!("Failed to parse profile '{}'", profile_name))?;
let tenant_id = profile.cloud.as_ref().and_then(|c| c.tenant_id.as_deref());
return Ok(SessionKey::from_tenant_id(tenant_id));
}
store.active_session_key().ok_or_else(|| {
anyhow::anyhow!(
"No active session. Use --profile <name> to specify which session to remove."
)
})
}