//! `relicario list` and `relicario history` — both read-only browse paths. use anyhow::Result; pub fn cmd_list( type_filter: Option, group_filter: Option, tag_filter: Option, trashed: bool, ) -> Result<()> { use relicario_core::ItemType; let vault = crate::session::UnlockedVault::unlock_interactive()?; let manifest = vault.load_manifest()?; crate::helpers::refresh_groups_cache(vault.root(), &manifest); let parsed_type: Option = match type_filter.as_deref() { None => None, Some("login") => Some(ItemType::Login), Some("secure_note") | Some("note") => Some(ItemType::SecureNote), Some("identity") => Some(ItemType::Identity), Some("card") => Some(ItemType::Card), Some("key") => Some(ItemType::Key), Some("document") => Some(ItemType::Document), Some("totp") => Some(ItemType::Totp), Some(other) => anyhow::bail!("unknown type filter: {other}"), }; let mut entries: Vec<_> = manifest.items.values() .filter(|e| { if trashed { e.trashed_at.is_some() } else { e.trashed_at.is_none() } }) .filter(|e| match parsed_type { Some(t) => e.r#type == t, None => true, }) .filter(|e| group_filter.as_ref().is_none_or(|g| e.group.as_deref() == Some(g.as_str()))) .filter(|e| tag_filter.as_ref().is_none_or(|t| e.tags.iter().any(|x| x == t))) .collect(); entries.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase())); if entries.is_empty() { eprintln!("(no items match)"); return Ok(()); } println!("{:<16} {:<14} {:<6} TITLE", "ID", "TYPE", "FAV"); for e in entries { let fav = if e.favorite { " *" } else { "" }; println!("{:<16} {:<14} {:<6} {}", e.id.as_str(), format!("{:?}", e.r#type), fav, e.title); } Ok(()) } pub fn cmd_history(query: String, show: bool, field: Option) -> Result<()> { let vault = crate::session::UnlockedVault::unlock_interactive()?; let manifest = vault.load_manifest()?; let entry = super::resolve_query(&manifest, &query)?; let item = vault.load_item(&entry.id)?; println!("History for {} ({})", item.title, item.id.as_str()); println!(); // Filter and sort the field-id keys so output is deterministic. let mut keys: Vec<&relicario_core::FieldId> = item.field_history.keys().collect(); keys.sort_by(|a, b| a.0.cmp(&b.0)); let mut printed_any = false; for fid in keys { let display_name = fid.0.strip_prefix("core:").unwrap_or(&fid.0); if let Some(filter) = &field { if display_name != filter && fid.0 != *filter { continue; } } let entries = &item.field_history[fid]; if entries.is_empty() { continue; } printed_any = true; println!("{display_name} ({} {})", entries.len(), if entries.len() == 1 { "entry" } else { "entries" }); for (i, e) in entries.iter().enumerate() { let ts = crate::helpers::iso8601(e.replaced_at); if show { println!(" [{i}] {ts} {}", e.value.as_str()); } else { println!(" [{i}] {ts} ********"); } } println!(); } if !printed_any { if field.is_some() { println!("no history for the requested field"); } else { println!("no history captured for this item"); } } else if !show { println!("(use --show to reveal values)"); } Ok(()) }