refactor(cli): move cmd_generate + cmd_rate into commands/

This commit is contained in:
adlee-was-taken
2026-05-06 18:27:41 -04:00
parent 52400230e0
commit 17bde162cd
4 changed files with 101 additions and 91 deletions

View File

@@ -0,0 +1,68 @@
//! `relicario generate` — emit a fresh password or BIP39 passphrase.
use anyhow::Result;
pub fn cmd_generate(
length: Option<u32>,
bip39: bool,
words: Option<u32>,
symbols: Option<String>,
separator: Option<String>,
) -> Result<()> {
use relicario_core::{
generate_passphrase, generate_password, Capitalization, CharClasses,
GeneratorRequest, SymbolCharset,
};
// If we're inside a vault, unlock and pull `generator_defaults`. Outside
// a vault, this stays a fast standalone CSPRNG tool (no unlock prompt).
let vault_defaults: Option<GeneratorRequest> = if crate::helpers::vault_dir().is_ok() {
let vault = crate::session::UnlockedVault::unlock_interactive()?;
Some(vault.load_settings()?.generator_defaults)
} else {
None
};
// `--bip39` flag forces Bip39 mode; otherwise use whatever mode the
// vault default is in (Random when no vault).
let use_bip39 = bip39 || matches!(vault_defaults, Some(GeneratorRequest::Bip39 { .. }));
let output = if use_bip39 {
let (def_words, def_sep, def_cap) = match &vault_defaults {
Some(GeneratorRequest::Bip39 { word_count, separator, capitalization }) => {
(*word_count, separator.clone(), *capitalization)
}
_ => (5, " ".to_string(), Capitalization::Lower),
};
generate_passphrase(&GeneratorRequest::Bip39 {
word_count: words.unwrap_or(def_words),
separator: separator.unwrap_or(def_sep),
capitalization: def_cap,
})?
} else {
let (def_length, def_classes, def_charset) = match &vault_defaults {
Some(GeneratorRequest::Random { length, classes, symbol_charset }) => {
(*length, *classes, symbol_charset.clone())
}
_ => (
20,
CharClasses { lower: true, upper: true, digits: true, symbols: true },
SymbolCharset::SafeOnly,
),
};
let symbol_charset = match symbols.as_deref() {
None => def_charset,
Some("safe") => SymbolCharset::SafeOnly,
Some("extended") => SymbolCharset::Extended,
Some(other) => SymbolCharset::Custom(other.to_string()),
};
generate_password(&GeneratorRequest::Random {
length: length.unwrap_or(def_length),
classes: def_classes,
symbol_charset,
})?
};
println!("{}", output.as_str());
Ok(())
}

View File

@@ -5,3 +5,6 @@
//! command modules (e.g. `commit_paths`, `resolve_query`) are defined in
//! this file as `pub(crate)` so siblings can pull them in via
//! `use crate::commands::*`.
pub mod generate;
pub mod rate;

View File

@@ -0,0 +1,28 @@
//! `relicario rate` — score a passphrase via zxcvbn.
use anyhow::Result;
pub fn cmd_rate(passphrase: String) -> Result<()> {
let pw: String = if passphrase == "-" {
use std::io::BufRead;
let stdin = std::io::stdin();
let mut line = String::new();
stdin.lock().read_line(&mut line)?;
line.trim_end_matches(&['\r', '\n'][..]).to_string()
} else {
passphrase
};
let est = relicario_core::generators::rate_passphrase(&pw);
let label = match est.score {
0 => "very weak",
1 => "weak",
2 => "fair",
3 => "good",
4 => "strong",
_ => "?",
};
println!("score: {}/4 ({})", est.score, label);
println!("guesses: ~10^{:.1}", est.guesses_log10);
println!("note: init requires score ≥ 3 (see `relicario init`)");
Ok(())
}

View File

