Menu
Public repository / d53bfa0cd853a43217367dcaf0b4f54c645b41a1
AkurAI-PassVault / crates/passvault-cli/src/cmd_tool.rs
Latest change 58558732228cea9b646049555021e899ee300f35 - Checkpoint WIP + add AkurAI Build CI/CD pipeline by Ólafur Búi Ólafsson
use akurai_json::Value;
use akurai_storage::BTree;
use passvault_core::crypto::Crypto;
use passvault_mcp::tools::ToolRegistry;
use std::io::Read;
use std::sync::{Arc, Mutex};
pub fn run(name: &str, confirmed: bool) -> Result<(), String> {
if matches!(name, "delete_entry" | "delete_folder") && !confirmed {
return Err(format!("{name} requires --confirm"));
}
let registry = registry()?;
let mut input = String::new();
std::io::stdin()
.read_to_string(&mut input)
.map_err(|error| format!("read stdin: {error}"))?;
let arguments = if input.trim().is_empty() {
Vec::new()
} else {
match akurai_json::parse(&input).map_err(|error| format!("invalid JSON input: {error}"))? {
Value::Object(arguments) => arguments,
_ => return Err("stdin must contain one JSON object".into()),
}
};
let mut result = registry.execute(name, &arguments)?;
redact(&mut result);
println!("{}", result.to_json());
Ok(())
}
pub fn list() -> Result<(), String> {
let registry = registry()?;
println!("{}", Value::Array(registry.list_tools()).to_json());
Ok(())
}
fn registry() -> Result<ToolRegistry, String> {
let db_path =
std::env::var("AKURAI_PASSVAULT_DB").unwrap_or_else(|_| "data/passvault.db".into());
let key = std::env::var("AKURAI_PASSVAULT_KEY")
.map_err(|_| "AKURAI_PASSVAULT_KEY must be set".to_string())?;
let db = BTree::open(&db_path).map_err(|error| format!("open {db_path}: {error}"))?;
let crypto = Crypto::from_hex_key(&key).map_err(|error| format!("crypto init: {error}"))?;
Ok(ToolRegistry::new(
Arc::new(Mutex::new(db)),
Arc::new(crypto),
))
}
fn redact(value: &mut Value) {
match value {
Value::Object(fields) => {
for (key, value) in fields {
if matches!(key.as_str(), "password" | "token" | "secret") {
*value = Value::Str("[REDACTED]".into());
} else {
redact(value);
}
}
}
Value::Array(values) => {
for value in values {
redact(value);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redacts_secret_fields_recursively() {
let mut value = Value::Object(vec![(
"nested".into(),
Value::Array(vec![Value::Object(vec![(
"password".into(),
Value::Str("do-not-print".into()),
)])]),
)]);
redact(&mut value);
let output = value.to_json();
assert!(!output.contains("do-not-print"));
assert!(output.contains("[REDACTED]"));
}
}