tsafe-cli 1.0.21

tsafe CLI — local secret and credential manager (replaces .env files)
Documentation
//! Global config command handler.
//!
//! Implements `tsafe config` subcommands — reading and writing the global
//! `config.json` (default profile, exec trust settings, password backup vault).

use anyhow::Result;
use colored::Colorize;
use tsafe_cli::cli::{ConfigAction, ExecCustomInheritSetting, ExecModeSetting, ToggleSetting};
use tsafe_core::profile;

pub(crate) fn cmd_config(action: ConfigAction) -> Result<()> {
    match action {
        ConfigAction::Show => {
            println!("Config file: {}", profile::config_path().display());
            println!("  default_profile: {}", profile::get_default_profile());
            println!(
                "  quick_unlock.auto_retrieve: {}",
                if profile::get_auto_quick_unlock() {
                    "on"
                } else {
                    "off"
                }
            );
            println!(
                "  quick_unlock.retry_cooldown_secs: {}",
                profile::get_quick_unlock_retry_cooldown_secs()
            );
            println!("  exec.mode: {}", profile::get_exec_mode().as_str());
            println!(
                "  exec.custom_inherit: {}",
                profile::get_exec_custom_inherit_mode().as_str()
            );
            println!(
                "  exec.auto_redact_output: {}",
                if profile::get_exec_auto_redact_output() {
                    "on"
                } else {
                    "off"
                }
            );
            println!(
                "  exec.custom_deny_dangerous_env: {}",
                if profile::get_exec_custom_deny_dangerous_env() {
                    "on"
                } else {
                    "off"
                }
            );
            let extra_sensitive = profile::get_exec_extra_sensitive_parent_vars();
            if extra_sensitive.is_empty() {
                println!("  exec.extra_sensitive_parent_vars: (none)");
                println!("    Add one: tsafe config add-exec-extra-strip OPENAI_API_KEY");
            } else {
                println!(
                    "  exec.extra_sensitive_parent_vars: {}",
                    extra_sensitive.join(", ")
                );
                println!("    Remove one: tsafe config remove-exec-extra-strip OPENAI_API_KEY");
            }
            if let Some(t) = profile::get_backup_new_profile_passwords_to() {
                println!("  backup_new_profile_passwords_to: {t}");
                println!("    When you create another vault, its master password is stored under profile-passwords/<profile> in that vault.");
                println!("    Change with: tsafe config set-backup-vault main | default | off");
            } else {
                println!("  backup_new_profile_passwords_to: (not set — disabled)");
                println!("    Enable: tsafe config set-backup-vault main");
            }
            Ok(())
        }
        ConfigAction::SetAutoQuickUnlock { mode } => {
            let enabled = matches!(mode, ToggleSetting::On);
            profile::set_auto_quick_unlock(enabled).map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} Automatic quick unlock is now {}.",
                "".green(),
                if enabled { "on" } else { "off" }
            );
            if enabled {
                println!(
                    "  Normal vault opens may use the OS credential store unless an agent or env password is already available."
                );
            } else {
                println!(
                    "  Normal vault opens now skip keychain / biometric prompts; use agent unlock, TSAFE_PASSWORD, or type the password instead."
                );
            }
            Ok(())
        }
        ConfigAction::SetQuickUnlockRetryCooldown { seconds } => {
            profile::set_quick_unlock_retry_cooldown_secs(seconds)
                .map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} Automatic quick-unlock retry cooldown is now {} second(s).",
                "".green(),
                seconds
            );
            if seconds == 0 {
                println!("  Automatic quick-unlock failures will not create a retry delay.");
            } else {
                println!(
                    "  After a denied or failed automatic keychain read, tsafe will wait before retrying automatically."
                );
            }
            Ok(())
        }
        ConfigAction::SetExecMode { mode } => {
            let mode = match mode {
                ExecModeSetting::Standard => profile::ExecMode::Standard,
                ExecModeSetting::Hardened => profile::ExecMode::Hardened,
                ExecModeSetting::Custom => profile::ExecMode::Custom,
            };
            profile::set_exec_mode(mode).map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} Default exec mode is now `{}`.",
                "".green(),
                mode.as_str()
            );
            if matches!(mode, profile::ExecMode::Custom) {
                println!(
                    "  Custom uses: exec.custom_inherit={}, exec.auto_redact_output={}, exec.custom_deny_dangerous_env={}",
                    profile::get_exec_custom_inherit_mode().as_str(),
                    if profile::get_exec_auto_redact_output() { "on" } else { "off" },
                    if profile::get_exec_custom_deny_dangerous_env() { "on" } else { "off" }
                );
            }
            Ok(())
        }
        ConfigAction::SetBackupVault { target } => {
            let t = target.trim();
            let lower = t.to_ascii_lowercase();
            if lower == "off" || lower == "none" || lower == "disable" {
                profile::set_backup_new_profile_passwords_to(None)
                    .map_err(|e| anyhow::anyhow!("{e}"))?;
                println!(
                    "{} Stopped copying new vault passwords into another profile.",
                    "".green()
                );
                return Ok(());
            }
            profile::validate_profile_name(t).map_err(|e| anyhow::anyhow!("{e}"))?;
            profile::set_backup_new_profile_passwords_to(Some(t))
                .map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} New vault master passwords will be copied into '{}' at profile-passwords/<new-profile>.",
                "".green(),
                t
            );
            println!("  Create that vault first if needed: tsafe --profile {t} init");
            println!("  Protect '{t}' strongly — anyone who opens it can read those backup keys.");
            Ok(())
        }
        ConfigAction::SetExecRedactOutput { mode } => {
            let enabled = matches!(mode, ToggleSetting::On);
            profile::set_exec_auto_redact_output(enabled).map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} `tsafe exec` custom-mode child output redaction default is now {}.",
                "".green(),
                if enabled { "on" } else { "off" }
            );
            if enabled {
                println!("  Override per run with: tsafe exec --no-redact-output -- <cmd>");
            } else {
                println!("  Override per run with: tsafe exec --redact-output -- <cmd>");
            }
            Ok(())
        }
        ConfigAction::SetExecCustomInherit { mode } => {
            let mode = match mode {
                ExecCustomInheritSetting::Full => profile::ExecCustomInheritMode::Full,
                ExecCustomInheritSetting::Minimal => profile::ExecCustomInheritMode::Minimal,
                ExecCustomInheritSetting::Clean => profile::ExecCustomInheritMode::Clean,
            };
            profile::set_exec_custom_inherit_mode(mode).map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} `tsafe exec` custom-mode inherit strategy is now `{}`.",
                "".green(),
                mode.as_str()
            );
            Ok(())
        }
        ConfigAction::SetExecCustomDenyDangerousEnv { mode } => {
            let enabled = matches!(mode, ToggleSetting::On);
            profile::set_exec_custom_deny_dangerous_env(enabled)
                .map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} `tsafe exec` custom-mode dangerous env handling is now `{}`.",
                "".green(),
                if enabled { "deny" } else { "warn" }
            );
            Ok(())
        }
        ConfigAction::AddExecExtraStrip { name } => {
            profile::add_exec_extra_sensitive_parent_var(&name)
                .map_err(|e| anyhow::anyhow!("{e}"))?;
            println!(
                "{} `tsafe exec` will now strip parent env var `{}` before spawn.",
                "".green(),
                name.trim()
            );
            Ok(())
        }
        ConfigAction::RemoveExecExtraStrip { name } => {
            let removed = profile::remove_exec_extra_sensitive_parent_var(&name)
                .map_err(|e| anyhow::anyhow!("{e}"))?;
            if removed {
                println!(
                    "{} Removed `{}` from the extra exec strip list.",
                    "".green(),
                    name.trim()
                );
            } else {
                println!(
                    "{} `{}` was not present in the extra exec strip list.",
                    "i".blue(),
                    name.trim()
                );
            }
            Ok(())
        }
    }
}