Skip to main content

systemprompt_cli/commands/admin/
mod.rs

1//! `admin` command tree: privileged platform administration.
2//!
3//! [`AdminCommands`] groups user, agent, configuration, session, bridge,
4//! access-control, and signing-key management plus the setup and bootstrap
5//! flows. On a `--database-url` invocation only the user-management subgroup
6//! is served; the rest require a full profile context.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11pub mod access_control;
12pub mod agents;
13pub mod bootstrap;
14pub mod bridge;
15pub mod config;
16pub mod identity;
17pub mod keys;
18pub mod session;
19pub mod setup;
20pub mod users;
21
22use anyhow::Result;
23use clap::Subcommand;
24
25use crate::context::CommandContext;
26use crate::descriptor::DataImpact;
27use crate::shared::render_result;
28
29#[derive(Debug, Subcommand)]
30pub enum AdminCommands {
31    #[command(subcommand, about = "User management and IP banning")]
32    Users(users::UsersCommands),
33
34    #[command(subcommand, about = "Agent management")]
35    Agents(agents::AgentsCommands),
36
37    #[command(subcommand, about = "Configuration management and rate limits")]
38    Config(config::ConfigCommands),
39
40    #[command(about = "Interactive setup wizard for local development environment")]
41    Setup(setup::SetupArgs),
42
43    #[command(
44        about = "Idempotently ensure the system admin user exists with the admin role. Required \
45                 by every install recipe before services start. Note: the admin ROLE only — \
46                 deployments that derive platform admin from organization membership grant that \
47                 half themselves (at boot, or via their own tooling)."
48    )]
49    Bootstrap(bootstrap::BootstrapArgs),
50
51    #[command(subcommand, about = "Manage CLI session and profile switching")]
52    Session(session::SessionCommands),
53
54    #[command(
55        subcommand,
56        about = "Bridge helper enrollment (device certs, exchange codes)"
57    )]
58    Bridge(bridge::BridgeCommands),
59
60    #[command(
61        subcommand,
62        name = "access-control",
63        about = "Access-control baseline operations (DB → YAML export)"
64    )]
65    AccessControl(access_control::AccessControlCommands),
66
67    #[command(
68        subcommand,
69        about = "RSA signing-key generation for the federated JWT plane"
70    )]
71    Keys(keys::KeysCommands),
72
73    #[command(
74        subcommand,
75        about = "Replica identity secrets shared by every node of a deployment"
76    )]
77    Identity(identity::IdentityCommands),
78}
79
80pub async fn execute(cmd: AdminCommands, ctx: &CommandContext) -> Result<()> {
81    if ctx.is_database_scoped()
82        && !matches!(cmd, AdminCommands::Users(_) | AdminCommands::Session(_))
83    {
84        return Err(crate::shared::database_scoped_command_error());
85    }
86
87    match cmd {
88        AdminCommands::Users(cmd) => users::execute(cmd, ctx).await,
89        AdminCommands::Agents(cmd) => Box::pin(agents::execute(cmd, ctx)).await,
90        AdminCommands::Config(cmd) => config::execute(cmd, ctx).await,
91        AdminCommands::Setup(args) => {
92            let result = Box::pin(setup::execute(args, ctx)).await?;
93            render_result(&result, &ctx.cli);
94            Ok(())
95        },
96        AdminCommands::Bootstrap(args) => {
97            let result = bootstrap::execute(args, &ctx.cli).await?;
98            render_result(&result, &ctx.cli);
99            Ok(())
100        },
101        AdminCommands::Session(cmd) => session::execute(cmd, ctx).await,
102        AdminCommands::Bridge(cmd) => bridge::execute(cmd, ctx).await,
103        AdminCommands::AccessControl(cmd) => access_control::execute(cmd, ctx).await,
104        AdminCommands::Keys(cmd) => keys::execute(cmd, ctx).await,
105        AdminCommands::Identity(cmd) => identity::execute(cmd, ctx),
106    }
107}
108
109impl AdminCommands {
110    pub const fn data_impact(&self) -> DataImpact {
111        match self {
112            Self::Users(cmd) => cmd.data_impact(),
113            Self::Agents(cmd) => cmd.data_impact(),
114            Self::Bootstrap(_) | Self::Bridge(bridge::BridgeCommands::RotateSigningKey(_)) => {
115                DataImpact::Destructive
116            },
117            Self::Bridge(
118                bridge::BridgeCommands::EnrollCert(_)
119                | bridge::BridgeCommands::IssueCode(_)
120                | bridge::BridgeCommands::List(_),
121            )
122            | Self::AccessControl(
123                access_control::AccessControlCommands::ExportYaml(_)
124                | access_control::AccessControlCommands::Lint(_),
125            )
126            | Self::Keys(
127                keys::KeysCommands::Generate(_) | keys::KeysCommands::IssuePluginToken(_),
128            )
129            | Self::Config(_)
130            | Self::Setup(_)
131            | Self::Session(_)
132            | Self::Identity(_) => DataImpact::Preserving,
133        }
134    }
135}