forked from kent/consciousness
Replace Result<_, String> with anyhow::Result throughout: - hippocampus/store module (persist, ops, types, view, mod) - CLI modules (admin, agent, graph, journal, node) - Run trait in main.rs Use .context() and .with_context() instead of .map_err(|e| format!(...)) patterns. Add bail!() for early error returns. Add access_local() helper in hippocampus/mod.rs that returns Result<Arc<Mutex<Store>>> for direct local store access. Fix store access patterns to properly lock Arc<Mutex<Store>> before accessing fields in mind/unconscious.rs, mind/mod.rs, subconscious/learn.rs, and hippocampus/memory.rs. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
96 lines
3.3 KiB
Rust
96 lines
3.3 KiB
Rust
// cli/journal.rs — journal subcommand handlers
|
|
|
|
use anyhow::{bail, Context, Result};
|
|
use crate::hippocampus as memory;
|
|
|
|
pub fn cmd_tail(n: usize, full: bool, provenance: Option<&str>, dedup: bool) -> Result<()> {
|
|
let path = crate::store::nodes_path();
|
|
if !path.exists() {
|
|
bail!("No node log found");
|
|
}
|
|
|
|
use std::io::BufReader;
|
|
let file = std::fs::File::open(&path)
|
|
.with_context(|| format!("open {}", path.display()))?;
|
|
let mut reader = BufReader::new(file);
|
|
|
|
// Read all entries, keep last N
|
|
let mut entries: Vec<crate::store::Node> = Vec::new();
|
|
while let Ok(msg) = capnp::serialize::read_message(&mut reader, capnp::message::ReaderOptions::new()) {
|
|
let log = msg.get_root::<crate::memory_capnp::node_log::Reader>()
|
|
.with_context(|| "read log")?;
|
|
for node_reader in log.get_nodes()
|
|
.with_context(|| "get nodes")? {
|
|
let node = crate::store::Node::from_capnp_migrate(node_reader)?;
|
|
entries.push(node);
|
|
}
|
|
}
|
|
|
|
// Filter by provenance if specified (substring match)
|
|
if let Some(prov) = provenance {
|
|
entries.retain(|n| n.provenance.contains(prov));
|
|
}
|
|
|
|
// Dedup: keep only the latest version of each key
|
|
if dedup {
|
|
let mut seen = std::collections::HashSet::new();
|
|
// Walk backwards so we keep the latest
|
|
entries = entries.into_iter().rev()
|
|
.filter(|n| seen.insert(n.key.clone()))
|
|
.collect();
|
|
entries.reverse();
|
|
}
|
|
|
|
let start = entries.len().saturating_sub(n);
|
|
for node in &entries[start..] {
|
|
let ts = if node.timestamp > 0 && node.timestamp < 4_000_000_000 {
|
|
crate::store::format_datetime(node.timestamp)
|
|
} else {
|
|
format!("(raw:{})", node.timestamp)
|
|
};
|
|
let del = if node.deleted { " [DELETED]" } else { "" };
|
|
if full {
|
|
println!("--- {} (v{}) {} via {} w={:.3}{} ---",
|
|
node.key, node.version, ts, node.provenance, node.weight, del);
|
|
println!("{}\n", node.content);
|
|
} else {
|
|
let preview = crate::util::first_n_chars(&node.content, 100).replace('\n', "\\n");
|
|
println!(" {} v{} w={:.2}{}",
|
|
ts, node.version, node.weight, del);
|
|
println!(" {} via {}", node.key, node.provenance);
|
|
if !preview.is_empty() {
|
|
println!(" {}", preview);
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn cmd_journal_tail(n: usize, full: bool, level: u8) -> Result<()> {
|
|
let entries = memory::journal_tail(None, Some(n as u64), Some(level as u64), None).await?;
|
|
for entry in entries {
|
|
if full {
|
|
println!("--- {} ---", entry.key);
|
|
println!("{}\n", entry.content);
|
|
} else {
|
|
let first_line = entry.content.lines().next().unwrap_or("(empty)");
|
|
println!("{}: {}", entry.key, first_line);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn cmd_journal_write(name: &str, text: &[String]) -> Result<()> {
|
|
if text.is_empty() {
|
|
bail!("journal write requires text");
|
|
}
|
|
super::check_dry_run();
|
|
let body = text.join(" ");
|
|
|
|
let result = memory::journal_new(None, name, name, &body, Some(0)).await?;
|
|
println!("{}", result);
|
|
Ok(())
|
|
}
|
|
|