Skip to main content

systemprompt_cli/commands/admin/session/
mod.rs

1//! Session management commands.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6mod list;
7pub mod login;
8pub mod login_helpers;
9mod logout;
10pub mod show;
11mod switch;
12pub mod types;
13
14use std::path::Path;
15
16use anyhow::Result;
17use clap::Subcommand;
18use systemprompt_cloud::{CloudError, SessionStore};
19use systemprompt_logging::CliService;
20
21use crate::context::CommandContext;
22use crate::descriptor::{CommandDescriptor, DescribeCommand};
23use crate::shared::render_result;
24
25#[derive(Debug, Subcommand)]
26pub enum SessionCommands {
27    #[command(about = "Show current session and routing info", alias = "current")]
28    Show,
29
30    #[command(about = "Switch to a different profile")]
31    Switch { profile_name: String },
32
33    #[command(about = "List available profiles")]
34    List,
35
36    #[command(about = "Create an admin session for CLI access")]
37    Login(login::LoginArgs),
38
39    #[command(about = "Remove a session")]
40    Logout(logout::LogoutArgs),
41}
42
43impl DescribeCommand for SessionCommands {
44    fn descriptor(&self) -> CommandDescriptor {
45        match self {
46            Self::Login(_) => CommandDescriptor::PROFILE_SECRETS_AND_PATHS.with_skip_validation(),
47            Self::Switch { .. } | Self::Show | Self::List | Self::Logout(_) => {
48                CommandDescriptor::NONE
49            },
50        }
51    }
52}
53
54pub async fn execute(cmd: SessionCommands, ctx: &CommandContext) -> Result<()> {
55    match cmd {
56        SessionCommands::Show => {
57            let result = show::execute(&ctx.cli);
58            render_result(&result, &ctx.cli);
59            Ok(())
60        },
61        SessionCommands::Switch { profile_name } => {
62            let result = switch::execute(&profile_name)?;
63            render_result(&result, &ctx.cli);
64            Ok(())
65        },
66        SessionCommands::List => {
67            let result = list::execute(&ctx.cli);
68            render_result(&result, &ctx.cli);
69            Ok(())
70        },
71        SessionCommands::Login(args) => {
72            let result = login::execute(args, &ctx.cli).await?;
73            render_result(&result, &ctx.cli);
74            Ok(())
75        },
76        SessionCommands::Logout(ref args) => {
77            let result = logout::execute(args, ctx.prompter(), &ctx.cli)?;
78            render_result(&result, &ctx.cli);
79            Ok(())
80        },
81    }
82}
83
84// Why: `switch` is the documented repair path for a corrupt index — the
85// error text sends the operator here — so it alone may start from an empty
86// store; every writer that is not a repair surfaces the corruption instead.
87pub(super) fn load_or_reset_corrupt(sessions_dir: &Path) -> Result<SessionStore> {
88    match SessionStore::load(sessions_dir) {
89        Ok(store) => Ok(store.unwrap_or_else(SessionStore::new)),
90        Err(CloudError::SessionStoreCorrupted { path, .. }) => {
91            CliService::warning(&format!(
92                "Session store at {path} is corrupt; starting fresh"
93            ));
94            Ok(SessionStore::new())
95        },
96        Err(error) => Err(error.into()),
97    }
98}
99
100pub(super) fn load_for_display(sessions_dir: &Path) -> SessionStore {
101    match SessionStore::load(sessions_dir) {
102        Ok(store) => store.unwrap_or_else(SessionStore::new),
103        Err(error) => {
104            CliService::warning(&format!("Session store unreadable: {error}"));
105            SessionStore::new()
106        },
107    }
108}