use ipld_core::ipld::Ipld;
use mnem_core::objects::{Edge, Node, Tombstone, View};
use super::*;
#[derive(clap::Args, Debug)]
#[command(after_long_help = "\
Examples:
mnem show # show the current op-head
mnem show <cid> # decode any Node/Edge/Commit/View/Operation/IndexSet/Tombstone
mnem log -n 1 && mnem show
")]
pub(crate) struct Args {
pub cid: Option<String>,
}
pub(crate) fn run(override_path: Option<&Path>, args: Args) -> Result<()> {
let (_dir, r, bs, _ohs) = repo::open_all(override_path)?;
let target_cid = match args.cid {
Some(s) => super::resolve_commitish(&r, &s)?,
None => r.op_id().clone(),
};
let bytes = bs
.get(&target_cid)?
.ok_or_else(|| anyhow!("block {target_cid} not found in this blockstore"))?;
let kind = peek_kind(&bytes);
println!("cid {target_cid}");
println!("size {} bytes", bytes.len());
println!(
"kind {}",
kind.as_deref().unwrap_or("<unknown (no _kind field)>")
);
match kind.as_deref() {
Some("node") => show_node(&bytes, &r, &target_cid),
Some("edge") => show_edge(&bytes),
Some("commit") => show_commit(&bytes),
Some("operation") => show_operation(&bytes, &bs),
Some("view") => show_view(&bytes),
Some("index_set") => show_index_set(&bytes),
Some("tombstone") => show_tombstone(&bytes),
_ => {
println!("(no structured pretty-printer; try `mnem cat-file {target_cid} --json`)");
Ok(())
}
}
}
fn peek_kind(bytes: &[u8]) -> Option<String> {
let ipld: Ipld = from_canonical_bytes(bytes).ok()?;
match ipld {
Ipld::Map(m) => match m.get("_kind")? {
Ipld::String(s) => Some(s.clone()),
_ => None,
},
_ => None,
}
}
fn show_node(bytes: &[u8], repo: &ReadonlyRepo, node_cid: &mnem_core::id::Cid) -> Result<()> {
let n: Node = from_canonical_bytes(bytes).context("decoding node")?;
println!("id {}", n.id.to_uuid_string());
println!("ntype {}", n.ntype);
if let Some(s) = &n.summary {
println!(
"summary {}",
if s.len() <= 120 {
s.clone()
} else {
format!(
"{}... ({}B)",
s.chars().take(117).collect::<String>(),
s.len()
)
}
);
}
if !n.props.is_empty() {
println!("props ({})", n.props.len());
for (k, v) in &n.props {
println!(" {k:<16} {}", ipld_preview(v));
}
}
if let Some(c) = &n.content {
println!("content {} bytes", c.len());
}
if let Some(model) = configured_model_fq()
&& let Some(emb) = repo.embedding_for(node_cid, &model)?
{
println!(
"embed model={} dim={} dtype={:?}",
emb.model, emb.dim, emb.dtype
);
}
Ok(())
}
fn configured_model_fq() -> Option<String> {
let cfg = config::load_global().ok()?;
let pc = config::resolve_embedder(&cfg)?;
Some(model_fq_of(&pc))
}
fn model_fq_of(pc: &mnem_embed_providers::ProviderConfig) -> String {
use mnem_embed_providers::ProviderConfig as PC;
match pc {
PC::Openai(c) => format!("openai:{}", c.model),
PC::Ollama(c) => format!("ollama:{}", c.model),
PC::Onnx(c) => format!("onnx:{}", c.model),
}
}
fn show_edge(bytes: &[u8]) -> Result<()> {
let e: Edge = from_canonical_bytes(bytes).context("decoding edge")?;
println!("id {}", e.id.to_uuid_string());
println!("etype {}", e.etype);
println!("src {}", e.src.to_uuid_string());
println!("dst {}", e.dst.to_uuid_string());
if !e.props.is_empty() {
println!("props ({})", e.props.len());
for (k, v) in &e.props {
println!(" {k:<16} {}", ipld_preview(v));
}
}
Ok(())
}
fn show_commit(bytes: &[u8]) -> Result<()> {
let c: Commit = from_canonical_bytes(bytes).context("decoding commit")?;
println!("change_id {}", c.change_id.to_uuid_string());
println!("time {}us", c.time);
println!("author {}", c.author);
if let Some(a) = &c.agent_id {
println!("agent_id {a}");
}
if let Some(t) = &c.task_id {
println!("task_id {t}");
}
println!("message {}", c.message);
println!(
"parents {}",
if c.parents.is_empty() {
"<root>".into()
} else {
c.parents
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
}
);
println!("nodes {}", c.nodes);
println!("edges {}", c.edges);
println!("schema {}", c.schema);
if let Some(i) = &c.indexes {
println!("indexes {i}");
}
if c.signature.is_some() {
println!("signature <present>");
}
Ok(())
}
fn show_operation(
bytes: &[u8],
bs: &std::sync::Arc<dyn mnem_core::store::Blockstore>,
) -> Result<()> {
let op: Operation = from_canonical_bytes(bytes).context("decoding operation")?;
println!("time {}us", op.time);
println!("author {}", op.author);
if let Some(a) = &op.agent_id {
println!("agent_id {a}");
}
if let Some(t) = &op.task_id {
println!("task_id {t}");
}
println!("description {}", op.description);
println!(
"parents {}",
if op.parents.is_empty() {
"<root>".into()
} else {
op.parents
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
}
);
println!("view {}", op.view);
if let Some(view_bytes) = bs.get(&op.view)? {
let view: View = from_canonical_bytes(&view_bytes).context("decoding view")?;
if let Some(head_cid) = view.heads.first() {
println!(" head {head_cid}");
}
println!(" refs {}", view.refs.len());
}
Ok(())
}
fn show_view(bytes: &[u8]) -> Result<()> {
let v: View = from_canonical_bytes(bytes).context("decoding view")?;
println!("heads {}", v.heads.len());
for h in &v.heads {
println!(" {h}");
}
println!("refs {}", v.refs.len());
for (name, target) in &v.refs {
match target {
RefTarget::Normal { target } => println!(" {name} -> {target}"),
RefTarget::Conflicted { adds, removes } => {
println!(" {name} conflicted(+{} -{})", adds.len(), removes.len());
}
}
}
if let Some(rr) = &v.remote_refs {
let total: usize = rr.values().map(BTreeMap::len).sum();
println!("remote_refs {} across {} remote(s)", total, rr.len());
}
if !v.tombstones.is_empty() {
println!("tombstones {}", v.tombstones.len());
}
Ok(())
}
fn show_index_set(bytes: &[u8]) -> Result<()> {
let idx: IndexSet = from_canonical_bytes(bytes).context("decoding index_set")?;
println!("labels {}", idx.nodes_by_label.len());
for (label, cid) in &idx.nodes_by_label {
println!(" {label:<20} {cid}");
}
let prop_count: usize = idx.nodes_by_prop.values().map(BTreeMap::len).sum();
println!(
"nodes_by_prop {} across {} label(s)",
prop_count,
idx.nodes_by_prop.len()
);
println!(
"outgoing {}",
idx.outgoing
.as_ref()
.map_or_else(|| "<none>".into(), ToString::to_string)
);
println!(
"incoming {}",
idx.incoming
.as_ref()
.map_or_else(|| "<none>".into(), ToString::to_string)
);
Ok(())
}
fn show_tombstone(bytes: &[u8]) -> Result<()> {
let t: Tombstone = from_canonical_bytes(bytes).context("decoding tombstone")?;
println!("tombstoned_at {}us", t.tombstoned_at);
println!("reason {}", t.reason);
Ok(())
}
use std::collections::BTreeMap;