Skip to main content

roder_api/
cli_identity.rs

1//! The command-line program a Roder runtime is embedded in.
2//!
3//! Roder's own CLI is `roder`, but other programs embed the runtime and ship
4//! their own command line: a hint such as "run `roder auth login codex`" means
5//! nothing to their users. An embedding program sets its identity once at
6//! startup with [`set_cli_identity`]; every user-facing sign-in hint and the
7//! provider sign-in token folder (Codex, SuperGrok, Kimi Code) then follow
8//! it. Until one is set, Roder's defaults apply; reading the identity never
9//! fixes it, so setting it later still takes effect.
10
11use std::path::PathBuf;
12use std::sync::{LazyLock, OnceLock};
13
14/// The `{provider}` placeholder in [`CliIdentity::auth_login`].
15pub const PROVIDER_PLACEHOLDER: &str = "{provider}";
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct CliIdentity {
19    /// The program name users type, such as `roder`.
20    pub name: String,
21    /// The command that signs in to a provider, with `{provider}` where the
22    /// provider id goes, such as `roder auth login {provider}`.
23    pub auth_login: String,
24    /// The folder provider sign-in tokens are kept in, used as given.
25    /// `None` keeps them in `auth/` under Roder's data folder
26    /// (`RODER_DATA_DIR`, else `RODER_CONFIG_DIR`, else `~/.roder`).
27    pub auth_dir: Option<PathBuf>,
28}
29
30impl CliIdentity {
31    /// The command a user runs to sign in to `provider` (`codex`, `supergrok`, ...).
32    pub fn auth_login_command(&self, provider: &str) -> String {
33        self.auth_login.replace(PROVIDER_PLACEHOLDER, provider)
34    }
35}
36
37impl Default for CliIdentity {
38    fn default() -> Self {
39        Self {
40            name: "roder".to_string(),
41            auth_login: format!("roder auth login {PROVIDER_PLACEHOLDER}"),
42            auth_dir: None,
43        }
44    }
45}
46
47static IDENTITY: OnceLock<CliIdentity> = OnceLock::new();
48static RODER: LazyLock<CliIdentity> = LazyLock::new(CliIdentity::default);
49
50/// Name the program embedding Roder, before any provider or sign-in runs.
51/// The first identity set wins; a later one is returned as the error.
52pub fn set_cli_identity(identity: CliIdentity) -> Result<(), CliIdentity> {
53    IDENTITY.set(identity)
54}
55
56/// The embedding program's identity, or Roder's own while none is set.
57pub fn cli_identity() -> &'static CliIdentity {
58    IDENTITY.get().unwrap_or(&RODER)
59}
60
61/// The folder provider sign-in tokens go in: the embedding program's
62/// `auth_dir`, else `default` (a token store's `auth/` under Roder's data
63/// folder).
64pub fn auth_dir_or(default: PathBuf) -> PathBuf {
65    cli_identity().auth_dir.clone().unwrap_or(default)
66}
67
68/// The command a user of this program runs to sign in to `provider`.
69pub fn auth_login_command(provider: &str) -> String {
70    cli_identity().auth_login_command(provider)
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn the_sign_in_command_names_the_provider() {
79        assert_eq!(
80            CliIdentity::default().auth_login_command("codex"),
81            "roder auth login codex"
82        );
83        let embedded = CliIdentity {
84            name: "com".to_string(),
85            auth_login: "com agent login {provider}".to_string(),
86            auth_dir: None,
87        };
88        assert_eq!(
89            embedded.auth_login_command("codex"),
90            "com agent login codex"
91        );
92    }
93
94    #[test]
95    fn reading_the_identity_leaves_it_settable() {
96        // The only test in this binary that sets the process-wide identity.
97        assert_eq!(cli_identity().name, "roder");
98        assert_eq!(
99            auth_dir_or(PathBuf::from("/default")),
100            PathBuf::from("/default")
101        );
102        let embedded = CliIdentity {
103            name: "com".to_string(),
104            auth_login: "com agent login {provider}".to_string(),
105            auth_dir: Some(PathBuf::from("/tokens")),
106        };
107        set_cli_identity(embedded.clone()).unwrap();
108        assert_eq!(cli_identity(), &embedded);
109        assert_eq!(auth_login_command("codex"), "com agent login codex");
110        assert_eq!(
111            auth_dir_or(PathBuf::from("/default")),
112            PathBuf::from("/tokens")
113        );
114        assert!(set_cli_identity(CliIdentity::default()).is_err());
115    }
116}