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