use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use super::InitError;
mod embedded {
include!(concat!(env!("OUT_DIR"), "/embedded_agents.rs"));
}
pub const BUILD_COMMIT: &str = env!("WEFTOS_GIT_COMMIT");
pub const LEGACY_DIRS: &[&str] = &[
"clawft",
"code-reviewer",
"weftos",
"weftos-ecc",
"weftos-kernel",
"weftos-mesh",
];
pub struct AgentSource {
pub files: BTreeMap<String, Cow<'static, [u8]>>,
pub origin: String,
pub commit: String,
}
impl AgentSource {
pub fn embedded() -> Self {
let files = embedded::EMBEDDED_AGENTS
.iter()
.map(|(p, b)| ((*p).to_string(), Cow::Borrowed(*b)))
.collect();
Self {
files,
origin: "embedded".into(),
commit: BUILD_COMMIT.into(),
}
}
pub fn from_dir(dir: &Path) -> Result<Self, InitError> {
if !dir.is_dir() {
return Err(InitError::Other(format!(
"--from {}: not a directory",
dir.display()
)));
}
let mut files = BTreeMap::new();
for entry in fs::read_dir(dir)?.flatten() {
let path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();
if !path.is_dir() || name.starts_with('.') || LEGACY_DIRS.contains(&name.as_str()) {
continue;
}
if name == "teams" || path.join("weftos-package.yaml").is_file() {
walk(&path, &name, &mut files)?;
}
}
Ok(Self {
files,
origin: dir.display().to_string(),
commit: git_commit(dir).unwrap_or_else(|| "unknown".into()),
})
}
pub fn package_ids(&self) -> Vec<String> {
self.files
.keys()
.filter_map(|p| p.strip_suffix("/weftos-package.yaml"))
.filter(|id| !id.contains('/') && !LEGACY_DIRS.contains(id))
.map(str::to_string)
.collect()
}
pub fn get(&self, rel: &str) -> Option<&[u8]> {
self.files.get(rel).map(|b| b.as_ref())
}
pub fn under<'a>(&'a self, prefix: &'a str) -> impl Iterator<Item = (&'a str, &'a [u8])> + 'a {
let dir = format!("{prefix}/");
self.files
.iter()
.filter_map(move |(p, b)| p.strip_prefix(dir.as_str()).map(|r| (r, b.as_ref())))
}
}
fn walk(
dir: &Path,
rel: &str,
files: &mut BTreeMap<String, Cow<'static, [u8]>>,
) -> Result<(), InitError> {
for entry in fs::read_dir(dir)?.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with('.') || name == "evals" {
continue;
}
let path: PathBuf = entry.path();
let child = format!("{rel}/{name}");
if path.is_dir() {
walk(&path, &child, files)?;
} else if path.is_file() {
files.insert(child, Cow::Owned(fs::read(&path)?));
}
}
Ok(())
}
fn git_commit(dir: &Path) -> Option<String> {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
let s = String::from_utf8(out.stdout).ok()?.trim().to_string();
(out.status.success() && !s.is_empty()).then_some(s)
}