@@ -443,7 +443,7 @@ fn main() -> Result<()> {
Commands::Extract { query, aid, out } => cmd_extract(query, aid, out),
Commands::Detach { query, aid } => cmd_detach(query, aid),
Commands::Generate { length, bip39, words, symbols, separator } => {
cmd_generate(length, bip39, words, symbols, separator)
commands::generate::cmd_generate(length, bip39, words, symbols, separator)
}
Commands::Settings { action } => cmd_settings(action),
Commands::Sync => cmd_sync(),
@@ -454,7 +454,7 @@ fn main() -> Result<()> {
generate(shell, &mut cmd, "relicario", &mut std::io::stdout());
Ok(())
}
Commands::Rate { passphrase } => cmd_rate(passphrase),
Commands::Rate { passphrase } => commands::rate::cmd_rate(passphrase),
Commands::Device { action } => cmd_device(action),
Commands::RecoveryQr { cmd } => cmd_recovery_qr(cmd),
}
@@ -1979,70 +1979,6 @@ fn cmd_detach(query: String, aid: String) -> Result<()> {
Ok(())
}
fn cmd_generate(
length: Option<u32>,
bip39: bool,
words: Option<u32>,
symbols: Option<String>,
separator: Option<String>,
) -> Result<()> {
use relicario_core::{
generate_passphrase, generate_password, Capitalization, CharClasses,
GeneratorRequest, SymbolCharset,
};
// If we're inside a vault, unlock and pull `generator_defaults`. Outside
// a vault, this stays a fast standalone CSPRNG tool (no unlock prompt).
let vault_defaults: Option<GeneratorRequest> = if crate::helpers::vault_dir().is_ok() {
let vault = crate::session::UnlockedVault::unlock_interactive()?;
Some(vault.load_settings()?.generator_defaults)
} else {
None
};
// `--bip39` flag forces Bip39 mode; otherwise use whatever mode the
// vault default is in (Random when no vault).
let use_bip39 = bip39 || matches!(vault_defaults, Some(GeneratorRequest::Bip39 { .. }));
let output = if use_bip39 {
let (def_words, def_sep, def_cap) = match &vault_defaults {
Some(GeneratorRequest::Bip39 { word_count, separator, capitalization }) => {
(*word_count, separator.clone(), *capitalization)
}
_ => (5, " ".to_string(), Capitalization::Lower),
};
generate_passphrase(&GeneratorRequest::Bip39 {
word_count: words.unwrap_or(def_words),
separator: separator.unwrap_or(def_sep),
capitalization: def_cap,
})?
} else {
let (def_length, def_classes, def_charset) = match &vault_defaults {
Some(GeneratorRequest::Random { length, classes, symbol_charset }) => {
(*length, *classes, symbol_charset.clone())
}
_ => (
20,
CharClasses { lower: true, upper: true, digits: true, symbols: true },
SymbolCharset::SafeOnly,
),
};
let symbol_charset = match symbols.as_deref() {
None => def_charset,
Some("safe") => SymbolCharset::SafeOnly,
Some("extended") => SymbolCharset::Extended,
Some(other) => SymbolCharset::Custom(other.to_string()),
};
generate_password(&GeneratorRequest::Random {
length: length.unwrap_or(def_length),
classes: def_classes,
symbol_charset,
})?
};
println!("{}", output.as_str());
Ok(())
}
fn cmd_settings(action: SettingsAction) -> Result<()> {
use relicario_core::{
Capitalization, CharClasses, GeneratorRequest, HistoryRetention,
@@ -2210,31 +2146,6 @@ struct ParamsKdf {
argon2_p: u32,
}
fn cmd_rate(passphrase: String) -> Result<()> {
let pw: String = if passphrase == "-" {
use std::io::BufRead;
let stdin = std::io::stdin();
let mut line = String::new();
stdin.lock().read_line(&mut line)?;
line.trim_end_matches(&['\r', '\n'][..]).to_string()
} else {
passphrase
};
let est = relicario_core::generators::rate_passphrase(&pw);
let label = match est.score {
0 => "very weak",
1 => "weak",
2 => "fair",
3 => "good",
4 => "strong",
_ => "?",
};
println!("score: {}/4 ({})", est.score, label);
println!("guesses: ~10^{:.1}", est.guesses_log10);
println!("note: init requires score ≥ 3 (see `relicario init`)");
Ok(())
}
// ── Device management ─────────────────────────────────────────────────────────
/// Build a `GiteaClient` from flags or environment variables.