Repository: sxyazi/yazi (language: rust)

Issue:
`ya pack -a` accepts only one value at a time
### `yazi --debug` output

```Shell
$ yazi --debug

Yazi
    Version: 0.4.2 (Arch Linux 2025-01-12)
    Debug  : false
    Triple : x86_64-unknown-linux-gnu (linux-x86_64)
    Rustc  : 1.84.0 (9fc6b431 2025-01-07)

Ya
    Version: 0.4.2 (Arch Linux 2025-01-12)

Emulator
    Brand.from_env      : Some(Ghostty)
    Emulator.detect     : Emulator { kind: Left(Ghostty), light: false, cell_size: Some((12, 26)) }
    Emulator.detect_full: Ok(Emulator { kind: Left(Ghostty), light: false, cell_size: Some((12, 26)) })

Adapter
    Adapter.matches: Kgp

Desktop
    XDG_SESSION_TYPE           : Some("x11")
    WAYLAND_DISPLAY            : None
    DISPLAY                    : Some(":0")
    SWAYSOCK                   : None
    HYPRLAND_INSTANCE_SIGNATURE: None
    WAYFIRE_SOCKET             : None

SSH
    shared.in_ssh_connection: false

WSL
    WSL: false

NVIM
    NVIM          : false
    Neovim version: 0.10.3

Variables
    SHELL           : Some("/usr/bin/fish")
    EDITOR          : Some("vim")
    VISUAL          : None
    YAZI_FILE_ONE   : None
    YAZI_CONFIG_HOME: None
    YAZI_ZOXIDE_OPTS: None
    FZF_DEFAULT_OPTS: Some("-e -m -0 --reverse --no-sort --preview \'fish -c \"fzf_previewer {}\"\' --preview-window=bottom:wrap --bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all")

Text Opener
    default     : Some(Opener { run: "${EDITOR:=vim} \"$@\"", block: true, orphan: false, desc: "$EDITOR", for_: None, spread: true })
    block-create: Some(Opener { run: "${EDITOR:=vim} \"$@\"", block: true, orphan: false, desc: "$EDITOR", for_: None, spread: true })
    block-rename: Some(Opener { run: "${EDITOR:=vim} \"$@\"", block: true, orphan: false, desc: "$EDITOR", for_: None, spread: true })

Multiplexers
    TMUX               : 2
    tmux version       : tmux 3.5a
    tmux build flags   : enable-sixel=Unknown
    ZELLIJ_SESSION_NAME: None
    Zellij version     : 0.41.2

Dependencies
    file          : 5.46
    ueberzugpp    : No such file or directory (os error 2)
    ffmpeg/ffprobe: 7.1 / 7.1
    pdftoppm      : 25.01.0
    magick        : 7.1.1-43
    fzf           : 0.57.0
    fd/fdfind     : 10.2.0 / No such file or directory (os error 2)
    rg            : 14.1.1
    chafa         : No such file or directory (os error 2)
    zoxide        : 0.9.6
    7z/7zz        : 24.09 / No such file or directory (os error 2)
    jq            : 1.7.1

Clipboard
    wl-copy/paste: No such file or directory (os error 2) / No such file or directory (os error 2)
    xclip        : 0.13
    xsel         : No such file or directory (os error 2)
```

### Please describe the problem you're trying to solve

For instance, I need to install multiple plugins at once, I cannot use command like `ya pack -a A B C D`, it seems `ya pack -a` accepts only one value at a time, all I need to do is using for-loop of my bash or fish shell to install all of them.

### Would you be willing to contribute this feature?

- [ ] Yes, I'll give it a shot

### Describe the solution you'd like

It would be great if `ya pack -a` can accept multiple values like `ya pack -a A B C D`

### Additional context

_No response_

### Checklist

