use anyhow::Result;
use clap::Parser;
#[derive(Parser)]
#[command(name = "pidge")]
#[command(author, version, about)]
#[command(long_about = "A fast CLI for e-mail and calendar.\n\n\
Manage one or more e-mail accounts and browse, search, send, and reply \
to e-mail from your terminal.")]
#[command(propagate_version = true)]
pub struct Cli {
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
pub verbose: u8,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub no_color: bool,
#[arg(long, global = true)]
pub json: bool,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(clap::Subcommand)]
pub enum Commands {
Ai {
#[command(subcommand)]
command: Option<AiCommands>,
},
Account {
#[command(subcommand)]
command: AccountCommands,
},
Mail {
#[command(subcommand)]
command: MailCommands,
},
Calendar {
#[command(subcommand)]
command: Option<CalendarCommands>,
},
Trust {
#[command(subcommand)]
command: TrustCommands,
},
Contacts {
#[command(subcommand)]
command: ContactsCommands,
},
Drafts {
#[command(subcommand)]
command: DraftsCommands,
},
Completion {
#[arg(value_enum)]
shell: Shell,
},
Config {
#[command(subcommand)]
command: ConfigCommands,
},
Categorize {
#[command(subcommand)]
command: CategorizeCommands,
},
Version,
}
#[derive(clap::Subcommand)]
pub enum AiCommands {
Test {
message: Option<String>,
},
Enable,
Disable,
Config,
Status,
Skill {
#[arg(long)]
emit: bool,
#[arg(long)]
from_source: bool,
},
Classify(Box<ClassifyArgs>),
}
#[derive(clap::Args, Debug, Clone)]
pub struct ClassifyArgs {
pub fragment: Option<String>,
#[arg(long, conflicts_with = "fragment")]
pub text: Option<String>,
#[arg(long)]
pub prompt: Option<String>,
#[arg(long, conflicts_with = "prompt")]
pub prompt_file: Option<String>,
#[arg(long, value_delimiter = ',')]
pub labels: Vec<String>,
#[arg(long, conflicts_with_all = ["fragment", "text"])]
pub from: Vec<String>,
#[arg(long, conflicts_with_all = ["fragment", "text"])]
pub older_than: Option<String>,
#[arg(long, conflicts_with_all = ["fragment", "text"])]
pub folder: Option<String>,
#[arg(short = 'n', long)]
pub limit: Option<usize>,
#[arg(long)]
pub account: Vec<String>,
#[arg(long)]
pub parallel: Option<usize>,
#[arg(long)]
pub no_cache: bool,
#[arg(long)]
pub set_category: bool,
}
#[derive(clap::Subcommand)]
pub enum AccountCommands {
Add {
#[arg(long, value_enum, default_value_t = StorageBackendArg::Keychain)]
store: StorageBackendArg,
},
List,
Remove {
email: Option<String>,
#[arg(long, conflicts_with = "email")]
all: bool,
#[arg(short = 'y', long)]
yes: bool,
},
Default {
#[command(subcommand)]
command: Option<DefaultCommands>,
},
MigrateStorage {
email: String,
#[arg(long = "to", value_enum)]
to: StorageBackendArg,
},
}
#[derive(clap::Subcommand)]
pub enum DefaultCommands {
#[command(name = "e-mail")]
EMail {
email: String,
},
Calendar {
email: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum StorageBackendArg {
Keychain,
File,
}
impl From<StorageBackendArg> for pidge_core::TokenStorage {
fn from(v: StorageBackendArg) -> Self {
match v {
StorageBackendArg::Keychain => Self::Keychain,
StorageBackendArg::File => Self::File,
}
}
}
#[derive(clap::Subcommand)]
pub enum MailCommands {
List {
#[arg(long)]
account: Vec<String>,
#[arg(long)]
folder: Option<String>,
#[arg(short = 'n', long, default_value = "25")]
limit: usize,
#[arg(short = 'p', long, default_value = "1")]
page: usize,
#[arg(long)]
unread: bool,
#[arg(short = 'c', long, conflicts_with_all = ["table", "full"])]
compact: bool,
#[arg(short = 't', long, conflicts_with = "full")]
table: bool,
#[arg(short = 'f', long)]
full: bool,
},
Show {
fragment: String,
#[arg(short = 'r', long)]
mark_read: bool,
#[arg(long)]
show_images: bool,
#[arg(long, hide = true)]
raw_html: bool,
},
Search {
query: String,
#[arg(long)]
account: Vec<String>,
#[arg(short = 'n', long, default_value = "25")]
limit: usize,
#[arg(short = 'c', long, conflicts_with_all = ["table", "full"])]
compact: bool,
#[arg(short = 't', long, conflicts_with = "full")]
table: bool,
#[arg(short = 'f', long)]
full: bool,
},
#[command(name = "mark-read")]
MarkRead {
fragment: String,
},
#[command(name = "mark-unread")]
MarkUnread {
fragment: String,
},
Flag {
fragment: String,
},
Unflag {
fragment: String,
},
Archive {
fragment: Option<String>,
#[arg(long, conflicts_with = "fragment")]
from: Vec<String>,
#[arg(long, conflicts_with = "fragment")]
older_than: Option<String>,
#[arg(long)]
account: Vec<String>,
#[arg(short = 'y', long)]
yes: bool,
},
Move {
fragment: Option<String>,
#[arg(long)]
to: String,
#[arg(long, conflicts_with = "fragment")]
from: Vec<String>,
#[arg(long, conflicts_with = "fragment")]
older_than: Option<String>,
#[arg(long)]
account: Vec<String>,
#[arg(short = 'y', long)]
yes: bool,
},
Folders {
#[arg(long)]
account: Vec<String>,
},
Mkdir {
name: String,
#[arg(long)]
account: Vec<String>,
},
Rmdir {
name: String,
#[arg(long)]
account: Vec<String>,
#[arg(short = 'y', long)]
yes: bool,
},
Delete {
fragment: Option<String>,
#[arg(long, conflicts_with = "fragment")]
from: Vec<String>,
#[arg(long, conflicts_with = "fragment")]
older_than: Option<String>,
#[arg(long)]
account: Vec<String>,
#[arg(short = 'y', long)]
yes: bool,
},
Unsubscribe {
fragment: String,
#[arg(short = 'y', long)]
yes: bool,
},
New(ComposeArgs),
Reply {
fragment: String,
#[command(flatten)]
compose: ReplyArgs,
},
#[command(name = "reply-all")]
ReplyAll {
fragment: String,
#[command(flatten)]
compose: ReplyArgs,
},
Forward {
fragment: String,
#[command(flatten)]
compose: ForwardArgs,
},
Attachments {
#[command(subcommand)]
command: MailAttachmentCommands,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum MailAttachmentCommands {
List {
fragment: String,
#[arg(long)]
include_inline: bool,
},
Save {
fragment: String,
name: Option<String>,
#[arg(short = 'o', long)]
out: Option<std::path::PathBuf>,
#[arg(long)]
include_inline: bool,
#[arg(short = 'f', long)]
force: bool,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum CategorizeCommands {
Show { fragment: String },
Set {
fragment: String,
labels: Vec<String>,
},
Add {
fragment: String,
labels: Vec<String>,
},
Clear { fragment: String },
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct ComposeArgs {
#[arg(long)]
pub from: Option<String>,
#[arg(long, value_delimiter = ',')]
pub to: Vec<String>,
#[arg(long, value_delimiter = ',')]
pub cc: Vec<String>,
#[arg(long, value_delimiter = ',')]
pub bcc: Vec<String>,
#[arg(long)]
pub subject: Option<String>,
#[arg(long, conflicts_with = "body_file")]
pub body: Option<String>,
#[arg(long)]
pub body_file: Option<String>,
#[arg(long)]
pub confirm: bool,
#[arg(long)]
pub draft: bool,
#[arg(long)]
pub attach: Vec<std::path::PathBuf>,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct ReplyArgs {
#[arg(long)]
pub from: Option<String>,
#[arg(long, conflicts_with = "body_file")]
pub body: Option<String>,
#[arg(long)]
pub body_file: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[arg(long)]
pub draft: bool,
#[arg(long)]
pub attach: Vec<std::path::PathBuf>,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct ForwardArgs {
#[arg(long)]
pub from: Option<String>,
#[arg(long, value_delimiter = ',')]
pub to: Vec<String>,
#[arg(long, conflicts_with = "body_file")]
pub body: Option<String>,
#[arg(long)]
pub body_file: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[arg(long)]
pub draft: bool,
#[arg(long)]
pub attach: Vec<std::path::PathBuf>,
}
pub const MAIL_SUBCOMMAND_NAMES: &[&str] = &[
"list",
"show",
"search",
"mark-read",
"mark-unread",
"flag",
"unflag",
"archive",
"move",
"folders",
"mkdir",
"rmdir",
"new",
"reply",
"reply-all",
"forward",
"delete",
"unsubscribe",
"attachments",
"help",
];
#[derive(clap::Subcommand)]
pub enum DraftsCommands {
List {
#[arg(long)]
account: Vec<String>,
#[arg(short = 'n', long, default_value = "25")]
limit: usize,
#[arg(short = 'p', long, default_value = "1")]
page: usize,
#[arg(short = 'c', long)]
compact: bool,
},
Show {
fragment: String,
},
Edit {
fragment: String,
},
Send {
fragment: String,
#[arg(short = 'y', long)]
yes: bool,
},
Delete {
fragment: String,
#[arg(short = 'y', long)]
yes: bool,
},
Attachments {
#[command(subcommand)]
command: DraftAttachmentCommands,
},
}
#[derive(clap::Subcommand)]
pub enum DraftAttachmentCommands {
List {
fragment: String,
},
Add {
fragment: String,
path: std::path::PathBuf,
},
Remove {
fragment: String,
name: String,
},
}
#[derive(clap::Subcommand)]
pub enum ContactsCommands {
Refresh {
#[arg(long, default_value = "365")]
days: i64,
#[arg(long)]
account: Vec<String>,
},
Find {
#[arg(default_value = "")]
query: String,
#[arg(short = 'n', long, default_value = "25")]
limit: usize,
},
}
#[derive(clap::Subcommand)]
pub enum TrustCommands {
List,
Add {
email: String,
},
Remove {
email: String,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum ConfigCommands {
Show,
Get { key: String },
Set {
key: String,
value: Option<String>,
#[arg(long)]
file: Option<String>,
},
Unset { key: String },
}
#[derive(Clone, clap::ValueEnum)]
pub enum Shell {
Bash,
Zsh,
Fish,
Powershell,
}
#[derive(clap::Subcommand)]
pub enum CalendarCommands {
List {
#[arg(long)]
account: Vec<String>,
#[arg(long)]
from: Option<String>,
#[arg(long)]
to: Option<String>,
#[arg(long, conflicts_with_all = ["tomorrow", "week", "month"])]
today: bool,
#[arg(long, conflicts_with_all = ["today", "week", "month"])]
tomorrow: bool,
#[arg(long, conflicts_with_all = ["today", "tomorrow", "month"])]
week: bool,
#[arg(long, conflicts_with_all = ["today", "tomorrow", "week"])]
month: bool,
#[arg(long)]
calendar: Option<String>,
#[arg(short = 'n', long, default_value = "50")]
limit: usize,
#[arg(short = 'c', long, conflicts_with = "table")]
compact: bool,
#[arg(short = 't', long)]
table: bool,
},
Show {
fragment: String,
},
Search {
query: String,
#[arg(long)]
account: Vec<String>,
#[arg(long)]
calendar: Option<String>,
#[arg(short = 'n', long, default_value = "25")]
limit: usize,
#[arg(long)]
from: Option<String>,
#[arg(long)]
to: Option<String>,
},
Calendars {
#[command(subcommand)]
command: Option<CalendarsCommands>,
},
New(CalendarNewArgs),
Edit {
fragment: String,
#[command(flatten)]
new: CalendarEditArgs,
},
#[command(name = "move-time")]
MoveTime {
fragment: String,
#[arg(long)]
start: String,
#[arg(long)]
end: Option<String>,
#[arg(long, conflicts_with = "no_notify")]
notify: bool,
#[arg(long)]
no_notify: bool,
#[arg(long)]
series: bool,
},
Duplicate {
fragment: String,
#[arg(long)]
start: Option<String>,
#[arg(long)]
title: Option<String>,
#[arg(long)]
calendar: Option<String>,
},
Delete {
fragment: String,
#[arg(short = 'y', long)]
yes: bool,
#[arg(long)]
series: bool,
},
Cancel {
fragment: String,
#[arg(long)]
comment: Option<String>,
#[arg(short = 'y', long)]
yes: bool,
#[arg(long)]
series: bool,
},
Move {
fragment: String,
#[arg(long = "to")]
to: String,
},
Rsvp {
fragment: String,
#[arg(long, conflicts_with_all = ["tentative", "decline"])]
accept: bool,
#[arg(long, conflicts_with_all = ["accept", "decline"])]
tentative: bool,
#[arg(long, conflicts_with_all = ["accept", "tentative"])]
decline: bool,
#[arg(long)]
comment: Option<String>,
#[arg(long)]
no_notify: bool,
},
}
#[derive(clap::Subcommand)]
pub enum CalendarsCommands {
List,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct CalendarNewArgs {
#[arg(long)]
pub from: Option<String>,
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub start: Option<String>,
#[arg(long)]
pub end: Option<String>,
#[arg(long)]
pub all_day: bool,
#[arg(long)]
pub location: Option<String>,
#[arg(long, conflicts_with = "body_file")]
pub body: Option<String>,
#[arg(long)]
pub body_file: Option<String>,
#[arg(long, value_delimiter = ',')]
pub invite: Vec<String>,
#[arg(long = "invite-optional", value_delimiter = ',')]
pub invite_optional: Vec<String>,
#[arg(long)]
pub repeat: Option<String>,
#[arg(long, value_delimiter = ',')]
pub on: Vec<String>,
#[arg(long, conflicts_with = "count")]
pub until: Option<String>,
#[arg(long)]
pub count: Option<u32>,
#[arg(long, default_value_t = 1)]
pub interval: u32,
#[arg(long)]
pub online: bool,
#[arg(long)]
pub calendar: Option<String>,
#[arg(long)]
pub tz: Option<String>,
#[arg(long)]
pub confirm: bool,
#[arg(short = 'y', long)]
pub yes: bool,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct CalendarEditArgs {
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub start: Option<String>,
#[arg(long)]
pub end: Option<String>,
#[arg(long)]
pub location: Option<String>,
#[arg(long, conflicts_with = "body_file")]
pub body: Option<String>,
#[arg(long)]
pub body_file: Option<String>,
#[arg(long, value_delimiter = ',')]
pub invite: Vec<String>,
#[arg(long = "invite-optional", value_delimiter = ',')]
pub invite_optional: Vec<String>,
#[arg(long, conflicts_with = "no_notify")]
pub notify: bool,
#[arg(long)]
pub no_notify: bool,
#[arg(long)]
pub series: bool,
#[arg(long)]
pub tz: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
}
pub const CALENDAR_SUBCOMMAND_NAMES: &[&str] = &[
"list",
"show",
"search",
"calendars",
"new",
"edit",
"move-time",
"duplicate",
"delete",
"cancel",
"move",
"rsvp",
"help",
];
impl Cli {
pub async fn run(self) -> Result<()> {
match self.command {
Some(Commands::Ai { command }) => crate::commands::ai::run(command, self.json).await,
Some(Commands::Account { command }) => {
crate::commands::account::run(command, self.json).await
}
Some(Commands::Mail { command }) => {
crate::commands::mail::run(command, self.json).await
}
Some(Commands::Calendar { command }) => {
crate::commands::calendar::run(command, self.json).await
}
Some(Commands::Trust { command }) => {
crate::commands::trust::run(command, self.json).await
}
Some(Commands::Contacts { command }) => {
crate::commands::contacts::run(command, self.json).await
}
Some(Commands::Drafts { command }) => {
crate::commands::drafts::run(command, self.json).await
}
Some(Commands::Completion { shell }) => {
crate::commands::completion::generate_completions(shell);
Ok(())
}
Some(Commands::Categorize { command }) => {
crate::commands::mail_categorize::run(command).await
}
Some(Commands::Config { command }) => crate::commands::config_cmd::run(command),
Some(Commands::Version) => {
crate::banner::print_banner_with_version();
Ok(())
}
None => {
use clap::CommandFactory;
let mut cmd = Self::command();
cmd.print_help()?;
println!();
Ok(())
}
}
}
}