fundaia 0.52.1

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
//! A Claude Code skill generated from this binary, so it always lists the commands it can run.

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>`.
- Sin `--env <entorno>` todo va al entorno por defecto (producción); `fundaia envs <proyecto>` los lista.
- `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")
        );
    }
}