use clap::{Args, Parser, Subcommand, ValueEnum};
use std::ffi::OsString;
use std::path::PathBuf;
pub const TRANSPORT_ENV: &str = "SEFY_TRANSPORT";
pub const REMOTE_NAME_ENV: &str = "SEFY_REMOTE_NAME";
pub const DEFAULT_REMOTE_NAME: &str = "vault";
#[derive(Debug, Parser)]
#[command(name = "sefy", version, about, long_about = None)]
pub struct Cli {
#[arg(
long,
short = 'f',
global = true,
env = "SEFY_VAULT",
value_name = "FILE"
)]
pub vault: Option<PathBuf>,
#[arg(long, global = true, value_name = "VAR")]
pub password_env: Option<String>,
#[arg(
long,
global = true,
env = "SEFY_AUTO_SYNC",
value_name = "WHEN",
value_enum,
ignore_case = true
)]
pub auto_sync: Option<Switch>,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Init,
Add {
#[command(subcommand)]
kind: AddKind,
},
Get(GetArgs),
Show {
reference: String,
},
Ls(ListArgs),
Find(FindArgs),
Edit(EditArgs),
History(HistoryArgs),
Restore(RestoreArgs),
Rm {
reference: String,
#[arg(long, short = 'y')]
yes: bool,
},
Extract {
reference: String,
#[arg(long, short = 'o', value_name = "PATH")]
output: Option<PathBuf>,
#[arg(long)]
force: bool,
},
Tags,
Gen(GenArgs),
Open(OpenArgs),
Otp(OtpArgs),
Fill(FillArgs),
Run(RunArgs),
Audit,
Status,
Export {
#[arg(long, short = 'o', value_name = "PATH")]
output: Option<PathBuf>,
#[arg(long, value_enum, default_value_t = ExportFormat::Sefy)]
format: ExportFormat,
#[arg(long)]
with_history: bool,
#[arg(long)]
i_know_this_writes_plaintext: bool,
#[arg(long)]
force: bool,
},
Import {
#[arg(value_name = "PATH")]
input: Option<PathBuf>,
},
Merge {
#[arg(value_name = "FILE")]
other: PathBuf,
#[arg(long, value_name = "VAR")]
other_password_env: Option<String>,
#[arg(long)]
dry_run: bool,
},
ChangePassword {
#[arg(long, value_name = "VAR")]
new_password_env: Option<String>,
},
Wipe,
Push(RemoteArgs),
Pull(PullArgs),
Sync(PullArgs),
Doctor(RemoteArgs),
Plugin {
#[command(subcommand)]
action: PluginAction,
},
Completions {
shell: clap_complete::Shell,
},
}
#[derive(Debug, Args)]
pub struct RemoteArgs {
#[arg(long, short = 'p', value_name = "NAME", env = TRANSPORT_ENV)]
pub transport: Option<String>,
#[arg(
long,
value_name = "NAME",
env = REMOTE_NAME_ENV,
default_value = DEFAULT_REMOTE_NAME
)]
pub name: String,
}
#[derive(Debug, Args)]
pub struct PullArgs {
#[command(flatten)]
pub remote: RemoteArgs,
#[arg(long, value_name = "VAR", conflicts_with = "ask_remote_password")]
pub remote_password_env: Option<String>,
#[arg(long)]
pub ask_remote_password: bool,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Switch {
#[value(alias = "true", alias = "yes", alias = "1")]
On,
#[value(alias = "false", alias = "no", alias = "0")]
Off,
}
#[derive(Debug, Subcommand)]
pub enum PluginAction {
List {
#[arg(long)]
paths: bool,
},
}
#[derive(Debug, Subcommand)]
pub enum AddKind {
Note {
title: String,
#[arg(long, short = 't', conflicts_with = "editor")]
text: Option<String>,
#[arg(long, short = 'e')]
editor: bool,
#[arg(long, value_delimiter = ',')]
tag: Vec<String>,
},
#[command(alias = "credential")]
Login {
title: String,
#[arg(long, short = 'l')]
login: String,
#[arg(long, short = 'u')]
url: Option<String>,
#[arg(long)]
totp: Option<String>,
#[arg(long)]
notes: Option<String>,
#[arg(long = "item-password-env", value_name = "VAR")]
item_password_env: Option<String>,
#[arg(long, value_delimiter = ',')]
tag: Vec<String>,
},
Card {
title: String,
#[arg(long)]
holder: Option<String>,
#[arg(long, value_name = "MM/YY")]
expires: Option<String>,
#[arg(long)]
notes: Option<String>,
#[arg(long)]
no_cvv: bool,
#[arg(long)]
no_pin: bool,
#[arg(long, value_name = "VAR")]
number_env: Option<String>,
#[arg(long, value_name = "VAR", conflicts_with = "no_cvv")]
cvv_env: Option<String>,
#[arg(long, value_name = "VAR", conflicts_with = "no_pin")]
pin_env: Option<String>,
#[arg(long, value_delimiter = ',')]
tag: Vec<String>,
},
SshKey {
title: String,
#[arg(long, value_name = "PATH")]
private_key: PathBuf,
#[arg(long, value_name = "PATH")]
public_key: Option<PathBuf>,
#[arg(long)]
host: Option<String>,
#[arg(long)]
notes: Option<String>,
#[arg(long)]
no_passphrase: bool,
#[arg(long, value_name = "VAR", conflicts_with = "no_passphrase")]
passphrase_env: Option<String>,
#[arg(long, value_delimiter = ',')]
tag: Vec<String>,
},
Wifi(RecordArgs),
ApiToken(RecordArgs),
Bank(RecordArgs),
File {
path: PathBuf,
#[arg(long, short = 'T')]
title: Option<String>,
#[arg(long, value_delimiter = ',')]
tag: Vec<String>,
},
}
#[derive(Debug, Args)]
pub struct RecordArgs {
pub title: String,
#[arg(long, value_name = "NAME=VALUE")]
pub set: Vec<String>,
#[arg(long, value_name = "NAME=VAR")]
pub secret_env: Vec<String>,
#[arg(long, value_name = "NAME")]
pub skip: Vec<String>,
#[arg(long, value_delimiter = ',')]
pub tag: Vec<String>,
}
#[derive(Debug, Args)]
pub struct GetArgs {
pub reference: String,
#[arg(long, value_name = "NAME")]
pub field: Option<String>,
#[arg(long)]
pub stdout: bool,
#[arg(long, value_name = "SECONDS", default_value_t = sefy_core::clipboard::HOLD_SECONDS)]
pub clear_after: u64,
}
#[derive(Debug, Args)]
pub struct GenArgs {
#[arg(
long,
short = 'n',
value_name = "N",
default_value_t = 20,
conflicts_with = "words"
)]
pub length: usize,
#[arg(long, conflicts_with_all = ["words", "pronounceable"])]
pub no_uppercase: bool,
#[arg(long, conflicts_with_all = ["words", "pronounceable"])]
pub no_digits: bool,
#[arg(long, conflicts_with_all = ["words", "pronounceable"])]
pub no_symbols: bool,
#[arg(long, conflicts_with = "words")]
pub pronounceable: bool,
#[arg(long, short = 'w', value_name = "N")]
pub words: Option<usize>,
#[arg(long, value_enum, default_value = "en", requires = "words")]
pub lang: WordList,
#[arg(long, value_name = "TEXT", default_value = "-", requires = "words")]
pub separator: String,
#[arg(long, value_name = "TITLE")]
pub save: Option<String>,
#[arg(long, short = 'l', requires = "save")]
pub login: Option<String>,
#[arg(long, short = 'u', requires = "save")]
pub url: Option<String>,
#[arg(long, value_delimiter = ',', requires = "save")]
pub tag: Vec<String>,
#[arg(long)]
pub stdout: bool,
#[arg(long, value_name = "SECONDS", default_value_t = sefy_core::clipboard::HOLD_SECONDS)]
pub clear_after: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum ExportFormat {
Sefy,
Keepass,
Csv,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum WordList {
En,
Ru,
}
impl From<WordList> for sefy_core::Language {
fn from(list: WordList) -> Self {
match list {
WordList::En => Self::English,
WordList::Ru => Self::Russian,
}
}
}
#[derive(Debug, Args)]
pub struct OpenArgs {
pub reference: String,
#[arg(long)]
pub no_password: bool,
#[arg(long, value_name = "SECONDS", default_value_t = sefy_core::clipboard::HOLD_SECONDS)]
pub clear_after: u64,
}
#[derive(Debug, Args)]
pub struct OtpArgs {
pub reference: String,
#[arg(long)]
pub set: bool,
#[arg(long, value_name = "VAR")]
pub key_env: Option<String>,
#[arg(long, conflicts_with = "stdout")]
pub qr: bool,
#[arg(long)]
pub stdout: bool,
#[arg(long, value_name = "SECONDS", default_value_t = sefy_core::clipboard::HOLD_SECONDS)]
pub clear_after: u64,
}
#[derive(Debug, Args)]
pub struct FillArgs {
pub reference: String,
#[arg(long, value_name = "SECONDS", default_value_t = sefy_core::clipboard::HOLD_SECONDS)]
pub clear_after: u64,
}
#[derive(Debug, Args)]
pub struct RunArgs {
#[arg(
long,
short = 'e',
value_name = "VAR=REFERENCE[#FIELD]",
required = true
)]
pub env: Vec<String>,
#[arg(last = true, required = true, value_name = "COMMAND")]
pub command: Vec<OsString>,
}
#[derive(Debug, Args)]
pub struct ListArgs {
#[arg(long, value_enum)]
pub kind: Option<Kind>,
#[arg(long, value_delimiter = ',')]
pub tag: Vec<String>,
}
#[derive(Debug, Args)]
pub struct FindArgs {
pub text: Option<String>,
#[arg(long, value_enum)]
pub kind: Option<Kind>,
#[arg(long, value_delimiter = ',')]
pub tag: Vec<String>,
}
#[derive(Debug, Args)]
pub struct EditArgs {
pub reference: String,
#[arg(long)]
pub title: Option<String>,
#[arg(long, short = 't', conflicts_with = "editor")]
pub text: Option<String>,
#[arg(long, short = 'e')]
pub editor: bool,
#[arg(long, value_name = "NAME=VALUE")]
pub set: Vec<String>,
#[arg(long, value_name = "NAME")]
pub set_secret: Vec<String>,
#[arg(long, value_name = "NAME")]
pub unset: Vec<String>,
#[arg(long, value_delimiter = ',')]
pub tag: Vec<String>,
#[arg(long, conflicts_with = "tag")]
pub clear_tags: bool,
}
#[derive(Debug, Args)]
pub struct HistoryArgs {
pub reference: String,
#[arg(value_name = "VERSION")]
pub version: Option<usize>,
}
#[derive(Debug, Args)]
pub struct RestoreArgs {
pub reference: String,
#[arg(value_name = "VERSION")]
pub version: usize,
#[arg(long, value_name = "NAME")]
pub field: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Kind {
Note,
#[value(alias = "credential")]
Login,
Card,
SshKey,
Wifi,
ApiToken,
Bank,
File,
}
impl From<Kind> for sefy_core::ItemKind {
fn from(kind: Kind) -> Self {
match kind {
Kind::Note => Self::Note,
Kind::Login => Self::Login,
Kind::Card => Self::Card,
Kind::SshKey => Self::SshKey,
Kind::Wifi => Self::Wifi,
Kind::ApiToken => Self::ApiToken,
Kind::Bank => Self::Bank,
Kind::File => Self::File,
}
}
}