diff --git a/Cargo.lock b/Cargo.lock
index 1339b8a30..0b7b56200 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1515,7 +1515,6 @@ version = "2.0.0-beta.6"
 dependencies = [
  "assert2",
  "clap 3.1.18",
- "clap-verbosity-flag",
  "clap_complete",
  "clap_complete_fig",
  "color-eyre",
diff --git a/packages_rs/nextclade-cli/Cargo.toml b/packages_rs/nextclade-cli/Cargo.toml
index 66820ecb2..d15a6ee2f 100644
--- a/packages_rs/nextclade-cli/Cargo.toml
+++ b/packages_rs/nextclade-cli/Cargo.toml
@@ -12,7 +12,6 @@ publish = false
 [dependencies]
 assert2 = "0.3.6"
 clap = { version = "3.1.8", features = ["derive"] }
-clap-verbosity-flag = "1.0.0"
 clap_complete = "3.1.1"
 clap_complete_fig = "3.1.4"
 color-eyre = "0.6.1"
diff --git a/packages_rs/nextclade-cli/src/cli/mod.rs b/packages_rs/nextclade-cli/src/cli/mod.rs
index 9d65c1ddd..01d8e1970 100644
--- a/packages_rs/nextclade-cli/src/cli/mod.rs
+++ b/packages_rs/nextclade-cli/src/cli/mod.rs
@@ -7,3 +7,4 @@ pub mod nextclade_dataset_get;
 pub mod nextclade_dataset_list;
 pub mod nextclade_loop;
 pub mod nextclade_ordered_writer;
+pub mod verbosity;
diff --git a/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs b/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs
index 3e94e229c..407d1caac 100644
--- a/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs
+++ b/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs
@@ -1,12 +1,11 @@
 use crate::cli::common::get_fasta_basename;
+use crate::cli::verbosity::{Verbosity, WarnLevel};
 use clap::{AppSettings, ArgEnum, CommandFactory, Parser, Subcommand, ValueHint};
 use clap_complete::{generate, Generator, Shell};
 use clap_complete_fig::Fig;
-use clap_verbosity_flag::{Verbosity, WarnLevel};
 use eyre::{eyre, ContextCompat, Report, WrapErr};
 use itertools::Itertools;
 use lazy_static::lazy_static;
-use log::LevelFilter;
 use nextclade::align::params::AlignPairwiseParamsOptional;
 use nextclade::make_error;
 use nextclade::utils::global_init::setup_logger;
@@ -18,7 +17,6 @@ use strum_macros::EnumIter;
 
 lazy_static! {
   static ref SHELLS: &'static [&'static str] = &["bash", "elvish", "fish", "fig", "powershell", "zsh"];
-  static ref VERBOSITIES: &'static [&'static str] = &["off", "error", "warn", "info", "debug", "trace"];
 }
 
 #[derive(Parser, Debug)]
@@ -37,17 +35,9 @@ pub struct NextalignArgs {
   #[clap(subcommand)]
   pub command: NextalignCommands,
 
-  /// Set verbosity level [default: warn]
-  #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "silent", possible_values(VERBOSITIES.iter()))]
-  pub verbosity: Option<LevelFilter>,
-
-  /// Disable all console output. Same as --verbosity=off
-  #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "verbosity")]
-  pub silent: bool,
-
   /// Make output more quiet or more verbose
   #[clap(flatten)]
-  pub verbose: Verbosity<WarnLevel>,
+  pub verbosity: Verbosity<WarnLevel>,
 }
 
 #[derive(Subcommand, Debug)]