- [x] I have searched the existing issues/discussions
- [x] The [latest nightly build](https://yazi-rs.github.io/docs/installation/#binaries) doesn't already have this feature


Discussion / hints:
Nice to have! Anyone want to give it a go?
I will try to do it

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5e6572be7..db707bc1f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -289,9 +289,9 @@ checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
 
 [[package]]
 name = "bumpalo"
-version = "3.16.0"
+version = "3.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
 
 [[package]]
 name = "bytemuck"
@@ -395,9 +395,9 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.42"
+version = "4.5.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33a7e468e750fa4b6be660e8b5651ad47372e8fb114030b594c2d75d48c5ffd0"
+checksum = "375f9d8255adeeedd51053574fd8d4ba875ea5fa558e86617b07f09f1680c8b6"
 dependencies = [
  "clap",
 ]
@@ -1385,9 +1385,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
 
 [[package]]
 name = "libfuzzer-sys"
-version = "0.4.8"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa"
+checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
 dependencies = [
  "arbitrary",
  "cc",
@@ -1450,6 +1450,15 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "lru"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
+dependencies = [
+ "hashbrown",
+]
+
 [[package]]
 name = "lua-src"
 version = "547.0.0"
@@ -1542,9 +1551,9 @@ dependencies = [
 
 [[package]]
 name = "mlua"
-version = "0.10.2"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ea43c3ffac2d0798bd7128815212dd78c98316b299b7a902dabef13dc7b6b8d"
+checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b"
 dependencies = [
  "anyhow",
  "bstr",
@@ -1562,9 +1571,9 @@ dependencies = [
 
 [[package]]
 name = "mlua-sys"
-version = "0.6.6"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771"
+checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594"
 dependencies = [
  "cc",
  "cfg-if",
@@ -2037,7 +2046,7 @@ dependencies = [
  "indoc",
  "instability",
  "itertools 0.13.0",
- "lru",
+ "lru 0.12.5",
  "paste",
  "strum",
  "unicode-segmentation",
@@ -2216,9 +2225,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
 
 [[package]]
 name = "ryu"
-version = "1.0.18"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
 
 [[package]]
 name = "same-file"
@@ -2273,9 +2282,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.137"
+version = "1.0.138"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
+checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
 dependencies = [
  "itoa",
  "memchr",
@@ -2834,9 +2843,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243"
+checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
 
 [[package]]
 name = "unicode-linebreak"
@@ -3285,9 +3294,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winnow"
-version = "0.6.24"
+version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
+checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310"
 dependencies = [
  "memchr",
 ]
@@ -3593,7 +3602,7 @@ dependencies = [
  "async-priority-channel",
  "futures",
  "libc",
- "lru",
+ "lru 0.13.0",
  "parking_lot",
  "scopeguard",
  "tokio",
diff --git a/Cargo.toml b/Cargo.toml
index 5191183bb..06525c6f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,16 +23,16 @@ futures             = "0.3.31"
 globset             = "0.4.15"
 indexmap            = { version = "2.7.1", features = [ "serde" ] }
 libc                = "0.2.169"
-lru                 = "0.12.5"
+lru                 = "0.13.0"
 md-5                = "0.10.6"
-mlua                = { version = "0.10.2", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serialize" ] }
+mlua                = { version = "0.10.3", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serialize" ] }
 objc                = "0.2.7"
 parking_lot         = "0.12.3"
 ratatui             = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
 regex               = "1.11.1"
 scopeguard          = "1.2.0"
 serde               = { version = "1.0.217", features = [ "derive" ] }
-serde_json          = "1.0.137"
+serde_json          = "1.0.138"
 shell-words         = "1.1.0"
 tokio               = { version = "1.43.0", features = [ "full" ] }
 tokio-stream        = "0.1.17"
diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml
index 54f8d675b..e66b4ecb7 100644
--- a/yazi-boot/Cargo.toml
+++ b/yazi-boot/Cargo.toml
@@ -22,7 +22,7 @@ serde = { workspace = true }
 
 [build-dependencies]
 clap                  = { workspace = true }
-clap_complete         = "4.5.42"
+clap_complete         = "4.5.44"
 clap_complete_fig     = "4.5.2"
 clap_complete_nushell = "4.5.5"
 vergen-gitcl          = { version = "1.0.5", features = [ "build", "rustc" ] }
diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml
index f92450e1e..4b8cd6dd9 100644
--- a/yazi-cli/Cargo.toml
+++ b/yazi-cli/Cargo.toml
@@ -31,7 +31,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.4.3" }
 # External build dependencies
 anyhow                = { workspace = true }
 clap                  = { workspace = true }
-clap_complete         = "4.5.42"
+clap_complete         = "4.5.44"
 clap_complete_fig     = "4.5.2"
 clap_complete_nushell = "4.5.5"
 serde_json            = { workspace = true }
diff --git a/yazi-cli/src/args.rs b/yazi-cli/src/args.rs
index c309e2014..e9bab4e9c 100644
--- a/yazi-cli/src/args.rs
+++ b/yazi-cli/src/args.rs
@@ -54,12 +54,12 @@ pub(super) struct CommandEmitTo {
 #[derive(clap::Args)]
 #[command(arg_required_else_help = true)]
 pub(super) struct CommandPack {
-	/// Add a package.
-	#[arg(short = 'a', long)]
-	pub(super) add:     Option<String>,
-	/// Delete a package.
-	#[arg(short = 'd', long)]
-	pub(super) delete:  Option<String>,
+	/// Add packages.
+	#[arg(short = 'a', long, num_args = 1..)]
+	pub(super) add:     Option<Vec<String>>,
+	/// Delete packages.
+	#[arg(short = 'd', long, num_args = 1..)]
+	pub(super) delete:  Option<Vec<String>>,
 	/// Install all packages.
 	#[arg(short = 'i', long)]
 	pub(super) install: bool,
diff --git a/yazi-cli/src/main.rs b/yazi-cli/src/main.rs
index e1470017a..ebb69f549 100644
--- a/yazi-cli/src/main.rs
+++ b/yazi-cli/src/main.rs
@@ -71,10 +71,10 @@ async fn run() -> anyhow::Result<()> {
 				package::Package::load().await?.print()?;
 			} else if cmd.upgrade {
 				package::Package::load().await?.install(true).await?;
-			} else if let Some(repo) = cmd.add {
-				package::Package::load().await?.add(&repo).await?;
-			} else if let Some(repo) = cmd.delete {
-				package::Package::load().await?.delete(&repo).await?;
+			} else if let Some(uses) = cmd.add {
+				package::Package::load().await?.add_many(&uses).await?;
+			} else if let Some(uses) = cmd.delete {
+				package::Package::load().await?.delete_many(&uses).await?;
 			}
 		}
 
diff --git a/yazi-cli/src/package/deploy.rs b/yazi-cli/src/package/deploy.rs
index 4e92dcfb0..403c1e891 100644
--- a/yazi-cli/src/package/deploy.rs
+++ b/yazi-cli/src/package/deploy.rs
@@ -1,8 +1,8 @@
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 
 use anyhow::{Context, Result, bail};
 use tokio::fs;
-use yazi_fs::{copy_and_seal, maybe_exists};
+use yazi_fs::{copy_and_seal, maybe_exists, remove_dir_clean};
 use yazi_macro::outln;
 
 use super::Dependency;
@@ -26,7 +26,22 @@ Please manually delete it from `{}` and re-run the command.",
 		}
 
 		fs::create_dir_all(&to).await?;
-		let files = if self.is_flavor {
+		if let Err(e) = Self::deploy_sources(&from, &to, self.is_flavor).await {
+			remove_dir_clean(&to).await;
+			return Err(e);
+		}
+
+		self.delete_assets().await?;
+		Self::deploy_assets(from.join("assets"), to.join("assets")).await?;
+
+		self.hash = self.hash().await?;
+		outln!("Done!")?;
+
+		Ok(())
+	}
+
+	async fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {
+		let files = if is_flavor {
 			&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
 		} else {
 			&["main.lua", "README.md", "LICENSE"][..]
@@ -48,13 +63,6 @@ Please manually delete it from `{}` and re-run the command.",
 				.await
 				.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;
 		}
-
-		self.delete_assets().await?;
-		Self::deploy_assets(from.join("assets"), to.join("assets")).await?;
-
-		self.hash = self.hash().await?;
-		outln!("Done!")?;
-
 		Ok(())
 	}
 
diff --git a/yazi-cli/src/package/package.rs b/yazi-cli/src/package/package.rs
index a23e1a988..6478b81ab 100644
--- a/yazi-cli/src/package/package.rs
+++ b/yazi-cli/src/package/package.rs
@@ -23,37 +23,18 @@ impl Package {
 		})
 	}
 
-	pub(crate) async fn add(&mut self, use_: &str) -> Result<()> {
-		let mut dep = Dependency::from_str(use_)?;
-		if let Some(d) = self.identical(&dep) {
-			bail!(
-				"{} `{}` already exists in package.toml",
-				if d.is_flavor { "Flavor" } else { "Plugin" },
-				dep.name
-			)
-		}
-
-		dep.add().await?;
-		if dep.is_flavor {
-			self.flavors.push(dep);
-		} else {
-			self.plugins.push(dep);
+	pub(crate) async fn add_many(&mut self, uses: &[String]) -> Result<()> {
+		for use_ in uses {
+			self.add(use_).await?;
 		}
 
 		let s = toml::to_string_pretty(self)?;
 		fs::write(Self::toml(), s).await.context("Failed to write package.toml")
 	}
 
-	pub(crate) async fn delete(&mut self, use_: &str) -> Result<()> {
-		let Some(dep) = self.identical(&Dependency::from_str(use_)?).cloned() else {
-			bail!("`{}` was not found in package.toml", use_)
-		};
-
-		dep.delete().await?;
-		if dep.is_flavor {
-			self.flavors.retain(|d| !d.identical(&dep));
-		} else {
-			self.plugins.retain(|d| !d.identical(&dep));
+	pub(crate) async fn delete_many(&mut self, uses: &[String]) -> Result<()> {
+		for use_ in uses {
+			self.delete(use_).await?;
 		}
 
 		let s = toml::to_string_pretty(self)?;
@@ -182,6 +163,39 @@ impl Package {
 		fs::write(Self::toml(), s).await.context("Failed to write package.toml")
 	}
 
+	async fn add(&mut self, use_: &str) -> Result<()> {
+		let mut dep = Dependency::from_str(use_)?;
+		if let Some(d) = self.identical(&dep) {
+			bail!(
+				"{} `{}` already exists in package.toml",
+				if d.is_flavor { "Flavor" } else { "Plugin" },
+				dep.name
+			)
+		}
+
+		dep.add().await?;
+		if dep.is_flavor {
+			self.flavors.push(dep);
+		} else {
+			self.plugins.push(dep);
+		}
+		Ok(())
+	}
+
+	async fn delete(&mut self, use_: &str) -> Result<()> {
+		let Some(dep) = self.identical(&Dependency::from_str(use_)?).cloned() else {
+			bail!("`{}` was not found in package.toml", use_)
+		};
+
+		dep.delete().await?;
+		if dep.is_flavor {
+			self.flavors.retain(|d| !d.identical(&dep));
+		} else {
+			self.plugins.retain(|d| !d.identical(&dep));
+		}
+		Ok(())
+	}
+
 	#[inline]
 	fn toml() -> PathBuf { Xdg::config_dir().join("package.toml") }
 


Test patch: