Skip to main content

systemprompt_cli/
env_overrides.rs

1//! Process-environment snapshot for the CLI.
2//!
3//! [`EnvOverrides`] captures every environment variable the CLI consults, read
4//! once at process start ([`EnvOverrides::from_process_env`]) and threaded
5//! through [`crate::context::CommandContext`]. Command code never calls
6//! `std::env::var` directly — tests construct the snapshot with
7//! [`EnvOverrides::from_vars`] instead of mutating process state.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use std::collections::HashMap;
13
14use systemprompt_identifiers::{ContextId, SessionId, SessionToken, UserId};
15
16/// `is_deployment_host` means the process runs on the host the active profile
17/// describes, so a command must run locally instead of routing to the
18/// deployment it is already inside.
19#[derive(Debug, Clone, Default)]
20pub struct EnvOverrides {
21    pub output_format: Option<String>,
22    pub log_level: Option<String>,
23    pub no_color: bool,
24    pub non_interactive: bool,
25    pub profile: Option<String>,
26    pub rust_log: Option<String>,
27    pub is_deployment_host: bool,
28    pub is_remote_cli: bool,
29    pub session: SessionEnv,
30}
31
32#[derive(Debug, Clone, Default)]
33pub struct SessionEnv {
34    pub user_id: Option<UserId>,
35    pub session_id: Option<SessionId>,
36    pub context_id: Option<ContextId>,
37    pub auth_token: Option<SessionToken>,
38}
39
40impl EnvOverrides {
41    #[must_use]
42    pub fn from_process_env() -> Self {
43        Self::from_lookup(|key| std::env::var(key).ok())
44    }
45
46    #[must_use]
47    pub fn from_vars<I, K, V>(vars: I) -> Self
48    where
49        I: IntoIterator<Item = (K, V)>,
50        K: Into<String>,
51        V: Into<String>,
52    {
53        let map: HashMap<String, String> = vars
54            .into_iter()
55            .map(|(k, v)| (k.into(), v.into()))
56            .collect();
57        Self::from_lookup(|key| map.get(key).cloned())
58    }
59
60    fn from_lookup(lookup: impl Fn(&str) -> Option<String>) -> Self {
61        Self {
62            output_format: lookup("SYSTEMPROMPT_OUTPUT_FORMAT"),
63            log_level: lookup("SYSTEMPROMPT_LOG_LEVEL"),
64            no_color: lookup("SYSTEMPROMPT_NO_COLOR").is_some() || lookup("NO_COLOR").is_some(),
65            non_interactive: lookup("SYSTEMPROMPT_NON_INTERACTIVE").is_some(),
66            profile: lookup("SYSTEMPROMPT_PROFILE"),
67            rust_log: lookup("RUST_LOG"),
68            is_deployment_host: systemprompt_models::subprocess::is_deployment_host(&lookup),
69            is_remote_cli: lookup("SYSTEMPROMPT_CLI_REMOTE").is_some(),
70            session: SessionEnv {
71                user_id: lookup("SYSTEMPROMPT_USER_ID").and_then(|value| {
72                    UserId::try_new(value)
73                        .inspect_err(|error| {
74                            tracing::warn!(error = %error, "ignoring malformed SYSTEMPROMPT_USER_ID");
75                        })
76                        .ok()
77                }),
78                session_id: lookup("SYSTEMPROMPT_SESSION_ID").and_then(|value| {
79                    SessionId::try_new(value)
80                        .inspect_err(|error| {
81                            tracing::warn!(
82                                error = %error,
83                                "ignoring malformed SYSTEMPROMPT_SESSION_ID"
84                            );
85                        })
86                        .ok()
87                }),
88                context_id: lookup("SYSTEMPROMPT_CONTEXT_ID").and_then(|value| {
89                    ContextId::try_new(value)
90                        .inspect_err(|error| {
91                            tracing::warn!(
92                                error = %error,
93                                "ignoring malformed SYSTEMPROMPT_CONTEXT_ID; expected a UUID"
94                            );
95                        })
96                        .ok()
97                }),
98                auth_token: lookup("SYSTEMPROMPT_AUTH_TOKEN").map(SessionToken::new),
99            },
100        }
101    }
102}