use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use clap::Command;
use owo_colors::OwoColorize;
use crate::commands::{guide::GUIDE, schema};
use crate::output::{self, Format};
use crate::render::theme;
const RULES: &str = "## Reglas para el agente
- Pasa `--json` siempre que vayas a leer la salida; los errores llegan como un objeto en stderr.
- Pide confirmación a la persona antes de un comando marcado *(destroys)*.
- Si algo está roto, empieza por `fundaia attention` o `fundaia diagnose <proyecto> <servicio>`.
- `fundaia schema --json` describe cada comando y argumento; `fundaia guide` es el briefing completo.";
pub fn render(root: &Command) -> String {
format!(
"---\nname: fundaia\ndescription: Operar la plataforma de despliegue Fundaia con la CLI `fundaia`: proyectos, servicios, despliegues, variables, logs, diagnóstico, arreglos con Claude Code, vulnerabilidades e issues. Úsala cuando haya que desplegar, investigar un fallo o cambiar algo en Fundaia.\n---\n\n<!-- generada por fundaia {version}; `fundaia skill install` la regenera -->\n\n{GUIDE}\n\n## Comandos\n\n{commands}\n\n{RULES}\n",
version = env!("CARGO_PKG_VERSION"),
commands = schema::reference(root),
)
}
pub fn path(project: bool) -> Result<PathBuf> {
let base = if project {
std::env::current_dir().context("no se pudo leer el directorio actual")?
} else {
dirs::home_dir().context("no se encontró el directorio personal")?
};
Ok(skill_file(&base))
}
fn skill_file(base: &Path) -> PathBuf {
base.join(".claude")
.join("skills")
.join("fundaia")
.join("SKILL.md")
}
pub fn show(root: &Command, format: Format) -> Result<()> {
let text = render(root);
if format.is_json() {
output::emit(
&serde_json::json!({ "version": env!("CARGO_PKG_VERSION"), "markdown": text }),
);
} else {
print!("{text}");
}
Ok(())
}
pub fn install(root: &Command, project: bool, format: Format) -> Result<()> {
let file = path(project)?;
if let Some(directory) = file.parent() {
std::fs::create_dir_all(directory)
.with_context(|| format!("no se pudo crear {}", directory.display()))?;
}
std::fs::write(&file, render(root))
.with_context(|| format!("no se pudo escribir {}", file.display()))?;
if format.is_json() {
output::emit(&serde_json::json!({ "path": file, "version": env!("CARGO_PKG_VERSION") }));
} else {
println!();
println!(
" {} {}",
"Skill instalada en".style(theme::muted()),
file.display()
);
println!(
" {}",
"Vuelve a ejecutar `fundaia skill install` tras actualizar la CLI para que la skill conozca los comandos nuevos."
.style(theme::muted())
);
println!();
}
Ok(())
}
pub fn print_path(project: bool, format: Format) -> Result<()> {
let file = path(project)?;
if format.is_json() {
output::emit(&serde_json::json!({ "path": file }));
} else {
println!("{}", file.display());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
fn rendered() -> String {
let mut command = crate::Cli::command();
command.build();
render(&command)
}
#[test]
fn it_should_open_with_the_front_matter_claude_code_reads() {
assert!(rendered().starts_with("---\nname: fundaia\ndescription: "));
}
#[test]
fn it_should_say_which_version_wrote_it() {
assert!(rendered().contains(&format!(
"generada por fundaia {}",
env!("CARGO_PKG_VERSION")
)));
}
#[test]
fn it_should_list_deploying_with_what_it_does() {
assert!(rendered().contains("- `fundaia deploy"));
}
#[test]
fn it_should_carry_the_guide() {
assert!(rendered().contains("Creating a service deploys it."));
}
#[test]
fn it_should_install_under_the_claude_skills_directory() {
assert_eq!(
skill_file(Path::new("/home/ana")),
PathBuf::from("/home/ana/.claude/skills/fundaia/SKILL.md")
);
}
}