clipmem 0.5.3

macOS clipboard memory backed by SQLite and searchable from agent runtimes
Documentation
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;

use anyhow::Result;

use super::support::{find_executable, home_dir, PackagedSkillFile};

pub(in crate::cli) const OPENCLAW_SKILL_NAME: &str = "clipboard-memory";
pub(in crate::cli) const OPENCLAW_SHARED_ROOT: &str = ".openclaw/skills";
pub(in crate::cli) const OPENCLAW_WORKSPACE_ROOT: &str = ".openclaw/workspace";
pub(in crate::cli) const HERMES_SKILL_NAME: &str = "clipboard-memory";
pub(in crate::cli) const HERMES_SKILL_ROOT: &str = ".hermes/skills/productivity";

pub(in crate::cli) const OPENCLAW_SKILL_MD: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/SKILL.md"
));
pub(in crate::cli) const OPENCLAW_COMMANDS_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/references/commands.md"
));
pub(in crate::cli) const OPENCLAW_TROUBLESHOOTING_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/references/troubleshooting.md"
));
pub(in crate::cli) const OPENCLAW_JSON_SCHEMA_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/references/json-schema.md"
));
pub(in crate::cli) const OPENCLAW_EXAMPLES_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/references/examples.md"
));
pub(in crate::cli) const OPENCLAW_SETUP_CHECK_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/references/setup-check.md"
));
pub(in crate::cli) const OPENCLAW_CHECK_SETUP_SH: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/openclaw/clipboard-memory/scripts/check-setup.sh"
));

pub(in crate::cli) const HERMES_SKILL_MD: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/SKILL.md"
));
pub(in crate::cli) const HERMES_COMMANDS_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/references/commands.md"
));
pub(in crate::cli) const HERMES_TROUBLESHOOTING_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/references/troubleshooting.md"
));
pub(in crate::cli) const HERMES_JSON_SCHEMA_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/references/json-schema.md"
));
pub(in crate::cli) const HERMES_EXAMPLES_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/references/examples.md"
));
pub(in crate::cli) const HERMES_SETUP_CHECK_REF: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/references/setup-check.md"
));
pub(in crate::cli) const HERMES_CHECK_SETUP_SH: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/extras/hermes/clipboard-memory/scripts/check-setup.sh"
));

pub(in crate::cli) fn packaged_openclaw_skill() -> String {
    OPENCLAW_SKILL_MD.to_string()
}

pub(in crate::cli) fn packaged_openclaw_files() -> &'static [PackagedSkillFile] {
    &[
        PackagedSkillFile {
            relative_path: "SKILL.md",
            contents: OPENCLAW_SKILL_MD,
        },
        PackagedSkillFile {
            relative_path: "references/commands.md",
            contents: OPENCLAW_COMMANDS_REF,
        },
        PackagedSkillFile {
            relative_path: "references/troubleshooting.md",
            contents: OPENCLAW_TROUBLESHOOTING_REF,
        },
        PackagedSkillFile {
            relative_path: "references/json-schema.md",
            contents: OPENCLAW_JSON_SCHEMA_REF,
        },
        PackagedSkillFile {
            relative_path: "references/examples.md",
            contents: OPENCLAW_EXAMPLES_REF,
        },
        PackagedSkillFile {
            relative_path: "references/setup-check.md",
            contents: OPENCLAW_SETUP_CHECK_REF,
        },
        PackagedSkillFile {
            relative_path: "scripts/check-setup.sh",
            contents: OPENCLAW_CHECK_SETUP_SH,
        },
    ]
}

pub(in crate::cli) fn resolve_openclaw_skill_dir(
    dest: Option<&Path>,
    shared: bool,
) -> Result<PathBuf> {
    if let Some(dest) = dest {
        return Ok(dest.to_path_buf());
    }

    let base = if shared {
        home_dir()?.join(OPENCLAW_SHARED_ROOT)
    } else {
        resolve_openclaw_workspace_root()?.join("skills")
    };
    Ok(base.join(OPENCLAW_SKILL_NAME))
}

pub(in crate::cli) fn resolve_openclaw_workspace_root() -> Result<PathBuf> {
    if let Some(path) = std::env::var_os("CLIPMEM_OPENCLAW_WORKSPACE") {
        return Ok(PathBuf::from(path));
    }

    if let Some(openclaw_bin) = find_executable("openclaw") {
        let output = ProcessCommand::new(openclaw_bin)
            .args(["config", "get", "agents.defaults.workspace"])
            .output();
        if let Ok(output) = output {
            if output.status.success() {
                let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
                if !value.is_empty() {
                    return Ok(PathBuf::from(value));
                }
            }
        }
    }

    Ok(home_dir()?.join(OPENCLAW_WORKSPACE_ROOT))
}

pub(in crate::cli) fn packaged_hermes_skill() -> String {
    HERMES_SKILL_MD.to_string()
}

