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