Skip to main content

systemprompt_cli/
descriptor.rs

1//! Per-command bootstrap descriptor: which of profile/secrets/paths a command
2//! needs.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7/// Bootstrap flags for one command.
8///
9/// Model discovery (installing the services registry through boot-time
10/// discovery) is set only for the server; every other command loads the YAML
11/// catalog as authored and must not reach for the network.
12#[derive(Debug, Clone, Copy, Default)]
13pub struct CommandDescriptor {
14    flags: u8,
15}
16
17impl CommandDescriptor {
18    const FLAG_PROFILE: u8 = 0b0000_0001;
19    const FLAG_SECRETS: u8 = 0b0000_0010;
20    const FLAG_PATHS: u8 = 0b0000_0100;
21    const FLAG_DATABASE: u8 = 0b0000_1000;
22    const FLAG_REMOTE_ELIGIBLE: u8 = 0b0001_0000;
23    const FLAG_SKIP_VALIDATION: u8 = 0b0010_0000;
24    const FLAG_READ_ONLY: u8 = 0b0100_0000;
25    const FLAG_MODEL_DISCOVERY: u8 = 0b1000_0000;
26
27    pub const NONE: Self = Self { flags: 0 };
28
29    pub const PROFILE_ONLY: Self = Self {
30        flags: Self::FLAG_PROFILE,
31    };
32
33    pub const PROFILE_AND_SECRETS: Self = Self {
34        flags: Self::FLAG_PROFILE | Self::FLAG_SECRETS,
35    };
36
37    pub const PROFILE_SECRETS_AND_PATHS: Self = Self {
38        flags: Self::FLAG_PROFILE | Self::FLAG_SECRETS | Self::FLAG_PATHS,
39    };
40
41    pub const FULL: Self = Self {
42        flags: Self::FLAG_PROFILE
43            | Self::FLAG_SECRETS
44            | Self::FLAG_PATHS
45            | Self::FLAG_DATABASE
46            | Self::FLAG_REMOTE_ELIGIBLE,
47    };
48
49    pub const fn profile(&self) -> bool {
50        self.flags & Self::FLAG_PROFILE != 0
51    }
52
53    pub const fn secrets(&self) -> bool {
54        self.flags & Self::FLAG_SECRETS != 0
55    }
56
57    pub const fn paths(&self) -> bool {
58        self.flags & Self::FLAG_PATHS != 0
59    }
60
61    pub const fn database(&self) -> bool {
62        self.flags & Self::FLAG_DATABASE != 0
63    }
64
65    pub const fn routing_class(&self) -> RoutingClass {
66        if self.flags & Self::FLAG_REMOTE_ELIGIBLE == 0 {
67            RoutingClass::LocalOnly
68        } else if self.flags & Self::FLAG_READ_ONLY != 0 {
69            RoutingClass::ReadOnly
70        } else {
71            RoutingClass::Mutating
72        }
73    }
74
75    pub const fn skip_validation(&self) -> bool {
76        self.flags & Self::FLAG_SKIP_VALIDATION != 0
77    }
78
79    pub const fn discovers_models(&self) -> bool {
80        self.flags & Self::FLAG_MODEL_DISCOVERY != 0
81    }
82
83    pub const fn with_remote_eligible(self) -> Self {
84        Self {
85            flags: self.flags | Self::FLAG_REMOTE_ELIGIBLE,
86        }
87    }
88
89    pub const fn with_read_only(self) -> Self {
90        Self {
91            flags: self.flags | Self::FLAG_READ_ONLY,
92        }
93    }
94
95    pub const fn with_skip_validation(self) -> Self {
96        Self {
97            flags: self.flags | Self::FLAG_SKIP_VALIDATION,
98        }
99    }
100
101    pub const fn with_model_discovery(self) -> Self {
102        Self {
103            flags: self.flags | Self::FLAG_MODEL_DISCOVERY,
104        }
105    }
106}
107
108/// What a command is allowed to do when the active profile is a cloud profile.
109///
110/// `LocalOnly` is never routed remotely and runs against whatever the profile
111/// resolves; `ReadOnly` prefers remote when a session is available and falls
112/// back to local with a warning; `Mutating` must route remotely or fail loudly.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum RoutingClass {
115    LocalOnly,
116    ReadOnly,
117    Mutating,
118}
119
120pub trait DescribeCommand {
121    fn descriptor(&self) -> CommandDescriptor;
122}