fix(config): use platform-specific keyring features and standardize config path

- Use apple-native keyring feature on macOS instead of linux-only sync-secret-service
- Use windows-native keyring feature on Windows
- Move config directory to ~/.config/tangled on all platforms for consistency
- Improve keychain error messages

💘 Generated with Crush

Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>

dunkirk.sh f2ef3568 2950b681

verified
Changed files
+50 -10
crates
tangled-config
+30 -3
Cargo.lock
··· 388 388 ] 389 389 390 390 [[package]] 391 + name = "core-foundation" 392 + version = "0.10.1" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 395 + dependencies = [ 396 + "core-foundation-sys", 397 + "libc", 398 + ] 399 + 400 + [[package]] 391 401 name = "core-foundation-sys" 392 402 version = "0.8.7" 393 403 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1117 1127 source = "registry+https://github.com/rust-lang/crates.io-index" 1118 1128 checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" 1119 1129 dependencies = [ 1130 + "byteorder", 1120 1131 "dbus-secret-service", 1121 1132 "log", 1122 1133 "openssl", 1134 + "security-framework 2.11.1", 1135 + "security-framework 3.5.1", 1136 + "windows-sys 0.60.2", 1123 1137 "zeroize", 1124 1138 ] 1125 1139 ··· 1316 1330 "openssl-probe", 1317 1331 "openssl-sys", 1318 1332 "schannel", 1319 - "security-framework", 1333 + "security-framework 2.11.1", 1320 1334 "security-framework-sys", 1321 1335 "tempfile", 1322 1336 ] ··· 1831 1845 checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1832 1846 dependencies = [ 1833 1847 "bitflags", 1834 - "core-foundation", 1848 + "core-foundation 0.9.4", 1849 + "core-foundation-sys", 1850 + "libc", 1851 + "security-framework-sys", 1852 + ] 1853 + 1854 + [[package]] 1855 + name = "security-framework" 1856 + version = "3.5.1" 1857 + source = "registry+https://github.com/rust-lang/crates.io-index" 1858 + checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 1859 + dependencies = [ 1860 + "bitflags", 1861 + "core-foundation 0.10.1", 1835 1862 "core-foundation-sys", 1836 1863 "libc", 1837 1864 "security-framework-sys", ··· 2055 2082 checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2056 2083 dependencies = [ 2057 2084 "bitflags", 2058 - "core-foundation", 2085 + "core-foundation 0.9.4", 2059 2086 "system-configuration-sys", 2060 2087 ] 2061 2088
+1 -1
Cargo.toml
··· 41 41 42 42 # Storage 43 43 dirs = "5.0" 44 - keyring = { version = "3.6", features = ["sync-secret-service", "vendored"] } 44 + keyring = "3.6" 45 45 46 46 # Error Handling 47 47 anyhow = "1.0"
+9 -1
crates/tangled-config/Cargo.toml
··· 8 8 [dependencies] 9 9 anyhow = { workspace = true } 10 10 dirs = { workspace = true } 11 - keyring = { workspace = true } 12 11 serde = { workspace = true, features = ["derive"] } 13 12 serde_json = { workspace = true } 14 13 toml = { workspace = true } 15 14 chrono = { workspace = true } 16 15 16 + [target.'cfg(target_os = "macos")'.dependencies] 17 + keyring = { workspace = true, features = ["apple-native"] } 18 + 19 + [target.'cfg(target_os = "linux")'.dependencies] 20 + keyring = { workspace = true, features = ["sync-secret-service", "vendored"] } 21 + 22 + [target.'cfg(target_os = "windows")'.dependencies] 23 + keyring = { workspace = true, features = ["windows-native"] } 24 +
+8 -3
crates/tangled-config/src/config.rs
··· 2 2 use std::path::{Path, PathBuf}; 3 3 4 4 use anyhow::{Context, Result}; 5 - use dirs::config_dir; 6 5 use serde::{Deserialize, Serialize}; 7 6 8 7 #[derive(Debug, Clone, Serialize, Deserialize, Default)] ··· 55 54 } 56 55 57 56 pub fn default_config_path() -> Result<PathBuf> { 58 - let base = config_dir().context("Could not determine platform config directory")?; 59 - Ok(base.join("tangled").join("config.toml")) 57 + // Use ~/.config/tangled on all platforms for consistency 58 + let home = std::env::var("HOME") 59 + .or_else(|_| std::env::var("USERPROFILE")) 60 + .context("Could not determine home directory")?; 61 + Ok(PathBuf::from(home) 62 + .join(".config") 63 + .join("tangled") 64 + .join("config.toml")) 60 65 } 61 66 62 67 pub fn load_config(path: Option<&Path>) -> Result<Option<RootConfig>> {
+2 -2
crates/tangled-config/src/keychain.rs
··· 21 21 pub fn set_password(&self, secret: &str) -> Result<()> { 22 22 self.entry()? 23 23 .set_password(secret) 24 - .map_err(|e| anyhow!("keyring error: {e}")) 24 + .map_err(|e| anyhow!("Failed to save credentials to keychain: {e}")) 25 25 } 26 26 27 27 pub fn get_password(&self) -> Result<String> { 28 28 self.entry()? 29 29 .get_password() 30 - .map_err(|e| anyhow!("keyring error: {e}")) 30 + .map_err(|e| anyhow!("Failed to load credentials from keychain: {e}")) 31 31 } 32 32 33 33 pub fn delete_password(&self) -> Result<()> {