use serde::de::DeserializeOwned;
use serde_json::json;
use std::{env, io::Read, process::ExitCode};
use zkr::{
ClaimEvidence, CorrectInput, DeleteInput, EmbeddingInput, EvidenceLocatorInput, GetInput,
MemoryDb, ProfileInput, ProfilesInput, ProjectionAuditInput, RememberRequest, ReviewInput,
ReviewsInput, SearchInput,
};
const MAX_REQUEST_BYTES: u64 = 1024 * 1024;
const HELP: &str = "zkr --db PATH COMMAND\n\nCommands (read one JSON object from stdin; write JSON to stdout):\n remember Store source evidence and an optional typed claim or transcript locator\n locator Read a live evidence transcript locator\n search Retrieve bounded, cited memory matches\n get Read one live cited memory by target\n correct Supersede a claim using new correction evidence\n link Attach supporting or contradicting evidence to a claim\n delete Tombstone a source and propagate unavailable evidence\n profile Project a live profile-fact claim into the current profile\n profiles Retrieve bounded live profile entries\n review Store a cited daily review without invoking an LLM\n reviews Retrieve bounded daily reviews\n projections List bounded stale or missing embedding inputs\n embed Upsert a rebuildable embedding projection\n help Show this help\n";
fn main() -> ExitCode {
match run() {
Ok(value) => {
if let Some(value) = value {
println!("{value}");
}
ExitCode::SUCCESS
}
Err(error) => {
eprintln!("{}", json!({ "error": error.to_string() }));
ExitCode::FAILURE
}
}
}
fn run() -> Result<Option<serde_json::Value>, Box<dyn std::error::Error>> {
let arguments = env::args().skip(1).collect::<Vec<_>>();
if arguments.as_slice() == ["help"]
|| arguments.as_slice() == ["--help"]
|| arguments.as_slice() == ["-h"]
|| arguments.is_empty()
{
print!("{HELP}");
return Ok(None);
}
if arguments.len() == 3 && arguments[0] == "--db" && arguments[2] == "help" {
print!("{HELP}");
return Ok(None);
}
if arguments.len() != 3 || arguments[0] != "--db" {
return Err("usage: zkr --db PATH COMMAND (use --help)".into());
}
let mut database = MemoryDb::open(&arguments[1])?;
let value = match arguments[2].as_str() {
"remember" => {
let request = read_json::<RememberRequest>()?;
serde_json::to_value(database.remember_with_locator(request.memory, request.locator)?)?
}
"locator" => {
serde_json::to_value(database.evidence_locator(read_json::<EvidenceLocatorInput>()?)?)?
}
"search" => serde_json::to_value(database.search(read_json::<SearchInput>()?)?)?,
"get" => serde_json::to_value(database.get(read_json::<GetInput>()?)?)?,
"correct" => serde_json::to_value(database.correct(read_json::<CorrectInput>()?)?)?,
"link" => {
database.link_claim_evidence(read_json::<ClaimEvidence>()?)?;
serde_json::json!({"ok": true})
}
"delete" => serde_json::to_value(database.delete_source(read_json::<DeleteInput>()?)?)?,
"profile" => serde_json::to_value(database.store_profile(read_json::<ProfileInput>()?)?)?,
"profiles" => serde_json::to_value(database.profiles(read_json::<ProfilesInput>()?)?)?,
"review" => serde_json::to_value(database.store_review(read_json::<ReviewInput>()?)?)?,
"reviews" => serde_json::to_value(database.reviews(read_json::<ReviewsInput>()?)?)?,
"projections" => {
serde_json::to_value(database.projection_issues(read_json::<ProjectionAuditInput>()?)?)?
}
"embed" => {
serde_json::to_value(database.upsert_embedding(read_json::<EmbeddingInput>()?)?)?
}
command => return Err(format!("unknown command {command:?}").into()),
};
Ok(Some(value))
}
fn read_json<T: DeserializeOwned>() -> Result<T, Box<dyn std::error::Error>> {
let mut input = Vec::new();
std::io::stdin()
.take(MAX_REQUEST_BYTES + 1)
.read_to_end(&mut input)?;
if input.len() as u64 > MAX_REQUEST_BYTES {
return Err("request exceeds 1048576 bytes".into());
}
Ok(serde_json::from_slice(&input)?)
}