Skip to main content

homeassistant_cli/commands/
config.rs

1use crate::config;
2use crate::output::{OutputConfig, mask_credential};
3
4pub fn show(out: &OutputConfig, profile_arg: Option<&str>) {
5    let _ = profile_arg; // profile_arg reserved for future use; show always displays all profiles
6    let summary = config::config_summary();
7
8    if out.is_json() {
9        let profiles_json: Vec<serde_json::Value> = summary
10            .profiles
11            .iter()
12            .map(|p| {
13                serde_json::json!({
14                    "name": p.name,
15                    "url": p.url,
16                    "token": p.token.as_deref().map(mask_credential)
17                })
18            })
19            .collect();
20
21        out.print_data(
22            &serde_json::to_string_pretty(&serde_json::json!({
23                "ok": true,
24                "data": {
25                    "config_file": summary.config_file,
26                    "file_exists": summary.file_exists,
27                    "profiles": profiles_json,
28                    "env": {
29                        "HA_URL": summary.env_url,
30                        "HA_TOKEN": summary.env_token.as_deref().map(mask_credential),
31                        "HA_PROFILE": summary.env_profile
32                    }
33                }
34            }))
35            .expect("serialize"),
36        );
37    } else {
38        println!("Config file: {}", summary.config_file.display());
39        if !summary.file_exists {
40            println!("  (not found — run `ha init` to create it)");
41            return;
42        }
43        for p in &summary.profiles {
44            println!("\n[{}]", p.name);
45            println!("  url   = {}", p.url.as_deref().unwrap_or("(not set)"));
46            println!(
47                "  token = {}",
48                p.token
49                    .as_deref()
50                    .map(mask_credential)
51                    .unwrap_or_else(|| "(not set)".into())
52            );
53        }
54        if summary.env_url.is_some() || summary.env_token.is_some() || summary.env_profile.is_some()
55        {
56            println!("\nEnvironment overrides:");
57            if let Some(v) = &summary.env_url {
58                println!("  HA_URL={v}");
59            }
60            if let Some(v) = &summary.env_token {
61                println!("  HA_TOKEN={}", mask_credential(v));
62            }
63            if let Some(v) = &summary.env_profile {
64                println!("  HA_PROFILE={v}");
65            }
66        }
67    }
68}
69
70pub fn set(out: &OutputConfig, profile_arg: Option<&str>, key: &str, value: &str) {
71    if key != "url" && key != "token" {
72        eprintln!("Unknown config key '{key}'. Valid keys: url, token");
73        std::process::exit(crate::output::exit_codes::GENERAL_ERROR);
74    }
75
76    let path = config::config_path();
77    let profile = profile_arg.unwrap_or("default");
78
79    let (current_url, current_token) =
80        config::read_profile_credentials(&path, profile).unwrap_or_default();
81
82    let (url, token) = if key == "url" {
83        (value.to_owned(), current_token)
84    } else {
85        (current_url, value.to_owned())
86    };
87
88    if let Err(e) = config::write_profile(&path, profile, &url, &token) {
89        eprintln!("{e}");
90        std::process::exit(crate::output::exit_codes::GENERAL_ERROR);
91    }
92
93    if out.is_json() {
94        out.print_data(
95            &serde_json::to_string_pretty(&serde_json::json!({
96                "ok": true,
97                "data": {"key": key, "profile": profile}
98            }))
99            .expect("serialize"),
100        );
101    } else {
102        println!("✔ Set {} for profile '{}'", key, profile);
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::output::{OutputConfig, OutputFormat};
110    use crate::test_support::{EnvVarGuard, ProcessEnvLock, write_config};
111    use tempfile::TempDir;
112
113    fn json_out() -> OutputConfig {
114        OutputConfig::new(Some(OutputFormat::Json), false)
115    }
116
117    #[test]
118    fn show_does_not_panic_with_no_config() {
119        let _lock = ProcessEnvLock::acquire().unwrap();
120        let dir = TempDir::new().unwrap();
121        let _env = EnvVarGuard::set("XDG_CONFIG_HOME", &dir.path().to_string_lossy());
122        // Just verify no panic
123        show(&json_out(), None);
124    }
125
126    #[test]
127    fn set_writes_url_to_config() {
128        let _lock = ProcessEnvLock::acquire().unwrap();
129        let dir = TempDir::new().unwrap();
130        write_config(
131            dir.path(),
132            "[default]\nurl = \"http://old:8123\"\ntoken = \"old-token\"\n",
133        )
134        .unwrap();
135        let _env = EnvVarGuard::set("XDG_CONFIG_HOME", &dir.path().to_string_lossy());
136
137        set(&json_out(), None, "url", "http://new:8123");
138
139        let path = dir.path().join("ha").join("config.toml");
140        let content = std::fs::read_to_string(&path).unwrap();
141        assert!(content.contains("http://new:8123"));
142        assert!(content.contains("old-token"), "token must not be changed");
143    }
144}