use anyhow::{Context, Result};
use clawgarden_proto::config::SecretsToml;
use std::path::PathBuf;
use crate::garden::load_gardens;
use crate::ui;
fn secrets_path(registry: &crate::garden::GardensRegistry, name: &str) -> PathBuf {
registry.garden_dir(name).join(".secrets.toml")
}
fn load_secrets(name: &str) -> Result<(SecretsToml, PathBuf)> {
let registry = load_gardens()?;
if !registry.exists(name) {
anyhow::bail!("Garden '{}' not found. Run 'garden new' first.", name);
}
let path = secrets_path(®istry, name);
let secrets = SecretsToml::load_from(&path);
Ok((secrets, path))
}
fn migrate_from_env(name: &str) -> Result<bool> {
let registry = load_gardens()?;
let env_path = registry.env_file(name);
let secrets_path = secrets_path(®istry, name);
if secrets_path.exists() {
return Ok(false);
}
if !env_path.exists() {
return Ok(false);
}
let secrets = SecretsToml::from_env_file(&env_path);
if secrets.secrets.is_empty() {
return Ok(false);
}
secrets
.save_to(&secrets_path)
.map_err(|e| anyhow::anyhow!("Failed to write .secrets.toml: {}", e))?;
ui::success(&format!(
"Migrated {} secrets from .env โ .secrets.toml",
secrets.secrets.len()
));
Ok(true)
}
pub fn cmd_list(name: Option<&str>) -> Result<()> {
let name = crate::garden::resolve_garden_name(name)?;
let _ = migrate_from_env(&name);
let (secrets, path) = load_secrets(&name)?;
println!();
ui::section_header_no_step("๐", &format!("Secrets ยท {}", name));
if secrets.secrets.is_empty() {
println!();
ui::hint("No secrets stored.");
ui::hint("Add one with: garden secret set <KEY>");
println!();
return Ok(());
}
let masked = secrets.list_masked();
let mut rows = vec![
(
"๐".to_string(),
"File".to_string(),
path.display().to_string(),
),
(
"๐ข".to_string(),
"Count".to_string(),
format!("{} secret(s)", masked.len()),
),
];
for (key, masked_value) in &masked {
rows.push(("๐".to_string(), key.clone(), masked_value.clone()));
}
ui::summary_box(&format!("๐ {} โ Secrets", name), &rows);
Ok(())
}
pub fn cmd_get(name: Option<&str>, key: &str) -> Result<()> {
let name = crate::garden::resolve_garden_name(name)?;
let _ = migrate_from_env(&name);
let (secrets, _) = load_secrets(&name)?;
match secrets.get(key) {
Some(value) => println!("{}", value),
None => {
anyhow::bail!("Secret '{}' not found.", key);
}
}
Ok(())
}
pub fn cmd_set(name: Option<&str>, key: &str, value: Option<&str>) -> Result<()> {
let name = crate::garden::resolve_garden_name(name)?;
let _ = migrate_from_env(&name);
let (mut secrets, path) = load_secrets(&name)?;
let value = match value {
Some(v) => v.to_string(),
None => {
let prompt_text = format!(" Enter value for {}:", key);
ui::retry_prompt(|| {
inquire::Password::new(&prompt_text)
.with_help_message("Value will be stored in .secrets.toml (gitignored)")
.without_confirmation()
.prompt()
})?
}
};
let is_update = secrets.get(key).is_some();
secrets.set(key.to_string(), value);
secrets
.save_to(&path)
.map_err(|e| anyhow::anyhow!("Failed to save secret: {}", e))?;
regenerate_env(&name)?;
if is_update {
ui::success(&format!("Secret '{}' updated.", key));
} else {
ui::success(&format!("Secret '{}' set.", key));
}
Ok(())
}
pub fn cmd_remove(name: Option<&str>, key: &str) -> Result<()> {
let name = crate::garden::resolve_garden_name(name)?;
let _ = migrate_from_env(&name);
let (mut secrets, path) = load_secrets(&name)?;
if secrets.get(key).is_none() {
anyhow::bail!("Secret '{}' not found.", key);
}
let confirm = ui::retry_prompt(|| {
inquire::Confirm::new(&format!(" Remove secret '{}'?", key))
.with_default(false)
.prompt()
})?;
if !confirm {
ui::warn("Cancelled.");
return Ok(());
}
secrets.remove(key);
secrets
.save_to(&path)
.map_err(|e| anyhow::anyhow!("Failed to save secrets: {}", e))?;
regenerate_env(&name)?;
ui::success(&format!("Secret '{}' removed.", key));
Ok(())
}
pub fn cmd_migrate(name: Option<&str>) -> Result<()> {
let name = crate::garden::resolve_garden_name(name)?;
println!();
ui::section_header_no_step("๐", &format!("Secret Migration ยท {}", name));
println!();
let migrated = migrate_from_env(&name)?;
if !migrated {
ui::hint("No .env secrets to migrate, or .secrets.toml already exists.");
}
Ok(())
}
pub fn cmd_env(name: Option<&str>) -> Result<()> {
let name = crate::garden::resolve_garden_name(name)?;
let _ = migrate_from_env(&name);
let (secrets, _) = load_secrets(&name)?;
print!("{}", secrets.to_env_content());
Ok(())
}
fn regenerate_env(name: &str) -> Result<()> {
let registry = load_gardens()?;
let secrets_path = secrets_path(®istry, name);
let env_path = registry.env_file(name);
let secrets = SecretsToml::load_from(&secrets_path);
let env_content = secrets.to_env_content();
std::fs::write(&env_path, env_content).context("Failed to write .env file")?;
Ok(())
}