feat(cli): relicario add login with flag + interactive prompting
Unlocks vault, builds LoginCore from flags (password via rpassword if --password-prompt), saves item + manifest, commits via hardened git. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
zeroize = "1"
|
||||
url = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
|
||||
@@ -350,7 +350,87 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
|
||||
eprintln!(" \u{2192} back this file up somewhere safe; it is your second factor.");
|
||||
Ok(())
|
||||
}
|
||||
fn cmd_add(_kind: AddKind) -> Result<()> { bail!("not yet implemented"); }
|
||||
fn cmd_add(kind: AddKind) -> Result<()> {
|
||||
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||
let mut manifest = vault.load_manifest()?;
|
||||
|
||||
let item = match kind {
|
||||
AddKind::Login { title, username, url, password_prompt, password, group, tags, favorite } => {
|
||||
use relicario_core::item_types::LoginCore;
|
||||
use relicario_core::{Item, ItemCore};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
let title = title.map(Ok).unwrap_or_else(|| prompt("Title"))?;
|
||||
let username = username.or_else(|| prompt_optional("Username").ok().flatten());
|
||||
let url = url.or_else(|| prompt_optional("URL").ok().flatten());
|
||||
let parsed_url = match url {
|
||||
Some(s) => Some(url::Url::parse(&s)
|
||||
.with_context(|| format!("invalid URL: {s}"))?),
|
||||
None => None,
|
||||
};
|
||||
let password = if let Some(p) = password {
|
||||
Some(Zeroizing::new(p))
|
||||
} else if password_prompt {
|
||||
Some(Zeroizing::new(rpassword::prompt_password("Password: ")?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let core = ItemCore::Login(LoginCore {
|
||||
username,
|
||||
password,
|
||||
url: parsed_url,
|
||||
totp: None,
|
||||
});
|
||||
let mut item = Item::new(title, core);
|
||||
item.group = group;
|
||||
item.tags = tags;
|
||||
item.favorite = favorite;
|
||||
item
|
||||
}
|
||||
// Task 8 fills in the other variants.
|
||||
_ => anyhow::bail!("item kind not yet implemented"),
|
||||
};
|
||||
|
||||
vault.save_item(&item)?;
|
||||
manifest.upsert(&item);
|
||||
vault.save_manifest(&manifest)?;
|
||||
|
||||
commit_paths(&vault, &format!("add: {} ({})", item.title, item.id.as_str()),
|
||||
&[&format!("items/{}.enc", item.id.as_str()), "manifest.enc"])?;
|
||||
|
||||
eprintln!("Added: {} (id={})", item.title, item.id.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prompt(label: &str) -> Result<String> {
|
||||
eprint!("{label}: ");
|
||||
std::io::Write::flush(&mut std::io::stderr())?;
|
||||
let mut s = String::new();
|
||||
std::io::stdin().read_line(&mut s)?;
|
||||
let trimmed = s.trim().to_string();
|
||||
if trimmed.is_empty() { anyhow::bail!("{label} required"); }
|
||||
Ok(trimmed)
|
||||
}
|
||||
|
||||
fn prompt_optional(label: &str) -> Result<Option<String>> {
|
||||
eprint!("{label} (leave blank to skip): ");
|
||||
std::io::Write::flush(&mut std::io::stderr())?;
|
||||
let mut s = String::new();
|
||||
std::io::stdin().read_line(&mut s)?;
|
||||
let trimmed = s.trim().to_string();
|
||||
Ok(if trimmed.is_empty() { None } else { Some(trimmed) })
|
||||
}
|
||||
|
||||
fn commit_paths(vault: &crate::session::UnlockedVault, message: &str, paths: &[&str]) -> Result<()> {
|
||||
let mut args: Vec<&str> = vec!["add"];
|
||||
args.extend_from_slice(paths);
|
||||
let status = crate::helpers::git_command(vault.root(), &args).status()?;
|
||||
if !status.success() { anyhow::bail!("git add failed"); }
|
||||
let status = crate::helpers::git_command(vault.root(), &["commit", "-m", message]).status()?;
|
||||
if !status.success() { anyhow::bail!("git commit failed"); }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_get(_query: String, _show: bool, _copy: bool) -> Result<()> { bail!("not yet implemented"); }
|
||||
fn cmd_list(_t: Option<String>, _g: Option<String>, _tag: Option<String>, _trashed: bool) -> Result<()> { bail!("not yet implemented"); }
|
||||
fn cmd_edit(_query: String) -> Result<()> { bail!("not yet implemented"); }
|
||||
|
||||
Reference in New Issue
Block a user