Skip to main content

mermaid_cli/ollama/
cloud_setup.rs

1use anyhow::Result;
2
3/// Whether the Ollama Cloud key is configured — i.e. the `OLLAMA_API_KEY`
4/// environment variable is set to a non-empty value. The key is never read from
5/// or written to `config.toml` (#88).
6#[must_use]
7pub fn is_cloud_configured() -> bool {
8    get_cloud_api_key().is_some()
9}
10
11/// Interactive Ollama Cloud setup. Mermaid resolves the cloud key from the
12/// `OLLAMA_API_KEY` environment variable only — it is never written to
13/// `config.toml` (#88), matching how every other provider key is handled. So
14/// this explains how to set the variable rather than prompting for and saving a
15/// secret to disk. Returns whether the key is currently configured.
16///
17/// # Errors
18///
19/// None today: this only prints instructions and re-reads the environment. The
20/// `Result` is kept because every caller is already in one, and because a
21/// version of this that ever writes anything would need it.
22pub fn setup_cloud_interactive() -> Result<bool> {
23    println!("\n=== Ollama Cloud Setup ===\n");
24    println!("Ollama Cloud runs large models on datacenter-grade hardware.");
25    println!("Cloud models use the :cloud suffix (e.g., kimi-k2-thinking:cloud).\n");
26    println!("Mermaid reads the key from the OLLAMA_API_KEY environment variable");
27    println!("and never writes it to disk. To configure it:\n");
28    println!("  1. Get an API key at https://ollama.com/cloud");
29    println!("  2. Export it in your shell:\n");
30    println!("       export OLLAMA_API_KEY=<your-key>\n");
31    println!("  3. To persist it, add that line to your shell rc (e.g. ~/.bashrc");
32    println!("     or ~/.zshrc), then start a new shell.\n");
33
34    if is_cloud_configured() {
35        println!("OLLAMA_API_KEY is set — cloud models are available.\n");
36    } else {
37        println!("OLLAMA_API_KEY is not set yet; cloud models stay unavailable");
38        println!("until you set it and re-run mermaid.\n");
39    }
40    Ok(is_cloud_configured())
41}
42
43/// Get the Ollama Cloud API key: the `OLLAMA_API_KEY` environment variable,
44/// falling back to the OS keyring (`mermaid login ollama`).
45///
46/// Never persisted to config files (#88); the keyring is the only at-rest
47/// store and it is the OS's. Empty values are treated as unset.
48#[must_use]
49pub fn get_cloud_api_key() -> Option<String> {
50    mermaid_model::utils::resolve_provider_key("ollama", "OLLAMA_API_KEY", None)
51}
52
53/// Check if a model name requires cloud access.
54#[must_use]
55pub fn is_cloud_model(model_name: &str) -> bool {
56    model_name.ends_with(":cloud")
57}
58
59/// Prompt the user to configure cloud access if a cloud model is requested
60/// without `OLLAMA_API_KEY` set.
61///
62/// # Errors
63///
64/// Only [`setup_cloud_interactive`]'s, which today are none. A cloud model
65/// with no key configured is `Ok(false)`, not an error — the caller decides
66/// whether to proceed.
67pub fn prompt_cloud_setup_if_needed(model_name: &str) -> Result<bool> {
68    if !is_cloud_model(model_name) {
69        return Ok(true); // Not a cloud model, proceed.
70    }
71
72    if is_cloud_configured() {
73        return Ok(true); // Already configured, proceed.
74    }
75
76    println!("\nCloud model requested but OLLAMA_API_KEY is not set.");
77    println!("   Model: {model_name}\n");
78
79    setup_cloud_interactive()
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn is_cloud_model_detects_suffix() {
88        assert!(is_cloud_model("kimi-k2-thinking:cloud"));
89        assert!(!is_cloud_model("qwen3-coder:30b"));
90        // Only the exact ":cloud" suffix counts (a "-cloud" tag does not).
91        assert!(!is_cloud_model("qwen3-coder:480b-cloud"));
92    }
93
94    #[test]
95    fn get_cloud_api_key_resolves_from_env_only() {
96        // #88: the key comes from the environment, never from config on disk.
97        temp_env::with_vars([("OLLAMA_API_KEY", Some("sk-test"))], || {
98            assert_eq!(get_cloud_api_key().as_deref(), Some("sk-test"));
99            assert!(is_cloud_configured());
100        });
101        temp_env::with_vars([("OLLAMA_API_KEY", None::<&str>)], || {
102            assert_eq!(get_cloud_api_key(), None);
103            assert!(!is_cloud_configured());
104        });
105        // Empty is treated as unset.
106        temp_env::with_vars([("OLLAMA_API_KEY", Some(""))], || {
107            assert_eq!(get_cloud_api_key(), None);
108        });
109    }
110}