flowmark 0.3.0

A Markdown auto-formatter for clean diffs and semantic line breaks
Documentation
//! Skill system for Claude Code integration.
//!
//! Ported from Python: `flowmark/skill.py`

use std::path::PathBuf;

/// The embedded SKILL.md content.
pub const SKILL_CONTENT: &str = include_str!("SKILL.md");
/// Embedded documentation content generated at build time.
pub const DOCS_CONTENT: &str = include_str!(concat!(env!("OUT_DIR"), "/flowmark_docs.md"));

/// Get the skill (SKILL.md) content.
pub fn get_skill_content() -> &'static str {
    SKILL_CONTENT
}

/// Get documentation content. Tries to find README.md relative to the
/// executable, falling back to embedded README content.
pub fn get_docs_content() -> String {
    // Try to find README.md relative to the executable
    if let Ok(exe) = std::env::current_exe() {
        // Check alongside the binary
        if let Some(dir) = exe.parent() {
            let readme = dir.join("README.md");
            if let Ok(content) = std::fs::read_to_string(&readme) {
                return content;
            }
            // Check one level up (e.g., if binary is in bin/ or target/)
            if let Some(parent) = dir.parent() {
                let readme = parent.join("README.md");
                if let Ok(content) = std::fs::read_to_string(&readme) {
                    return content;
                }
            }
        }
    }

    // Fallback: embedded docs content.
    DOCS_CONTENT.to_string()
}

/// Install the flowmark skill to the agent configuration directory.
///
/// Default location: `~/.claude/skills/flowmark/SKILL.md`
/// Custom: `{agent_base}/skills/flowmark/SKILL.md`
///
/// # Errors
///
/// Returns an error if the directory cannot be created or the file cannot be written.
pub fn install_skill(agent_base: Option<&str>) -> Result<(), String> {
    let base: PathBuf = if let Some(custom) = agent_base {
        let p = PathBuf::from(custom);
        // Reject paths with parent-directory traversal components
        if p.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
            return Err(format!("invalid --agent-base path (contains '..'): {custom}"));
        }
        p
    } else {
        let Some(home) = dirs::home_dir() else {
            return Err("Could not determine home directory".to_string());
        };
        home.join(".claude")
    };

    let skill_dir = base.join("skills").join("flowmark");
    let skill_path = skill_dir.join("SKILL.md");

    std::fs::create_dir_all(&skill_dir)
        .map_err(|e| format!("failed to create skill directory: {e}"))?;

    std::fs::write(&skill_path, SKILL_CONTENT)
        .map_err(|e| format!("failed to write skill file: {e}"))?;

    eprintln!("Installed flowmark skill to {}", skill_path.display());

    if agent_base.is_some() {
        eprintln!("Tip: Commit .claude/skills/ to share with team");
    }

    Ok(())
}