use super::*;
pub(in crate::cli) fn openclaw_install_skill(args: &OpenClawInstallSkillArgs) -> Result<()> {
let target_dir = resolve_openclaw_skill_dir(args.dest.as_deref(), args.shared)?;
if target_dir.exists() {
if args.force {
std::fs::remove_dir_all(&target_dir).with_context(|| {
format!(
"failed to remove existing skill at {}",
target_dir.display()
)
})?;
} else {
return Err(anyhow!(
"skill directory already exists at {} (pass --force to replace it)",
target_dir.display()
));
}
}
install_openclaw_package(&target_dir)?;
println!("Installed OpenClaw skill into {}", target_dir.display());
println!("Skill file: {}", target_dir.join("SKILL.md").display());
println!(
"Reload OpenClaw skills or restart OpenClaw if the skill does not appear immediately."
);
Ok(())
}
pub(in crate::cli) fn openclaw_uninstall_skill(args: &OpenClawUninstallSkillArgs) -> Result<()> {
let target_dir = resolve_openclaw_skill_dir(args.dest.as_deref(), args.shared)?;
if !target_dir.exists() {
println!("No installed skill found at {}", target_dir.display());
return Ok(());
}
std::fs::remove_dir_all(&target_dir)
.with_context(|| format!("failed to remove {}", target_dir.display()))?;
println!("Removed OpenClaw skill from {}", target_dir.display());
Ok(())
}
pub(in crate::cli) fn openclaw_doctor(args: &OpenClawDoctorArgs) -> Result<()> {
let report = build_openclaw_doctor_report(args)?;
print!("{}", render_openclaw_doctor_report(&report));
if report
.checks
.iter()
.any(|check| matches!(check.status, OpenClawDoctorStatus::Fail))
{
Err(anyhow!("OpenClaw integration checks failed"))
} else {
Ok(())
}
}
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 install_openclaw_package(target_dir: &Path) -> Result<()> {
install_packaged_skill(target_dir, packaged_openclaw_files())
}
pub(in crate::cli) fn install_packaged_skill(
target_dir: &Path,
files: &[PackagedSkillFile],
) -> Result<()> {
std::fs::create_dir_all(target_dir)
.with_context(|| format!("failed to create {}", target_dir.display()))?;
for file in files {
let path = target_dir.join(file.relative_path);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create {}", parent.display()))?;
}
std::fs::write(&path, file.contents)
.with_context(|| format!("failed to write {}", path.display()))?;
if file.relative_path.ends_with(".sh") {
set_executable(&path)
.with_context(|| format!("failed to mark {} executable", path.display()))?;
}
}
Ok(())
}
#[cfg(unix)]
pub(in crate::cli) fn set_executable(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(path, perms)?;
Ok(())
}
#[cfg(not(unix))]
pub(in crate::cli) fn set_executable(_path: &Path) -> Result<()> {
Ok(())
}
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 home_dir() -> Result<PathBuf> {
std::env::var_os("HOME")
.map(PathBuf::from)
.ok_or_else(|| anyhow!("HOME is not set"))
}
pub(in crate::cli) fn find_executable(name: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path).find_map(|dir| {
let candidate = dir.join(name);
candidate.is_file().then_some(candidate)
})
}