use clap::Args;
use std::path::Path;
#[derive(Args)]
pub struct AddArgs {
#[arg(value_name = "PACKAGE")]
pub package: String,
}
fn agents_dir_or_error(dir: Option<std::path::PathBuf>) -> anyhow::Result<std::path::PathBuf> {
dir.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))
}
pub async fn execute(args: AddArgs) -> anyhow::Result<()> {
let installer = leviath_package::AgentInstaller::new();
let agents_dir = resolve_agents_dir()?;
let config = crate::config::Config::load().ok();
execute_with(&args, &installer, &agents_dir, config.as_ref()).await
}
fn resolve_agents_dir() -> anyhow::Result<std::path::PathBuf> {
#[cfg(test)]
if FORCE_AGENTS_DIR_ERROR.with(|f| f.get()) {
anyhow::bail!("Could not determine home directory");
}
agents_dir_or_error(leviath_core::paths::agents_dir())
}
#[cfg(test)]
thread_local! {
static FORCE_AGENTS_DIR_ERROR: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
async fn execute_with(
args: &AddArgs,
installer: &leviath_package::AgentInstaller,
agents_dir: &Path,
config: Option<&crate::config::Config>,
) -> anyhow::Result<()> {
tracing::info!("Installing agent package");
let package_path = Path::new(&args.package);
if package_path.is_dir() {
install_from_dir(package_path, agents_dir, config)?;
} else if package_path.exists() || args.package.ends_with(".leviath-bundle") {
if !package_path.exists() {
anyhow::bail!("Package file not found: {}", args.package);
}
println!("Installing from bundle: {}", args.package);
let installed = installer.install(package_path)?;
println!(
"Installed agent '{}' v{} to {}",
installed.name,
installed.version,
installed.path.display()
);
print_capabilities(&installed.name, &installed.path, config);
} else {
anyhow::bail!(
"'{}' is not a local agent directory or a .leviath-bundle file - \
pass a path to one of those instead.",
args.package
);
}
Ok(())
}
pub(crate) fn describe_capabilities(
manifest_toml: &str,
script_tools: &[String],
read_paths: Option<&crate::read_path_report::GrantReport>,
) -> Vec<String> {
let mut findings = Vec::new();
let Ok(value) = toml::from_str::<toml::Value>(manifest_toml) else {
return findings;
};
if !script_tools.is_empty() {
findings.push(format!(
"ships {} executable script tool(s): {}",
script_tools.len(),
script_tools.join(", ")
));
}
let mut granted: Vec<String> = Vec::new();
let mut collect_grants = |table: Option<&toml::Value>| {
if let Some(t) = table.and_then(|v| v.as_table()) {
for (tool, policy) in t {
if policy.as_str() == Some("allow") && !granted.contains(tool) {
granted.push(tool.clone());
}
}
}
};
collect_grants(value.get("tool_permissions"));
if let Some(stages) = value.get("stages").and_then(|v| v.as_table()) {
for stage in stages.values() {
collect_grants(stage.get("tool_permissions"));
}
}
if !granted.is_empty() {
granted.sort();
findings.push(format!(
"pre-approves these tools (no prompt at run time): {}",
granted.join(", ")
));
}
if let Some(t) = value
.get("tool_script_permissions")
.and_then(|v| v.as_table())
{
let mut allowed: Vec<&String> = t
.iter()
.filter(|(_, v)| v.as_str() == Some("allow"))
.map(|(k, _)| k)
.collect();
if !allowed.is_empty() {
allowed.sort();
findings.push(format!(
"requests script host access: {}",
allowed
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
));
}
}
if let Some(kind) = value
.get("sandbox")
.and_then(|v| v.get("kind"))
.and_then(|v| v.as_str())
&& kind == "none"
{
findings.push("asks to run tools directly on the host (sandbox = none)".to_string());
}
if let Some(entries) = value
.get("read_paths")
.and_then(|v| v.get("allow"))
.and_then(|v| v.as_array())
&& !entries.is_empty()
{
let listed: Vec<String> = entries
.iter()
.filter_map(|e| e.as_str().map(str::to_string))
.collect();
let status = match read_paths {
Some(report) if report.has_ungranted() => format!(
"; {} - grant the rest with [agent_read_paths.{}] in your config",
report.summary(),
report.agent
),
Some(report) => format!("; {}, all granted by your config", report.summary()),
None => "; inert unless you grant it via [security] read_paths / \
allow_blueprint_read_paths or [agent_read_paths.<name>] in your config"
.to_string(),
};
findings.push(format!(
"asks to read outside its workdir (read-only): {}{status}",
listed.join(", ")
));
}
let seed_commands = collect_seed_commands(&value);
for command in seed_commands {
findings.push(format!(
"runs this command at startup, before any prompt: `{command}`"
));
}
findings
}
fn collect_seed_commands(value: &toml::Value) -> Vec<String> {
let mut out = Vec::new();
let mut scan = |regions: Option<&toml::Value>| {
if let Some(t) = regions.and_then(|v| v.as_table()) {
for region in t.values() {
if let Some(cmd) = region
.get("seed")
.and_then(|s| s.get("command"))
.and_then(|c| c.as_str())
{
out.push(cmd.to_string());
}
}
}
};
scan(value.get("context").and_then(|c| c.get("regions")));
if let Some(stages) = value.get("stages").and_then(|v| v.as_table()) {
for stage in stages.values() {
scan(stage.get("context").and_then(|c| c.get("regions")));
}
}
out
}
fn print_capabilities(name: &str, install_dir: &Path, config: Option<&crate::config::Config>) {
let manifest = std::fs::read_to_string(install_dir.join("agent.leviath")).unwrap_or_default();
let scripts = script_tool_names(install_dir);
let report = read_path_report(&manifest, config);
let findings = describe_capabilities(&manifest, &scripts, report.as_ref());
if findings.is_empty() {
return;
}
println!("\n '{name}' asks for the following. Review before running it:");
for finding in &findings {
println!(" - {finding}");
}
println!(" Inspect it with: lev validate {name}");
}
fn read_path_report(
manifest_toml: &str,
config: Option<&crate::config::Config>,
) -> Option<crate::read_path_report::GrantReport> {
let config = config?;
let blueprint = leviath_core::manifest::parse_manifest(manifest_toml).ok()?;
let workdir = crate::commands::resolve_cwd().unwrap_or_default();
crate::read_path_report::build(&blueprint, config, &workdir)?.ok()
}
fn script_tool_names(install_dir: &Path) -> Vec<String> {
let mut names: Vec<String> = std::fs::read_dir(install_dir.join("tools"))
.into_iter()
.flatten()
.flatten()
.filter_map(|e| {
let name = e.file_name().to_string_lossy().into_owned();
name.ends_with(".rhai").then_some(name)
})
.collect();
names.sort();
names
}
fn install_from_dir(
src: &Path,
agents_dir: &Path,
config: Option<&crate::config::Config>,
) -> anyhow::Result<()> {
let manifest_path = src.join("agent.leviath");
if !manifest_path.exists() {
anyhow::bail!(
"No agent.leviath found in '{}'. Is this an agent directory?",
src.display()
);
}
let content = std::fs::read_to_string(&manifest_path)?;
let name = parse_agent_name(&content).unwrap_or_else(|| {
src.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string()
});
let install_dir = agents_dir.join(&name);
if install_dir.exists() {
println!("Reinstalling agent '{}' (replacing existing)", name);
std::fs::remove_dir_all(&install_dir)?;
}
copy_dir_recursive(src, &install_dir)?;
println!("Installed agent '{}' to {}", name, install_dir.display());
print_capabilities(&name, &install_dir, config);
println!("Run with: lev run {} --task \"...\"", name);
Ok(())
}
#[cfg(test)]
thread_local! {
static FORCE_DIR_ENTRY_ERROR: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
fn unwrap_dir_entry(
entry: std::io::Result<std::fs::DirEntry>,
) -> anyhow::Result<std::fs::DirEntry> {
#[cfg(test)]
if FORCE_DIR_ENTRY_ERROR.with(|f| f.get()) {
anyhow::bail!("forced dir-entry error for testing");
}
Ok(entry?)
}
fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = unwrap_dir_entry(entry)?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if src_path.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else {
std::fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}
fn parse_agent_name(content: &str) -> Option<String> {
for line in content.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("name") {
let rest = rest.trim_start_matches(|c: char| c.is_whitespace() || c == '=');
let name = rest.trim().trim_matches('"');
if !name.is_empty() {
return Some(name.to_string());
}
}
}
None
}
#[cfg(test)]
mod capability_tests {
use std::path::Path;
fn describe_capabilities(manifest_toml: &str, script_tools: &[String]) -> Vec<String> {
super::describe_capabilities(manifest_toml, script_tools, None)
}
#[test]
fn an_ordinary_agent_has_nothing_to_report() {
let manifest = "[agent]\nname = \"x\"\nversion = \"1.0.0\"\ndescription = \"d\"\n\n\
[stages.main]\nsystem_prompt = \"p\"\n";
assert!(describe_capabilities(manifest, &[]).is_empty());
}
#[test]
fn script_tools_are_listed_by_name() {
let findings = describe_capabilities(
"[agent]\nname = \"x\"\n",
&["web_fetch.rhai".to_string(), "post.rhai".to_string()],
);
assert_eq!(findings.len(), 1);
assert!(findings[0].contains("2 executable script tool"));
assert!(findings[0].contains("web_fetch.rhai"));
}
#[test]
fn self_granted_tool_permissions_are_reported() {
let manifest = "[agent]\nname = \"x\"\n\n\
[tool_permissions]\nshell = \"allow\"\nread_file = \"ask\"\n";
let findings = describe_capabilities(manifest, &[]);
assert_eq!(findings.len(), 1);
assert!(findings[0].contains("pre-approves"));
assert!(findings[0].contains("shell"));
assert!(!findings[0].contains("read_file"));
}
#[test]
fn a_script_permission_table_that_grants_nothing_is_not_reported() {
let manifest = "[agent]\nname = \"x\"\n\n\
[tool_script_permissions]\nenv_var = \"deny\"\nhttp_get = \"ask\"\n";
assert!(
describe_capabilities(manifest, &[]).is_empty(),
"denying host access is not a capability to warn about"
);
}
#[test]
fn stage_level_grants_are_reported_too() {
let manifest = "[agent]\nname = \"x\"\n\n\
[stages.build.tool_permissions]\nwrite_file = \"allow\"\n";
let findings = describe_capabilities(manifest, &[]);
assert!(findings[0].contains("write_file"), "{findings:?}");
}
#[test]
fn script_host_grants_and_sandbox_opt_out_are_reported() {
let manifest = "[agent]\nname = \"x\"\n\n\
[tool_script_permissions]\nshell = \"allow\"\nhttp_post = \"allow\"\n\n\
[sandbox]\nkind = \"none\"\n";
let findings = describe_capabilities(manifest, &[]);
let joined = findings.join(" | ");
assert!(joined.contains("script host access"), "{joined}");
assert!(joined.contains("http_post"), "{joined}");
assert!(joined.contains("sandbox = none"), "{joined}");
}
#[test]
fn command_seeds_are_reported_verbatim() {
let manifest = "[agent]\nname = \"x\"\n\n\
[context.regions]\n\
repo = { kind = \"pinned\", seed = { command = \"git ls-files\" } }\n";
let findings = describe_capabilities(manifest, &[]);
assert_eq!(findings.len(), 1);
assert!(findings[0].contains("before any prompt"), "{findings:?}");
assert!(findings[0].contains("git ls-files"), "{findings:?}");
}
#[test]
fn stage_level_command_seeds_are_reported() {
let manifest = "[agent]\nname = \"x\"\n\n\
[stages.discover.context.regions]\n\
env = { kind = \"pinned\", seed = { command = \"curl https://evil\" } }\n";
let findings = describe_capabilities(manifest, &[]);
assert!(findings[0].contains("curl https://evil"), "{findings:?}");
}
#[test]
fn opting_into_a_sandbox_is_not_reported() {
let manifest = "[agent]\nname = \"x\"\n\n[sandbox]\nkind = \"container\"\n";
assert!(describe_capabilities(manifest, &[]).is_empty());
}
#[test]
fn read_path_declarations_are_reported() {
let manifest = "[agent]\nname = \"x\"\n\n\
[read_paths]\n\
allow = [\"~/.leviath/runs\", \"glob:~/design-docs/**\"]\n";
let findings = describe_capabilities(manifest, &[]);
assert_eq!(findings.len(), 1);
assert!(
findings[0].contains("read outside its workdir"),
"{findings:?}"
);
assert!(findings[0].contains("~/.leviath/runs"), "{findings:?}");
assert!(
findings[0].contains("glob:~/design-docs/**"),
"{findings:?}"
);
assert!(
findings[0].contains("inert unless you grant it"),
"{findings:?}"
);
}
#[test]
fn read_path_declarations_carry_their_grant_status() {
let manifest = "[agent]\nname = \"cto\"\nversion = \"1.0.0\"\ndescription = \"d\"\n\n\
[stages.main]\nmode = \"autonomous\"\n\n\
[context.regions]\nsystem = { kind = \"pinned\", max_tokens = 1000 }\n\n\
[read_paths]\nallow = [\"/data/runs\", \"/data/docs\"]\n";
let blueprint = leviath_core::manifest::parse_manifest(manifest).expect("parses");
let mut config = crate::config::Config::default();
config.security.read_paths = vec!["/data/runs".to_string()];
let partial = crate::read_path_report::build(&blueprint, &config, Path::new("/work"))
.expect("declares read paths")
.expect("grants compile");
let findings = super::describe_capabilities(manifest, &[], Some(&partial));
assert!(
findings[0].contains("2 declared, 1 granted"),
"{findings:?}"
);
assert!(
findings[0].contains("[agent_read_paths.cto]"),
"{findings:?}"
);
config.security.read_paths.push("/data/docs".to_string());
let full = crate::read_path_report::build(&blueprint, &config, Path::new("/work"))
.expect("declares read paths")
.expect("grants compile");
let findings = super::describe_capabilities(manifest, &[], Some(&full));
assert!(findings[0].contains("all granted"), "{findings:?}");
}
#[test]
fn an_empty_read_paths_block_is_not_reported() {
let manifest = "[agent]\nname = \"x\"\n\n[read_paths]\nallow = []\n";
assert!(describe_capabilities(manifest, &[]).is_empty());
}
#[test]
fn no_grant_report_is_built_without_a_config_or_a_parseable_manifest() {
let manifest = "[agent]\nname = \"x\"\nversion = \"1.0.0\"\ndescription = \"d\"\n\n\
[stages.main]\nmode = \"autonomous\"\n\n\
[context.regions]\nsystem = { kind = \"pinned\", max_tokens = 1000 }\n\n\
[read_paths]\nallow = [\"/data/runs\"]\n";
assert!(super::read_path_report(manifest, None).is_none());
assert!(
super::read_path_report(
"not valid toml [[[",
Some(&crate::config::Config::default())
)
.is_none()
);
let plain = "[agent]\nname = \"x\"\nversion = \"1.0.0\"\ndescription = \"d\"\n\n\
[stages.main]\nmode = \"autonomous\"\n\n\
[context.regions]\nsystem = { kind = \"pinned\", max_tokens = 1000 }\n";
assert!(super::read_path_report(plain, Some(&crate::config::Config::default())).is_none());
let mut broken = crate::config::Config::default();
broken.security.read_paths = vec!["regex:relative/.*".to_string()];
assert!(super::read_path_report(manifest, Some(&broken)).is_none());
let report = super::read_path_report(manifest, Some(&crate::config::Config::default()))
.expect("a parseable manifest and a config give a report");
assert_eq!(report.declared(), 1);
}
#[test]
fn script_tool_names_lists_only_rhai_files_sorted() {
let dir = tempfile::tempdir().unwrap();
let tools = dir.path().join("tools");
std::fs::create_dir(&tools).unwrap();
for name in ["zeta.rhai", "alpha.rhai", "README.md", "notes.txt"] {
std::fs::write(tools.join(name), "x").unwrap();
}
assert_eq!(
super::script_tool_names(dir.path()),
vec!["alpha.rhai".to_string(), "zeta.rhai".to_string()]
);
}
#[test]
fn script_tool_names_is_empty_without_a_tools_directory() {
let dir = tempfile::tempdir().unwrap();
assert!(super::script_tool_names(dir.path()).is_empty());
}
#[test]
fn print_capabilities_reads_the_installed_directory() {
crate::test_support::with_tracing(|| {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("agent.leviath"),
"[agent]\nname = \"q\"\n\n[tool_permissions]\nshell = \"allow\"\n",
)
.unwrap();
let tools = dir.path().join("tools");
std::fs::create_dir(&tools).unwrap();
std::fs::write(tools.join("t.rhai"), "// @tool t\n").unwrap();
super::print_capabilities("q", dir.path(), None);
let plain = tempfile::tempdir().unwrap();
std::fs::write(
plain.path().join("agent.leviath"),
"[agent]\nname = \"p\"\n\n[stages.main]\nsystem_prompt = \"p\"\n",
)
.unwrap();
super::print_capabilities("p", plain.path(), None);
});
}
#[test]
fn an_unparseable_manifest_reports_nothing() {
assert!(describe_capabilities("{ not toml", &[]).is_empty());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{with_tracing, write_test_agent};
async fn execute_with(
args: &AddArgs,
installer: &leviath_package::AgentInstaller,
agents_dir: &Path,
) -> anyhow::Result<()> {
super::execute_with(args, installer, agents_dir, None).await
}
fn install_from_dir(src: &Path, agents_dir: &Path) -> anyhow::Result<()> {
super::install_from_dir(src, agents_dir, None)
}
#[test]
fn agents_dir_or_error_some_returns_path() {
let dir = std::path::PathBuf::from("/home/testuser/.leviath/agents");
assert_eq!(agents_dir_or_error(Some(dir.clone())).unwrap(), dir);
}
#[test]
fn agents_dir_or_error_none_returns_error() {
let err = agents_dir_or_error(None).unwrap_err();
assert!(
err.to_string()
.contains("Could not determine home directory")
);
}
#[test]
fn parse_agent_name_standard() {
let content = r#"
name = "my-agent"
version = "1.0"
"#;
assert_eq!(parse_agent_name(content), Some("my-agent".to_string()));
}
#[test]
fn parse_agent_name_no_quotes() {
let content = r#"name = my-agent"#;
assert_eq!(parse_agent_name(content), Some("my-agent".to_string()));
}
#[test]
fn parse_agent_name_extra_whitespace() {
let content = r#" name = "spacy-agent" "#;
assert_eq!(parse_agent_name(content), Some("spacy-agent".to_string()));
}
#[test]
fn parse_agent_name_missing() {
let content = r#"
version = "1.0"
description = "test"
"#;
assert_eq!(parse_agent_name(content), None);
}
#[test]
fn parse_agent_name_empty_value() {
let content = r#"name = """#;
assert_eq!(parse_agent_name(content), None);
}
#[test]
fn copy_dir_recursive_copies_files() {
let src_dir = tempfile::tempdir().unwrap();
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("copy");
std::fs::write(src_dir.path().join("file1.txt"), "hello").unwrap();
std::fs::create_dir_all(src_dir.path().join("sub")).unwrap();
std::fs::write(src_dir.path().join("sub/file2.txt"), "world").unwrap();
copy_dir_recursive(src_dir.path(), &dst_path).unwrap();
assert!(dst_path.join("file1.txt").exists());
assert!(dst_path.join("sub/file2.txt").exists());
assert_eq!(
std::fs::read_to_string(dst_path.join("file1.txt")).unwrap(),
"hello"
);
assert_eq!(
std::fs::read_to_string(dst_path.join("sub/file2.txt")).unwrap(),
"world"
);
}
#[test]
fn copy_dir_recursive_empty_dir() {
let src_dir = tempfile::tempdir().unwrap();
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("empty-copy");
copy_dir_recursive(src_dir.path(), &dst_path).unwrap();
assert!(dst_path.exists());
assert!(dst_path.is_dir());
}
#[test]
fn copy_dir_recursive_nonexistent_src_errors() {
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("dst");
let missing_src = dst_dir.path().join("does-not-exist");
let result = copy_dir_recursive(&missing_src, &dst_path);
assert!(result.is_err());
}
#[test]
fn copy_dir_recursive_dst_parent_is_file_errors() {
let tmp = tempfile::tempdir().unwrap();
let file_path = tmp.path().join("not-a-dir");
std::fs::write(&file_path, "x").unwrap();
let src = tempfile::tempdir().unwrap();
let dst = file_path.join("child");
let result = copy_dir_recursive(src.path(), &dst);
assert!(result.is_err());
}
#[test]
fn copy_dir_recursive_file_over_existing_dir_errors() {
let src_dir = tempfile::tempdir().unwrap();
std::fs::write(src_dir.path().join("clash"), "top secret").unwrap();
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("copy");
std::fs::create_dir_all(dst_path.join("clash")).unwrap();
let result = copy_dir_recursive(src_dir.path(), &dst_path);
assert!(result.is_err());
}
#[test]
fn copy_dir_recursive_recursion_error_propagates() {
let src_dir = tempfile::tempdir().unwrap();
let sub = src_dir.path().join("sub");
std::fs::create_dir_all(&sub).unwrap();
std::fs::write(sub.join("file.txt"), "data").unwrap();
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("copy");
std::fs::create_dir_all(&dst_path).unwrap();
std::fs::write(dst_path.join("sub"), "i am a file").unwrap();
let result = copy_dir_recursive(src_dir.path(), &dst_path);
assert!(result.is_err());
}
#[test]
fn copy_dir_recursive_forced_mid_iteration_entry_error() {
let src_dir = tempfile::tempdir().unwrap();
std::fs::write(src_dir.path().join("file.txt"), "data").unwrap();
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("copy");
FORCE_DIR_ENTRY_ERROR.with(|f| f.set(true));
let result = copy_dir_recursive(src_dir.path(), &dst_path);
FORCE_DIR_ENTRY_ERROR.with(|f| f.set(false));
assert!(result.is_err());
}
#[test]
fn unwrap_dir_entry_propagates_a_real_err_argument() {
let result = unwrap_dir_entry(Err(std::io::Error::other("synthetic entry error")));
assert!(result.is_err());
}
#[test]
fn install_from_dir_no_manifest_errors() {
let dir = tempfile::tempdir().unwrap();
let agents_dir = tempfile::tempdir().unwrap();
let result = install_from_dir(dir.path(), agents_dir.path());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("agent.leviath"));
}
#[test]
fn install_from_dir_copies_and_names_from_manifest() {
let src = tempfile::tempdir().unwrap();
let agents_dir = tempfile::tempdir().unwrap();
std::fs::write(
src.path().join("agent.leviath"),
"[agent]\nname = \"my-agent\"\n",
)
.unwrap();
std::fs::write(src.path().join("extra.txt"), "data").unwrap();
install_from_dir(src.path(), agents_dir.path()).unwrap();
let installed_dir = agents_dir.path().join("my-agent");
assert!(installed_dir.join("agent.leviath").exists());
assert!(installed_dir.join("extra.txt").exists());
}
#[test]
fn install_from_dir_falls_back_to_dirname_when_name_missing() {
let src = tempfile::tempdir().unwrap();
let agent_dir = src.path().join("my-dir-name");
std::fs::create_dir_all(&agent_dir).unwrap();
std::fs::write(agent_dir.join("agent.leviath"), "version = \"1.0\"\n").unwrap();
let agents_dir = tempfile::tempdir().unwrap();
install_from_dir(&agent_dir, agents_dir.path()).unwrap();
assert!(agents_dir.path().join("my-dir-name").exists());
}
#[test]
fn install_from_dir_reinstalls_existing() {
let src = tempfile::tempdir().unwrap();
std::fs::write(
src.path().join("agent.leviath"),
"[agent]\nname = \"dup-agent\"\n",
)
.unwrap();
let agents_dir = tempfile::tempdir().unwrap();
let existing = agents_dir.path().join("dup-agent");
std::fs::create_dir_all(&existing).unwrap();
std::fs::write(existing.join("stale.txt"), "old").unwrap();
install_from_dir(src.path(), agents_dir.path()).unwrap();
assert!(!existing.join("stale.txt").exists());
assert!(existing.join("agent.leviath").exists());
}
#[test]
fn install_from_dir_invalid_utf8_manifest_errors() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("agent.leviath"), [0xFF, 0xFE, 0xFA]).unwrap();
let agents_dir = tempfile::tempdir().unwrap();
let result = install_from_dir(dir.path(), agents_dir.path());
assert!(result.is_err());
}
#[test]
fn install_from_dir_remove_dir_all_failure_errors() {
let src = tempfile::tempdir().unwrap();
std::fs::write(
src.path().join("agent.leviath"),
"[agent]\nname = \"file-agent\"\n",
)
.unwrap();
let agents_dir = tempfile::tempdir().unwrap();
std::fs::write(agents_dir.path().join("file-agent"), "not a dir").unwrap();
let result = install_from_dir(src.path(), agents_dir.path());
assert!(result.is_err());
}
#[test]
fn install_from_dir_copy_failure_propagates() {
let src = tempfile::tempdir().unwrap();
std::fs::write(
src.path().join("agent.leviath"),
"[agent]\nname = \"broken-copy-agent\"\n",
)
.unwrap();
std::fs::write(src.path().join("extra.txt"), "data").unwrap();
let tmp = tempfile::tempdir().unwrap();
let agents_file = tmp.path().join("agents-is-a-file");
std::fs::write(&agents_file, "not a dir").unwrap();
let result = install_from_dir(src.path(), &agents_file);
assert!(result.is_err());
}
#[test]
fn execute_with_directory_package_installs() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
let src = tempfile::tempdir().unwrap();
std::fs::write(
src.path().join("agent.leviath"),
"[agent]\nname = \"dir-pkg\"\n",
)
.unwrap();
let agents_dir = tempfile::tempdir().unwrap();
let installer = leviath_package::AgentInstaller::with_install_dir(
agents_dir.path().to_path_buf(),
);
let args = AddArgs {
package: src.path().to_str().unwrap().to_string(),
};
execute_with(&args, &installer, agents_dir.path())
.await
.unwrap();
assert!(agents_dir.path().join("dir-pkg").exists());
})
});
}
#[test]
fn execute_with_directory_without_manifest_errors() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
let src = tempfile::tempdir().unwrap(); let agents_dir = tempfile::tempdir().unwrap();
let installer = leviath_package::AgentInstaller::with_install_dir(
agents_dir.path().to_path_buf(),
);
let args = AddArgs {
package: src.path().to_str().unwrap().to_string(),
};
let err = execute_with(&args, &installer, agents_dir.path())
.await
.unwrap_err();
assert!(err.to_string().contains("agent.leviath"));
})
});
}
#[test]
fn execute_with_missing_bundle_file_errors() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
let agents_dir = tempfile::tempdir().unwrap();
let installer = leviath_package::AgentInstaller::with_install_dir(
agents_dir.path().to_path_buf(),
);
let args = AddArgs {
package: "nonexistent.leviath-bundle".to_string(),
};
let err = execute_with(&args, &installer, agents_dir.path())
.await
.unwrap_err();
assert!(err.to_string().contains("Package file not found"));
})
});
}
#[test]
fn execute_with_bundle_file_installs() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
let project_dir = tempfile::tempdir().unwrap();
std::fs::write(
project_dir.path().join("agent.leviath"),
"[agent]\nname = \"bundled-pkg\"\nversion = \"1.0.0\"\ndescription = \"d\"\n",
)
.unwrap();
let bundle_bytes = leviath_package::AgentBundler::new()
.bundle(project_dir.path())
.unwrap();
let bundle_dir = tempfile::tempdir().unwrap();
let bundle_path = bundle_dir.path().join("bundled-pkg.leviath-bundle");
std::fs::write(&bundle_path, bundle_bytes).unwrap();
let agents_dir = tempfile::tempdir().unwrap();
let installer = leviath_package::AgentInstaller::with_install_dir(
agents_dir.path().to_path_buf(),
);
let args = AddArgs {
package: bundle_path.to_str().unwrap().to_string(),
};
execute_with(&args, &installer, agents_dir.path())
.await
.unwrap();
assert!(agents_dir.path().join("bundled-pkg").exists());
})
});
}
#[test]
fn execute_with_corrupt_bundle_file_errors() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
let bundle_dir = tempfile::tempdir().unwrap();
let bundle_path = bundle_dir.path().join("broken.leviath-bundle");
std::fs::write(&bundle_path, b"not a valid gzip archive").unwrap();
let agents_dir = tempfile::tempdir().unwrap();
let installer = leviath_package::AgentInstaller::with_install_dir(
agents_dir.path().to_path_buf(),
);
let args = AddArgs {
package: bundle_path.to_str().unwrap().to_string(),
};
let err = execute_with(&args, &installer, agents_dir.path())
.await
.unwrap_err();
assert!(err.to_string().contains("Failed to extract package"));
})
});
}
#[test]
fn execute_with_unrecognized_package_reports_local_only() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
let agents_dir = tempfile::tempdir().unwrap();
let installer = leviath_package::AgentInstaller::with_install_dir(
agents_dir.path().to_path_buf(),
);
let args = AddArgs {
package: "some-registry-agent".to_string(),
};
let err = execute_with(&args, &installer, agents_dir.path())
.await
.unwrap_err();
assert!(
err.to_string()
.contains("not a local agent directory or a .leviath-bundle file"),
"expected the v1-cut message, got: {err}"
);
})
});
}
#[test]
fn bundle_extension_detected() {
let package = "my-agent-1.0.leviath-bundle";
assert!(package.ends_with(".leviath-bundle"));
}
#[test]
fn directory_path_detected() {
let dir = tempfile::tempdir().unwrap();
let package_path = Path::new(dir.path().to_str().unwrap());
assert!(package_path.is_dir());
}
#[test]
fn registry_name_not_dir_not_bundle() {
let package = "my-cool-agent";
let package_path = Path::new(package);
assert!(!package_path.is_dir());
assert!(!package.ends_with(".leviath-bundle"));
}
#[test]
fn parse_agent_name_in_section() {
let content = r#"
[agent]
name = "my-agent"
version = "1.0"
"#;
assert_eq!(parse_agent_name(content), Some("my-agent".to_string()));
}
#[test]
fn parse_agent_name_with_single_quotes() {
let content = r#"name = my-agent-no-quotes"#;
assert_eq!(
parse_agent_name(content),
Some("my-agent-no-quotes".to_string())
);
}
#[test]
fn parse_agent_name_multiple_name_fields_returns_first() {
let content = r#"
name = "first"
name = "second"
"#;
assert_eq!(parse_agent_name(content), Some("first".to_string()));
}
#[test]
fn copy_dir_recursive_deeply_nested() {
let src_dir = tempfile::tempdir().unwrap();
let dst_dir = tempfile::tempdir().unwrap();
let dst_path = dst_dir.path().join("deep-copy");
std::fs::create_dir_all(src_dir.path().join("a/b/c")).unwrap();
std::fs::write(src_dir.path().join("a/b/c/deep.txt"), "deep").unwrap();
copy_dir_recursive(src_dir.path(), &dst_path).unwrap();
assert!(dst_path.join("a/b/c/deep.txt").exists());
assert_eq!(
std::fs::read_to_string(dst_path.join("a/b/c/deep.txt")).unwrap(),
"deep"
);
}
#[test]
fn execute_real_wrapper_fails_fast_without_touching_real_agents_dir() {
let rt = tokio::runtime::Runtime::new().unwrap();
with_tracing(|| {
rt.block_on(async {
crate::config::with_isolated_config_path_async("add-real-wrapper", |_fake| async {
let args = AddArgs {
package: "definitely-not-a-real-bundle-xyz.leviath-bundle".to_string(),
};
let err = execute(args).await.unwrap_err();
assert!(err.to_string().contains("Package file not found"));
})
.await;
})
});
}
#[test]
fn execute_returns_err_when_agents_dir_unresolvable() {
let rt = tokio::runtime::Runtime::new().unwrap();
FORCE_AGENTS_DIR_ERROR.with(|f| f.set(true));
let result = rt.block_on(async {
let args = AddArgs {
package: "whatever.leviath-bundle".to_string(),
};
execute(args).await
});
FORCE_AGENTS_DIR_ERROR.with(|f| f.set(false));
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("Could not determine home directory")
);
}
#[test]
fn install_from_dir_with_manifest_runs() {
let dir = tempfile::tempdir().unwrap();
let manifest = r#"
[agent]
name = "test-install-agent-xyz"
version = "0.1.0"
description = "test"
"#;
write_test_agent(dir.path(), manifest);
std::fs::write(dir.path().join("readme.txt"), "hello").unwrap();
let agents_dir = tempfile::tempdir().unwrap();
install_from_dir(dir.path(), agents_dir.path()).unwrap();
let install_dir = agents_dir.path().join("test-install-agent-xyz");
assert!(install_dir.join("agent.leviath").exists());
assert!(install_dir.join("readme.txt").exists());
}
}