@@ -426,17 +416,7 @@ pub fn nextalign_check_removed_args(run_args: &mut NextalignRunArgs) -> Result<(
 pub fn nextalign_parse_cli_args() -> Result<NextalignArgs, Report> {
   let mut args = NextalignArgs::parse();
 
-  // --verbosity=<level> and --silent take priority over -v and -q
-  let filter_level = if args.silent {
-    LevelFilter::Off
-  } else {
-    match args.verbosity {
-      None => args.verbose.log_level_filter(),
-      Some(verbosity) => verbosity,
-    }
-  };
-
-  setup_logger(filter_level);
+  setup_logger(args.verbosity.get_filter_level());
 
   match &mut args.command {
     NextalignCommands::Completions { shell } => {
diff --git a/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs b/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs
index acb1afffb..a8faae72b 100644
--- a/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs
+++ b/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs
@@ -1,15 +1,13 @@
 use crate::cli::common::get_fasta_basename;
+use crate::cli::verbosity::{Verbosity, WarnLevel};
 use crate::io::http_client::ProxyConfig;
 use clap::{AppSettings, ArgEnum, ArgGroup, CommandFactory, Parser, Subcommand, ValueHint};
 use clap_complete::{generate, Generator, Shell};
 use clap_complete_fig::Fig;
-use clap_verbosity_flag::{Verbosity, WarnLevel};
 use eyre::{eyre, ContextCompat, Report, WrapErr};
 use itertools::Itertools;
 use lazy_static::lazy_static;
-use log::LevelFilter;
 use nextclade::align::params::AlignPairwiseParamsOptional;
-use nextclade::io::fs::{basename_maybe, extension};
 use nextclade::utils::global_init::setup_logger;
 use nextclade::{getenv, make_error};
 use std::fmt::Debug;
@@ -24,7 +22,6 @@ const DATA_FULL_DOMAIN: &str = getenv!("DATA_FULL_DOMAIN");
 
 lazy_static! {
   static ref SHELLS: &'static [&'static str] = &["bash", "elvish", "fish", "fig", "powershell", "zsh"];
-  static ref VERBOSITIES: &'static [&'static str] = &["off", "error", "warn", "info", "debug", "trace"];
 }
 
 #[derive(Parser, Debug)]
@@ -43,17 +40,9 @@ pub struct NextcladeArgs {
   #[clap(subcommand)]
   pub command: NextcladeCommands,
 
-  /// Set verbosity level [default: warn]
-  #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "silent", possible_values(VERBOSITIES.iter()))]
-  pub verbosity: Option<LevelFilter>,
-
-  /// Disable all console output. Same as --verbosity=off
-  #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "verbosity")]
-  pub silent: bool,
-
   /// Make output more quiet or more verbose
   #[clap(flatten)]
-  pub verbose: Verbosity<WarnLevel>,
+  pub verbosity: Verbosity<WarnLevel>,
 }
 
 #[derive(Subcommand, Debug)]
@@ -733,17 +722,7 @@ pub fn nextclade_check_removed_args(run_args: &mut NextcladeRunArgs) -> Result<(
 pub fn nextclade_parse_cli_args() -> Result<NextcladeArgs, Report> {
   let mut args = NextcladeArgs::parse();
 
-  // --verbosity=<level> and --silent take priority over -v and -q
-  let filter_level = if args.silent {
-    LevelFilter::Off
-  } else {
-    match args.verbosity {
-      None => args.verbose.log_level_filter(),
-      Some(verbosity) => verbosity,
-    }
-  };
-
-  setup_logger(filter_level);
+  setup_logger(args.verbosity.get_filter_level());
 
   match &mut args.command {
     NextcladeCommands::Completions { shell } => {
diff --git a/packages_rs/nextclade-cli/src/cli/verbosity.rs b/packages_rs/nextclade-cli/src/cli/verbosity.rs
new file mode 100644
index 000000000..5483c85fb
--- /dev/null
+++ b/packages_rs/nextclade-cli/src/cli/verbosity.rs
@@ -0,0 +1,135 @@
+//! Inspired by clap-verbosity-flag:
+//! https://github.com/rust-cli/clap-verbosity-flag
+use clap::Args;
+use lazy_static::lazy_static;
+use log::{Level, LevelFilter};
+use std::fmt::{Display, Formatter, Result};
+use std::marker::PhantomData;
+
+lazy_static! {
+  static ref VERBOSITIES: &'static [&'static str] = &["off", "error", "warn", "info", "debug", "trace"];
+}
+
+#[derive(Args, Debug, Clone)]
+pub struct Verbosity<L: LogLevel = ErrorLevel> {
+  /// Set verbosity level of console output [default: warn]
+  #[clap(long, global = true, possible_values(VERBOSITIES.iter()))]
+  #[clap(conflicts_with = "quiet", conflicts_with = "verbose", conflicts_with = "silent")]
+  #[clap(display_order = 900)]
+  pub verbosity: Option<LevelFilter>,
+
+  /// Disable all console output. Same as `--verbosity=off`
+  #[clap(long, global = true)]
+  #[clap(conflicts_with = "quiet", conflicts_with = "verbose", conflicts_with = "verbosity")]
+  #[clap(display_order = 901)]
+  pub silent: bool,
+
+  /// Make console output more verbose. Add multiple occurrences to increase verbosity further.
+  #[clap(long, short = 'v', parse(from_occurrences), global = true)]
+  #[clap(conflicts_with = "quiet", conflicts_with = "verbosity", conflicts_with = "silent")]
+  #[clap(display_order = 902)]
+  verbose: i8,
+
+  /// Make console output more quiet. Add multiple occurrences to make output even more quiet.
+  #[clap(long, short = 'q', parse(from_occurrences), global = true)]
+  #[clap(conflicts_with = "verbose", conflicts_with = "verbosity")]
+  #[clap(display_order = 903)]
+  quiet: i8,
+
+  #[clap(skip)]
+  phantom: PhantomData<L>,
+}
+
+impl<L: LogLevel> Verbosity<L> {
+  pub fn get_filter_level(&self) -> LevelFilter {
+    // --verbosity=<level> and --silent take priority over -v and -q
+    if self.silent {
+      LevelFilter::Off
+    } else {
+      match self.verbosity {
+        Some(verbosity) => verbosity,
+        None => self.log_level_filter(),
+      }
+    }
+  }
+
+  /// Get the log level.
+  ///
+  /// `None` means all output is disabled.
+  pub fn log_level(&self) -> Option<Level> {
+    level_enum(self.verbosity())
+  }
+
+  /// Get the log level filter.
+  pub fn log_level_filter(&self) -> LevelFilter {
+    level_enum(self.verbosity()).map_or(LevelFilter::Off, |l| l.to_level_filter())
+  }
+
+  /// If the user requested complete silence (i.e. not just no-logging).
+  pub fn is_silent(&self) -> bool {
+    self.log_level().is_none()
+  }
+
+  fn verbosity(&self) -> i8 {
+    level_value(L::default()) - self.quiet + self.verbose
+  }
+}
+
+const fn level_value(level: Option<Level>) -> i8 {
+  match level {
+    None => -1,
+    Some(Level::Error) => 0,
+    Some(Level::Warn) => 1,
+    Some(Level::Info) => 2,
+    Some(Level::Debug) => 3,
+    Some(Level::Trace) => 4,
+  }
+}
+
+const fn level_enum(verbosity: i8) -> Option<Level> {
+  match verbosity {
+    i8::MIN..=-1 => None,
+    0 => Some(Level::Error),
+    1 => Some(Level::Warn),
+    2 => Some(Level::Info),
+    3 => Some(Level::Debug),
+    4..=i8::MAX => Some(Level::Trace),
+  }
+}
+
+impl<L: LogLevel> Display for Verbosity<L> {
+  fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+    write!(f, "{}", self.verbosity())
+  }
+}
+
+pub trait LogLevel {
+  fn default() -> Option<Level>;
+}
+
+#[derive(Copy, Clone, Debug, Default)]
+pub struct ErrorLevel;
+
+impl LogLevel for ErrorLevel {
+  fn default() -> Option<Level> {
+    Some(Level::Error)
+  }
+}
+
+#[derive(Copy, Clone, Debug, Default)]
+pub struct WarnLevel;
+
+impl LogLevel for WarnLevel {
+  fn default() -> Option<Level> {
+    Some(Level::Warn)
+  }
+}
+
+#[derive(Copy, Clone, Debug, Default)]
+pub struct InfoLevel;
+
+impl LogLevel for InfoLevel {
+  fn default() -> Option<Level> {
+    Some(Level::Info)
+  }
+}