use std::env;
use std::fs;
use std::io;
use std::path::Path;
use sysinfo::System;
fn main() {
compile_conch_proto();
if is_cargo_install() {
install_builtin_skills();
}
}
fn compile_conch_proto() {
let protoc = protoc_bin_vendored::protoc_bin_path().expect("find vendored protoc");
std::env::set_var("PROTOC", protoc);
tonic_build::configure()
.build_server(false)
.compile_protos(
&["src/backend/conch/proto/agent.proto"],
&["src/backend/conch/proto"],
)
.expect("compile conch agent proto");
}
fn is_cargo_install() -> bool {
let cmd = get_parent_cmdline();
if cmd.is_empty() {
return false;
}
let has_cargo = cmd.iter().any(|s| {
let lower = s.to_lowercase();
lower == "cargo"
|| lower == "cargo.exe"
|| lower.ends_with("/cargo")
|| lower.ends_with("/cargo.exe")
|| lower.ends_with("\\cargo")
|| lower.ends_with("\\cargo.exe")
|| lower.contains(".cargo/bin/cargo")
});
let has_install = cmd.iter().any(|s| s == "install");
has_cargo && has_install
}
fn get_parent_cmdline() -> Vec<String> {
let sys = System::new_all();
let my_pid = std::process::id();
for (pid, process) in sys.processes() {
if pid.as_u32() == my_pid {
if let Some(parent) = process.parent() {
if let Some(parent_process) = sys.processes().get(&parent) {
return parent_process
.cmd()
.iter()
.map(|s| s.to_string_lossy().to_string())
.collect();
}
}
}
}
Vec::new()
}
fn install_builtin_skills() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let skills_src_dir = Path::new(&manifest_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.join("plugins/skills");
println!("cargo:rerun-if-changed={}", skills_src_dir.display());
if !skills_src_dir.exists() {
println!(
"cargo:warning=Builtin skills source directory not found: {}",
skills_src_dir.display()
);
println!("cargo:warning=Builtin skills will NOT be installed.");
println!("cargo:warning=This may cause security policy enforcement to be unavailable.");
return;
}
let skills: Vec<String> = match fs::read_dir(&skills_src_dir) {
Ok(entries) => entries
.filter_map(|entry| entry.ok())
.filter(|entry| entry.file_type().map(|t| t.is_dir()).unwrap_or(false))
.map(|entry| entry.file_name().to_string_lossy().to_string())
.collect(),
Err(_) => Vec::new(),
};
if skills.is_empty() {
println!(
"cargo:warning=No builtin skills found in {}",
skills_src_dir.display()
);
return;
}
let system_install_base = Path::new("/usr/lib/.xiaoo/skills");
let mut system_success_skills: Vec<(String, bool)> = Vec::new();
let mut system_failed_skills: Vec<String> = Vec::new();
if let Err(e) = fs::create_dir_all(system_install_base) {
println!(
"cargo:warning=Failed to create system-level skills directory {}: {}",
system_install_base.display(),
e
);
println!("cargo:warning=Reason: likely permission denied (requires root privileges)");
} else {
for skill_name in &skills {
let src = skills_src_dir.join(skill_name);
let dst = system_install_base.join(skill_name);
let is_replacement = dst.exists();
if install_skill(&src, &dst).is_ok() {
system_success_skills.push((skill_name.clone(), is_replacement));
} else {
system_failed_skills.push(skill_name.clone());
}
}
}
for (skill_name, is_replacement) in &system_success_skills {
let skill_path = system_install_base.join(skill_name);
if *is_replacement {
println!(" Replacing {}", skill_path.display());
} else {
println!(" Installing {}", skill_path.display());
}
}
if system_failed_skills.is_empty() && system_success_skills.len() == skills.len() {
return;
}
let fallback_skills = if !system_failed_skills.is_empty() {
println!(
"cargo:warning=Failed to install {} skill(s) to system-level directory",
system_failed_skills.len()
);
println!("cargo:warning=Reason: permission denied (requires root privileges)");
println!("cargo:warning=Fallback: installing to user-level directory ~/.xiaoo/skills");
system_failed_skills.clone()
} else {
skills.clone()
};
let Ok(home) = env::var("HOME") else {
println!("cargo:warning=Cannot determine HOME directory");
println!("cargo:warning=Builtin skills will NOT be installed.");
println!("cargo:warning=This may cause security policy enforcement to be unavailable.");
return;
};
let user_install_base = Path::new(&home).join(".xiaoo/skills");
if let Err(e) = fs::create_dir_all(&user_install_base) {
println!(
"cargo:warning=Failed to create user skills directory {}: {}",
user_install_base.display(),
e
);
println!("cargo:warning=Builtin skills will NOT be installed.");
println!("cargo:warning=This may cause security policy enforcement to be unavailable.");
return;
}
let mut user_success_count = 0;
let mut user_success_skills: Vec<(String, bool)> = Vec::new();
for skill_name in &fallback_skills {
let src = skills_src_dir.join(skill_name);
let dst = user_install_base.join(skill_name);
let is_replacement = dst.exists();
if install_skill(&src, &dst).is_ok() {
user_success_count += 1;
user_success_skills.push((skill_name.clone(), is_replacement));
}
}
for (skill_name, is_replacement) in &user_success_skills {
let skill_path = user_install_base.join(skill_name);
if *is_replacement {
println!(" Replacing {}", skill_path.display());
} else {
println!(" Installing {}", skill_path.display());
}
}
if user_success_count == 0 && !fallback_skills.is_empty() {
println!("cargo:warning=Failed to install builtin skills to user-level directory");
println!("cargo:warning=This may cause security policy enforcement to be unavailable.");
}
}
fn install_skill(src: &Path, dst: &Path) -> io::Result<()> {
if dst.exists() {
let backup_path = format!("{}.bak", dst.display());
let backup = Path::new(&backup_path);
if backup.exists() {
fs::remove_dir_all(backup)?;
}
fs::rename(dst, backup)?;
match copy_dir_recursive(src, dst) {
Ok(()) => {
fs::remove_dir_all(backup)?;
Ok(())
}
Err(e) => {
if dst.exists() {
let _ = fs::remove_dir_all(dst);
}
fs::rename(backup, dst)?;
Err(e)
}
}
} else {
copy_dir_recursive(src, dst)
}
}
fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
if !dst.exists() {
fs::create_dir_all(dst)?;
}
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let file_name = entry.file_name();
let src_path = src.join(&file_name);
let dst_path = dst.join(&file_name);
if ty.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else {
fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}