diff --git a/crates/relicario-cli/src/main.rs b/crates/relicario-cli/src/main.rs
index f7a8bea..ee70fa3 100644
--- a/crates/relicario-cli/src/main.rs
+++ b/crates/relicario-cli/src/main.rs
@@ -818,7 +818,146 @@ fn cmd_list(
}
Ok(())
}
-fn cmd_edit(_query: String) -> Result<()> { bail!("not yet implemented"); }
+fn cmd_edit(query: String) -> Result<()> {
+ use relicario_core::time::now_unix;
+ use relicario_core::ItemCore;
+ use zeroize::Zeroizing;
+
+ let vault = crate::session::UnlockedVault::unlock_interactive()?;
+ let mut manifest = vault.load_manifest()?;
+ let entry = resolve_query(&manifest, &query)?;
+ let id = entry.id.clone();
+ let _ = entry;
+ let mut item = vault.load_item(&id)?;
+
+ eprintln!("Editing: {} ({}) — leave a prompt blank to keep the current value.",
+ item.title, item.id.as_str());
+
+ // Title
+ if let Some(v) = prompt_keep("Title", &item.title)? { item.title = v; }
+ // Group
+ if let Some(v) = prompt_keep_opt("Group", item.group.as_deref())? { item.group = Some(v); }
+ // Tags (comma-separated)
+ if let Some(v) = prompt_keep_opt("Tags (comma-separated)", Some(&item.tags.join(",")))? {
+ item.tags = v.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect();
+ }
+
+ // Core-specific fields. Only Login.password and Card.number/cvv/pin are
+ // history-tracked from the core path.
+ match &mut item.core {
+ ItemCore::Login(l) => {
+ if let Some(v) = prompt_keep_opt("Username", l.username.as_deref())? { l.username = Some(v); }
+ if let Some(v) = prompt_keep_opt("URL", l.url.as_ref().map(|u| u.as_str()))? {
+ l.url = Some(url::Url::parse(&v).with_context(|| format!("invalid URL: {v}"))?);
+ }
+ if prompt_yesno("Change password?")? {
+ let old = l.password.clone();
+ let new_pw = Zeroizing::new(rpassword::prompt_password("New password: ")?);
+ l.password = Some(new_pw);
+ if let Some(old_pw) = old {
+ push_history(&mut item.field_history, "login_password",
+ Zeroizing::new(old_pw.as_str().to_string()));
+ }
+ }
+ }
+ ItemCore::SecureNote(n) => {
+ if prompt_yesno("Edit body?")? {
+ let old = n.body.clone();
+ eprintln!("Enter new body; end with Ctrl-D:");
+ let mut s = String::new();
+ std::io::Read::read_to_string(&mut std::io::stdin(), &mut s)?;
+ n.body = Zeroizing::new(s);
+ push_history(&mut item.field_history, "secure_note_body",
+ Zeroizing::new(old.as_str().to_string()));
+ }
+ }
+ ItemCore::Identity(i) => {
+ if let Some(v) = prompt_keep_opt("Full name", i.full_name.as_deref())? { i.full_name = Some(v); }
+ if let Some(v) = prompt_keep_opt("Email", i.email.as_deref())? { i.email = Some(v); }
+ if let Some(v) = prompt_keep_opt("Phone", i.phone.as_deref())? { i.phone = Some(v); }
+ }
+ ItemCore::Card(c) => {
+ if let Some(v) = prompt_keep_opt("Holder", c.holder.as_deref())? { c.holder = Some(v); }
+ if prompt_yesno("Change card number?")? {
+ let old = c.number.clone();
+ c.number = Some(Zeroizing::new(rpassword::prompt_password("New number: ")?));
+ if let Some(o) = old {
+ push_history(&mut item.field_history, "card_number",
+ Zeroizing::new(o.as_str().to_string()));
+ }
+ }
+ }
+ ItemCore::Key(k) => {
+ if prompt_yesno("Replace key material?")? {
+ eprintln!("Paste new key material; end with Ctrl-D:");
+ let mut s = String::new();
+ std::io::Read::read_to_string(&mut std::io::stdin(), &mut s)?;
+ let old = k.key_material.clone();
+ k.key_material = Zeroizing::new(s);
+ push_history(&mut item.field_history, "key_material",
+ Zeroizing::new(old.as_str().to_string()));
+ }
+ }
+ ItemCore::Document(_) => {
+ eprintln!("Document items: use `relicario attach` / `relicario extract` instead.");
+ }
+ ItemCore::Totp(_) => {
+ eprintln!("TOTP rotation not yet implemented — delete and re-add for now.");
+ }
+ }
+
+ item.modified = now_unix();
+ vault.save_item(&item)?;
+ manifest.upsert(&item);
+ vault.save_manifest(&manifest)?;
+ commit_paths(&vault, &format!("edit: {} ({})", item.title, item.id.as_str()),
+ &[&format!("items/{}.enc", item.id.as_str()), "manifest.enc"])?;
+ eprintln!("Updated {}", item.id.as_str());
+ Ok(())
+}
+
+fn prompt_keep(label: &str, current: &str) -> Result