Skip to main content

auths_cli/commands/
config.rs

1//! `auths config` command — view and modify `~/.auths/config.toml`.
2
3use crate::commands::executable::ExecutableCommand;
4use crate::config::CliConfig;
5use anyhow::{Result, bail};
6use auths_sdk::core_config::{AuthsConfig, PassphraseCachePolicy, load_config, save_config};
7
8use crate::adapters::config_store::FileConfigStore;
9use clap::{Parser, Subcommand};
10
11/// Manage Auths configuration.
12#[derive(Parser, Debug, Clone)]
13#[command(
14    name = "config",
15    about = "View and modify Auths configuration",
16    after_help = "Configuration file: ~/.auths/config.toml
17
18Examples:
19  auths config show                               # View all settings
20  auths config get passphrase.cache               # Check caching status
21  auths config set passphrase.cache always        # Cache passphrases
22
23Valid Keys:
24  passphrase.cache       — 'never', 'session', 'always'
25  passphrase.duration    — seconds until cache expires
26  passphrase.biometric   — 'enabled', 'disabled' (macOS)
27
28Related:
29  auths doctor  — Check system configuration"
30)]
31pub struct ConfigCommand {
32    #[command(subcommand)]
33    pub action: ConfigAction,
34}
35
36/// Config subcommands.
37#[derive(Subcommand, Debug, Clone)]
38pub enum ConfigAction {
39    /// Set a configuration value (e.g. `auths config set passphrase.cache always`).
40    Set {
41        /// Dotted key path (e.g. `passphrase.cache`, `passphrase.duration`).
42        key: String,
43        /// Value to assign.
44        value: String,
45    },
46    /// Get a configuration value (e.g. `auths config get passphrase.cache`).
47    Get {
48        /// Dotted key path.
49        key: String,
50    },
51    /// Show the full configuration.
52    Show,
53}
54
55impl ExecutableCommand for ConfigCommand {
56    fn execute(&self, _ctx: &CliConfig) -> Result<()> {
57        match &self.action {
58            ConfigAction::Set { key, value } => execute_set(key, value),
59            ConfigAction::Get { key } => execute_get(key),
60            ConfigAction::Show => execute_show(),
61        }
62    }
63}
64
65fn execute_set(key: &str, value: &str) -> Result<()> {
66    let store = FileConfigStore;
67    let mut config = load_config(&store);
68
69    match key {
70        "passphrase.cache" => {
71            config.passphrase.cache = parse_cache_policy(value)?;
72        }
73        "passphrase.duration" => {
74            auths_sdk::keychain::parse_duration_str(value).ok_or_else(|| {
75                anyhow::anyhow!(
76                    "Invalid duration '{}'. Use formats like '7d', '24h', '30m', '3600s'.",
77                    value
78                )
79            })?;
80            config.passphrase.duration = Some(value.to_string());
81        }
82        "passphrase.biometric" => {
83            config.passphrase.biometric = parse_bool(value)?;
84        }
85        _ => bail!(
86            "Unknown config key '{}'. Valid keys: passphrase.cache, passphrase.duration, passphrase.biometric",
87            key
88        ),
89    }
90
91    save_config(&config, &store)?;
92    println!("Set {} = {}", key, value);
93    Ok(())
94}
95
96fn execute_get(key: &str) -> Result<()> {
97    let config = load_config(&FileConfigStore);
98
99    match key {
100        "passphrase.cache" => {
101            let label = policy_label(&config.passphrase.cache);
102            println!("{}", label);
103        }
104        "passphrase.duration" => {
105            println!(
106                "{}",
107                config.passphrase.duration.as_deref().unwrap_or("(not set)")
108            );
109        }
110        "passphrase.biometric" => {
111            println!("{}", config.passphrase.biometric);
112        }
113        _ => bail!(
114            "Unknown config key '{}'. Valid keys: passphrase.cache, passphrase.duration, passphrase.biometric",
115            key
116        ),
117    }
118
119    Ok(())
120}
121
122fn execute_show() -> Result<()> {
123    let config = load_config(&FileConfigStore);
124    if crate::ux::format::is_json_mode() {
125        let json = serde_json::to_string_pretty(&config)
126            .map_err(|e| anyhow::anyhow!("Failed to serialize config as JSON: {}", e))?;
127        println!("{}", json);
128    } else {
129        let toml_str = toml::to_string_pretty(&config)
130            .map_err(|e| anyhow::anyhow!("Failed to serialize config: {}", e))?;
131        println!("{}", toml_str);
132    }
133    Ok(())
134}
135
136fn parse_cache_policy(s: &str) -> Result<PassphraseCachePolicy> {
137    match s.to_lowercase().as_str() {
138        "always" => Ok(PassphraseCachePolicy::Always),
139        "session" => Ok(PassphraseCachePolicy::Session),
140        "duration" => Ok(PassphraseCachePolicy::Duration),
141        "never" => Ok(PassphraseCachePolicy::Never),
142        _ => bail!(
143            "Invalid cache policy '{}'. Valid values: always, session, duration, never",
144            s
145        ),
146    }
147}
148
149fn policy_label(policy: &PassphraseCachePolicy) -> &'static str {
150    match policy {
151        PassphraseCachePolicy::Always => "always",
152        PassphraseCachePolicy::Session => "session",
153        PassphraseCachePolicy::Duration => "duration",
154        PassphraseCachePolicy::Never => "never",
155    }
156}
157
158fn parse_bool(s: &str) -> Result<bool> {
159    match s.to_lowercase().as_str() {
160        "true" | "1" | "yes" => Ok(true),
161        "false" | "0" | "no" => Ok(false),
162        _ => bail!("Invalid boolean '{}'. Use true/false, yes/no, or 1/0", s),
163    }
164}
165
166fn _ensure_default_config_exists() -> Result<AuthsConfig> {
167    let store = FileConfigStore;
168    let config = load_config(&store);
169    save_config(&config, &store)?;
170    Ok(config)
171}