use std::path::PathBuf;
use std::{collections::hash_map::DefaultHasher, hash::Hash, hash::Hasher};
const DEFAULT_API_URL: &str = "https://mcp.falsegreen.com/v1/mcp";
pub fn api_url() -> String {
std::env::var("FALSEGREEN_API_URL").unwrap_or_else(|_| DEFAULT_API_URL.to_string())
}
pub fn workspace_url() -> String {
if let Ok(url) = std::env::var("FALSEGREEN_WORKSPACE_URL") {
return url;
}
let mcp = api_url();
if let Some(prefix) = mcp.strip_suffix("/v1/mcp") {
format!("{prefix}/v1/workspace")
} else if let Some(prefix) = mcp.strip_suffix("/mcp") {
format!("{prefix}/workspace")
} else {
format!("{}/workspace", mcp.trim_end_matches('/'))
}
}
pub fn config_dir() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join("falsegreen")
}
pub fn credentials_path() -> PathBuf {
config_dir().join("credentials.toml")
}
pub fn device_id_path() -> PathBuf {
config_dir().join("device_id")
}
pub fn principal_id_path() -> PathBuf {
config_dir().join("principal_id")
}
fn new_device_id() -> String {
let mut seed = String::new();
for name in ["HOSTNAME", "COMPUTERNAME", "USER", "USERNAME"] {
if let Ok(value) = std::env::var(name) {
seed.push_str(name);
seed.push_str(&value);
}
}
seed.push_str(std::env::consts::OS);
seed.push_str(std::env::consts::ARCH);
seed.push_str(&std::process::id().to_string());
seed.push_str(
&std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
.to_string(),
);
let mut first = DefaultHasher::new();
("falsegreen-device-1", &seed).hash(&mut first);
let mut second = DefaultHasher::new();
("falsegreen-device-2", &seed).hash(&mut second);
format!("{:016x}{:016x}", first.finish(), second.finish())
}
pub fn load_or_create_device_id() -> std::io::Result<String> {
let path = device_id_path();
if let Ok(existing) = std::fs::read_to_string(&path) {
let existing = existing.trim();
if (16..=64).contains(&existing.len())
&& existing
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
{
return Ok(existing.to_string());
}
}
let dir = config_dir();
std::fs::create_dir_all(&dir)?;
let device_id = new_device_id();
std::fs::write(&path, format!("{device_id}\n"))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(device_id)
}
pub fn load_or_create_principal_id() -> std::io::Result<String> {
let path = principal_id_path();
if let Ok(existing) = std::fs::read_to_string(&path) {
let existing = existing.trim();
if (16..=128).contains(&existing.len())
&& existing
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
{
return Ok(existing.to_string());
}
}
let dir = config_dir();
std::fs::create_dir_all(&dir)?;
let principal_id = format!("principal_{}", new_device_id());
std::fs::write(&path, format!("{principal_id}\n"))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(principal_id)
}
pub fn load_token() -> Option<String> {
if let Ok(key) = std::env::var("FALSEGREEN_KEY")
&& !key.is_empty()
{
return Some(key);
}
let path = credentials_path();
let content = std::fs::read_to_string(&path).ok()?;
let parsed: toml::Value = toml::from_str(&content).ok()?;
parsed
.get("token")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
pub fn save_credentials(token: &str, session_id: &str) -> std::io::Result<()> {
let dir = config_dir();
std::fs::create_dir_all(&dir)?;
let content = format!("token = \"{}\"\nsession_id = \"{}\"\n", token, session_id);
let path = credentials_path();
std::fs::write(&path, content)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(())
}
pub fn load_session_id() -> Option<String> {
let path = credentials_path();
let content = std::fs::read_to_string(&path).ok()?;
let parsed: toml::Value = toml::from_str(&content).ok()?;
parsed
.get("session_id")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
pub fn clear_token() -> std::io::Result<()> {
let path = credentials_path();
if path.exists() {
std::fs::remove_file(path)?;
}
Ok(())
}