pub(in crate::cli) fn packaged_hermes_files() -> &'static [PackagedSkillFile] {
    &[
        PackagedSkillFile {
            relative_path: "SKILL.md",
            contents: HERMES_SKILL_MD,
        },
        PackagedSkillFile {
            relative_path: "references/commands.md",
            contents: HERMES_COMMANDS_REF,
        },
        PackagedSkillFile {
            relative_path: "references/troubleshooting.md",
            contents: HERMES_TROUBLESHOOTING_REF,
        },
        PackagedSkillFile {
            relative_path: "references/json-schema.md",
            contents: HERMES_JSON_SCHEMA_REF,
        },
        PackagedSkillFile {
            relative_path: "references/examples.md",
            contents: HERMES_EXAMPLES_REF,
        },
        PackagedSkillFile {
            relative_path: "references/setup-check.md",
            contents: HERMES_SETUP_CHECK_REF,
        },
        PackagedSkillFile {
            relative_path: "scripts/check-setup.sh",
            contents: HERMES_CHECK_SETUP_SH,
        },
    ]
}

pub(in crate::cli) fn resolve_hermes_skill_dir(dest: Option<&Path>) -> Result<PathBuf> {
    if let Some(dest) = dest {
        return Ok(dest.to_path_buf());
    }

    Ok(home_dir()?.join(HERMES_SKILL_ROOT).join(HERMES_SKILL_NAME))
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::{
        packaged_hermes_files, packaged_hermes_skill, packaged_openclaw_files,
        packaged_openclaw_skill, resolve_hermes_skill_dir, resolve_openclaw_skill_dir,
        HERMES_CHECK_SETUP_SH, HERMES_COMMANDS_REF, HERMES_EXAMPLES_REF, HERMES_JSON_SCHEMA_REF,
        HERMES_SETUP_CHECK_REF, HERMES_SKILL_MD, HERMES_SKILL_NAME, HERMES_TROUBLESHOOTING_REF,
        OPENCLAW_CHECK_SETUP_SH, OPENCLAW_COMMANDS_REF, OPENCLAW_EXAMPLES_REF,
        OPENCLAW_JSON_SCHEMA_REF, OPENCLAW_SETUP_CHECK_REF, OPENCLAW_SKILL_MD, OPENCLAW_SKILL_NAME,
        OPENCLAW_TROUBLESHOOTING_REF,
    };

    #[test]
    fn packaged_skill_strings_return_embedded_skill_markdown() {
        assert_eq!(packaged_openclaw_skill(), OPENCLAW_SKILL_MD);
        assert_eq!(packaged_hermes_skill(), HERMES_SKILL_MD);
        assert!(packaged_openclaw_skill().contains(OPENCLAW_SKILL_NAME));
        assert!(packaged_hermes_skill().contains(HERMES_SKILL_NAME));
    }

    #[test]
    fn packaged_openclaw_manifest_lists_all_embedded_files() {
        let files = packaged_openclaw_files();
        let manifest = files
            .iter()
            .map(|file| (file.relative_path, file.contents))
            .collect::<Vec<_>>();

        assert_eq!(
            manifest,
            vec![
                ("SKILL.md", OPENCLAW_SKILL_MD),
                ("references/commands.md", OPENCLAW_COMMANDS_REF),
                (
                    "references/troubleshooting.md",
                    OPENCLAW_TROUBLESHOOTING_REF
                ),
                ("references/json-schema.md", OPENCLAW_JSON_SCHEMA_REF),
                ("references/examples.md", OPENCLAW_EXAMPLES_REF),
                ("references/setup-check.md", OPENCLAW_SETUP_CHECK_REF),
                ("scripts/check-setup.sh", OPENCLAW_CHECK_SETUP_SH),
            ]
        );
    }

    #[test]
    fn packaged_hermes_manifest_lists_all_embedded_files() {
        let files = packaged_hermes_files();
        let manifest = files
            .iter()
            .map(|file| (file.relative_path, file.contents))
            .collect::<Vec<_>>();

        assert_eq!(
            manifest,
            vec![
                ("SKILL.md", HERMES_SKILL_MD),
                ("references/commands.md", HERMES_COMMANDS_REF),
                ("references/troubleshooting.md", HERMES_TROUBLESHOOTING_REF),
                ("references/json-schema.md", HERMES_JSON_SCHEMA_REF),
                ("references/examples.md", HERMES_EXAMPLES_REF),
                ("references/setup-check.md", HERMES_SETUP_CHECK_REF),
                ("scripts/check-setup.sh", HERMES_CHECK_SETUP_SH),
            ]
        );
    }

    #[test]
    fn explicit_skill_destinations_bypass_default_resolution() {
        let openclaw_dest = Path::new("/tmp/custom-openclaw-skill");
        let hermes_dest = Path::new("/tmp/custom-hermes-skill");

        assert_eq!(
            resolve_openclaw_skill_dir(Some(openclaw_dest), false)
                .expect("explicit OpenClaw destination should resolve"),
            openclaw_dest
        );
        assert_eq!(
            resolve_openclaw_skill_dir(Some(openclaw_dest), true)
                .expect("explicit shared OpenClaw destination should resolve"),
            openclaw_dest
        );
        assert_eq!(
            resolve_hermes_skill_dir(Some(hermes_dest))
                .expect("explicit Hermes destination should resolve"),
            hermes_dest
        );
    }
}