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 evals;
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::shared::render_result;
27
28#[derive(Debug, Subcommand)]
29pub enum AdminCommands {
30    #[command(subcommand, about = "User management and IP banning")]
31    Users(users::UsersCommands),
32
33    #[command(subcommand, about = "Agent management")]
34    Agents(agents::AgentsCommands),
35
36    #[command(subcommand, about = "Configuration management and rate limits")]
37    Config(config::ConfigCommands),
38
39    #[command(subcommand, about = "Evaluation runs over production AI traffic")]
40    Evals(evals::EvalsCommands),
41
42    #[command(about = "Interactive setup wizard for local development environment")]
43    Setup(setup::SetupArgs),
44
45    #[command(
46        about = "Idempotently ensure the system admin user exists with the admin role. Required \
47                 by every install recipe before services start. Note: the admin ROLE only — \
48                 deployments that derive platform admin from organization membership grant that \
49                 half themselves (at boot, or via their own tooling)."
50    )]
51    Bootstrap(bootstrap::BootstrapArgs),
52
53    #[command(subcommand, about = "Manage CLI session and profile switching")]
54    Session(session::SessionCommands),
55
56    #[command(
57        subcommand,
58        about = "Bridge helper enrollment (device certs, exchange codes)"
59    )]
60    Bridge(bridge::BridgeCommands),
61
62    #[command(
63        subcommand,
64        name = "access-control",
65        about = "Access-control baseline operations (DB → YAML export)"
66    )]
67    AccessControl(access_control::AccessControlCommands),
68
69    #[command(
70        subcommand,
71        about = "RSA signing-key generation for the federated JWT plane"
72    )]
73    Keys(keys::KeysCommands),
74}
75
76pub async fn execute(cmd: AdminCommands, ctx: &CommandContext) -> Result<()> {
77    // Why: Session is exempt alongside Users — `session login` against a cloud
78    // profile with external_db_access runs exactly in this database-scoped
79    // mode (profile + secrets + external DB URL, no local /app paths), and it
80    // is the command that mints the token remote routing needs. Refusing it
81    // here made cloud login circular: the error told the operator to run the
82    // very command being refused.
83    if ctx.is_database_scoped()
84        && !matches!(cmd, AdminCommands::Users(_) | AdminCommands::Session(_))
85    {
86        return Err(crate::shared::database_scoped_command_error());
87    }
88
89    match cmd {
90        AdminCommands::Users(cmd) => users::execute(cmd, ctx).await,
91        AdminCommands::Agents(cmd) => Box::pin(agents::execute(cmd, ctx)).await,
92        AdminCommands::Config(cmd) => config::execute(cmd, ctx).await,
93        AdminCommands::Evals(cmd) => Box::pin(evals::execute(cmd, ctx)).await,
94        AdminCommands::Setup(args) => {
95            let result = Box::pin(setup::execute(args, ctx)).await?;
96            render_result(&result, &ctx.cli);
97            Ok(())
98        },
99        AdminCommands::Bootstrap(args) => {
100            let result = bootstrap::execute(args, &ctx.cli).await?;
101            render_result(&result, &ctx.cli);
102            Ok(())
103        },
104        AdminCommands::Session(cmd) => session::execute(cmd, ctx).await,
105        AdminCommands::Bridge(cmd) => bridge::execute(cmd, ctx).await,
106        AdminCommands::AccessControl(cmd) => access_control::execute(cmd, ctx).await,
107        AdminCommands::Keys(cmd) => keys::execute(cmd, ctx).await,
108    }
109}