use crate::filesystem::Filesystem;
use crate::types::BuiltinTool;
const ALLOWLIST_PATH: &str = ".lh_tool_allowlist.txt";
pub(crate) async fn load() -> Option<Vec<BuiltinTool>> {
let fs = super::shared_opfs();
let bytes = fs.read(ALLOWLIST_PATH).await.ok()?;
if bytes.is_empty() {
return None;
}
let text = String::from_utf8(bytes).ok()?;
let tools: Vec<BuiltinTool> = text
.lines()
.filter_map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
return None;
}
BuiltinTool::ALL.iter().find(|t| t.wire_name() == trimmed).copied()
})
.collect();
if tools.is_empty() {
return None;
}
Some(tools)
}
pub(crate) async fn save(tools: &[BuiltinTool]) -> Result<(), String> {
let fs = super::shared_opfs();
if tools.is_empty() {
let _ = fs.delete(ALLOWLIST_PATH).await;
return Ok(());
}
let mut lines: Vec<&str> = tools.iter().map(|t| t.wire_name()).collect();
if !lines.contains(&"finish") {
lines.push("finish");
}
lines.sort();
lines.dedup();
let content = lines.join("\n");
fs.write_atomic(ALLOWLIST_PATH, content.as_bytes())
.await
.map_err(|e| format!("write: {e}"))
}
pub(crate) fn summary(tools: &[BuiltinTool]) -> String {
if tools.is_empty() {
return "all tools enabled".to_string();
}
let names: Vec<&str> = tools.iter().map(|t| t.wire_name()).collect();
format!("{} tools: {}", names.len(), names.join(", "))
}