consciousness/src/cli/journal.rs

99 lines
3.3 KiB
Rust
Raw Normal View History

// cli/journal.rs — journal subcommand handlers
pub fn cmd_tail(n: usize, full: bool, provenance: Option<&str>, dedup: bool) -> Result<(), String> {
let path = crate::store::nodes_path();
if !path.exists() {
return Err("No node log found".into());
}
use std::io::BufReader;
let file = std::fs::File::open(&path)
.map_err(|e| format!("open {}: {}", path.display(), e))?;
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>()
.map_err(|e| format!("read log: {}", e))?;
for node_reader in log.get_nodes()
.map_err(|e| format!("get nodes: {}", e))? {
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 fn cmd_journal_tail(n: usize, full: bool, level: u8) -> Result<(), String> {
let format = if full { "full" } else { "compact" };
let result = crate::mcp_server::memory_rpc(
"journal_tail",
serde_json::json!({"count": n, "level": level, "format": format}),
).map_err(|e| e.to_string())?;
print!("{}", result);
Ok(())
}
pub fn cmd_journal_write(name: &str, text: &[String]) -> Result<(), String> {
if text.is_empty() {
return Err("journal write requires text".into());
}
super::check_dry_run();
let body = text.join(" ");
let result = crate::mcp_server::memory_rpc(
"journal_new",
serde_json::json!({
"name": name,
"title": name,
"body": body,
"level": 0
}),
).map_err(|e| e.to_string())?;
println!("{}", result);
Ok(())
}