auths_cli/commands/
config.rs1use 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#[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#[derive(Subcommand, Debug, Clone)]
38pub enum ConfigAction {
39 Set {
41 key: String,
43 value: String,
45 },
46 Get {
48 key: String,
50 },
51 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}