cmn-hypha 0.3.0

CMN CLI tool — spawn, grow, release, taste, bond, and absorb spores on the Code Mycelial Network
Documentation
use std::process::ExitCode;

use agent_first_data::skill::{
    run_skill_admin, SkillAction as AfSkillAction, SkillAgentSelection, SkillOptions, SkillScope,
    SkillSpec,
};

use crate::api::Output;
use crate::cli::{SkillAgentArg, SkillCommand, SkillOptionsArg, SkillScopeArg};

const HYPHA_SKILL: &str = include_str!("SKILL.md");

fn spec() -> SkillSpec<'static> {
    SkillSpec {
        name: "hypha",
        source: HYPHA_SKILL,
        title: "Hypha",
        marker_slug: "hypha",
    }
}

pub fn handle_skill(out: &Output, command: SkillCommand) -> ExitCode {
    let (action, options) = match command {
        SkillCommand::Status(options) => (AfSkillAction::Status, options),
        SkillCommand::Install(options) => (AfSkillAction::Install, options),
        SkillCommand::Uninstall(options) => (AfSkillAction::Uninstall, options),
    };

    let options = to_afdata_options(options);
    match run_skill_admin(&spec(), action, &options) {
        Ok(report) => match serde_json::to_value(report) {
            Ok(value) => out.value(value),
            Err(err) => out.error_hint(
                "serialize_error",
                &format!("Failed to serialize skill report: {err}"),
                Some("report this bug with the command and --output value used"),
            ),
        },
        Err(err) => out.error_hint(
            "skill_error",
            &err.message,
            err.hint.as_deref().or(Some(
                "check hypha skill --help and retry with the suggested options",
            )),
        ),
    }
}

fn to_afdata_options(options: SkillOptionsArg) -> SkillOptions {
    SkillOptions {
        agent: match options.agent {
            SkillAgentArg::All => SkillAgentSelection::All,
            SkillAgentArg::Codex => SkillAgentSelection::Codex,
            SkillAgentArg::ClaudeCode => SkillAgentSelection::ClaudeCode,
            SkillAgentArg::Opencode => SkillAgentSelection::Opencode,
        },
        scope: match options.scope {
            SkillScopeArg::Personal => SkillScope::Personal,
            SkillScopeArg::Project => SkillScope::Project,
        },
        skills_dir: options.skills_dir,
        force: options.force,
    }
}