Repository: block/goose
File: crates/goose-cli/src/commands/configure.rs (Rust)

Code before:
            )
            .interact()?
            .to_string())
    }
}

/// Store secret with graceful keyring fallback
fn store_secret_with_fallback(config: &Config, key: &str, value: &str) -> anyhow::Result<()> {
    match config.set_secret(key, &value.to_string()) {
        Ok(_) => {
            let _ = cliclack::log::success("Successfully stored secret in keyring");
        }
        Err(ConfigError::FallbackToFileStorage) => {
            handle_keyring_fallback()?;
            let _ = cliclack::log::success("Successfully stored secret using file-based storage");
        }
        Err(e) => {
            cliclack::outro(
                style(format!("Failed to store secret: {}", e))
                    .on_red()
                    .white(),
            )?;
            return Err(anyhow::anyhow!("Failed to store secret"));
        }
    }
    Ok(())
}

/// Handle keyring storage failure with user-friendly fallback message
fn handle_keyring_fallback() -> anyhow::Result<()> {
    let _ = cliclack::log::warning(
        "Keyring service unavailable. Falling back to file-based storage for secrets.",
    );
    let _ = cliclack::log::info("For better security, consider:");
    let _ = cliclack::log::info("  - Fixing your system's keyring service");
    let _ = cliclack::log::info("  - Or using environment variables for sensitive data");
    let _ = cliclack::log::success("Configuration will continue normally with file-based storage");
    Ok(())
}

pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
    // Get global config instance
    let config = Config::global();

    // Get all available providers and their metadata
    let mut available_providers = providers().await;

    // Sort providers alphabetically by display name
    available_providers.sort_by(|a, b| a.0.display_name.cmp(&b.0.display_name));

    // Create selection items from provider metadata

Review comment (security):
is this actually storing the secret? I don't see it? also again, I feel this is quite noisy; if we can make it so that it warns once saying keyring is not working, it would be fine. it could even auto dissable the flag?

Code after:
        // just a few models, show all without search for better UX
        Ok(cliclack::select("Select a model:")
            .items(
                &models
                    .iter()
                    .map(|m| (m, m.as_str(), ""))
                    .collect::<Vec<_>>(),
            )
            .interact()?
            .to_string())
    }
}

fn try_store_secret(config: &Config, key_name: &str, value: String) -> anyhow::Result<bool> {
    match config.set_secret(key_name, &value) {
        Ok(_) => Ok(true),
        Err(ConfigError::FallbackToFileStorage) => Ok(true),
        Err(e) => {
            cliclack::outro(style(format!(
                "Failed to store {} securely: {}. Please ensure your system's secure storage is accessible. Alternatively you can run with GOOSE_DISABLE_KEYRING=true or set the key in your environment variables",
                key_name, e
            )).on_red().white())?;
            Ok(false)
        }
    }
}

pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
    // Get global config instance
    let config = Config::global();

    // Get all available providers and their metadata
    let mut available_providers = providers().await;

    // Sort providers alphabetically by display name
    available_providers.sort_by(|a, b| a.0.display_name.cmp(&b.0.display_name));

    // Create selection items from provider metadata
    let provider_items: Vec<(&String, &str, &str)> = available_providers
        .iter()
        .map(|(p, _)| (&p.name, p.display_name.as_str(), p.description.as_str()))
        .collect();

    // Get current default provider if it exists
    let current_provider: Option<String> = config.get_goose_provider().ok();
    let default_provider = current_provider.unwrap_or_default();

    // Select provider
    let provider_name = cliclack::select("Which model provider should we use?")
        .initial_value(&default_provider)
        .items(&provider_items)