1use std::io::{self};
2use std::path::PathBuf;
3
4use clap::builder::styling;
5use clap::{
6 ColorChoice, CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint
7};
8use clap_complete::{Shell, generate};
9use colored::Colorize;
10use zoi_common::Runnable;
11
12use crate::pkg::lock;
13use crate::{cmd, utils};
14
15const BRANCH: &str = "Production";
18const STATUS: &str = "Release";
20const NUMBER: &str = "1.25.3";
22const PKG_SOURCE_HELP: &str =
24 "Package identifier (e.g. @repo/name, #git@repo/name, path, or URL)";
25
26#[derive(Parser)]
31#[command(name = "zoi", author, about, long_about = None, disable_version_flag = true,
32 trailing_var_arg = true,
33 color = ColorChoice::Auto,
34 arg_required_else_help = true,
35)]
36pub struct Cli {
37 #[command(subcommand)]
39 command: Option<Commands>,
40
41 #[arg(
43 short = 'v',
44 long = "version",
45 help = "Print detailed version information"
46 )]
47 version_flag: bool,
48
49 #[arg(
51 short = 'y',
52 long,
53 help = "Automatically answer yes to all prompts",
54 global = true
55 )]
56 yes: bool,
57
58 #[arg(
60 long = "root",
61 help = "Operate on a different root directory",
62 global = true,
63 value_hint = ValueHint::DirPath
64 )]
65 pub root: Option<std::path::PathBuf>,
66
67 #[arg(
69 long = "offline",
70 help = "Do not attempt to connect to the network",
71 global = true
72 )]
73 pub offline: bool,
74
75 #[arg(
77 long = "pkg-dir",
78 help = "Additional directory to search for .zpa archives",
79 global = true,
80 value_hint = ValueHint::DirPath
81 )]
82 pub pkg_dirs: Vec<std::path::PathBuf>
84}
85
86#[derive(clap::ValueEnum, Clone, Debug, Copy, PartialEq, Eq)]
88pub enum SetupScope {
89 User,
91 System
93}
94
95#[derive(clap::ValueEnum, Clone, Debug, Copy)]
97pub enum InstallScope {
98 User,
100 System,
102 Project
104}
105
106#[derive(Subcommand)]
108enum Commands {
109 #[command(hide = true)]
111 GenerateCompletions {
112 #[arg(value_enum)]
114 shell: Shell
115 },
116
117 #[command(hide = true)]
119 Complete {
120 #[arg(value_enum)]
122 shell: Shell,
123 index: usize,
125 words: Vec<String>
127 },
128
129 #[command(hide = true)]
131 GenerateManual,
132
133 #[command(
135 alias = "v",
136 long_about = "Displays the version number, build status, branch, and \
137 commit hash. This is the same output provided by the -v \
138 and --version flags."
139 )]
140 Version,
141
142 #[command(long_about = "Displays the full application name, description, \
144 author, license, and homepage information.")]
145 About,
146
147 #[command(long_about = "Detects and displays key system details, \
149 including the OS, CPU architecture, Linux \
150 distribution (if applicable), and available \
151 package managers.")]
152 Info,
153
154 #[command(
156 alias = "dl",
157 long_about = "Downloads the binary archive (.zpa) or source bundle \
158 (.zsa) for a package to the local cache or a specified \
159 directory."
160 )]
161 Download {
162 #[arg(value_name = "PACKAGE", required = true, help = PKG_SOURCE_HELP)]
164 package: String,
165
166 #[arg(long, group = "type")]
168 archive: bool,
169
170 #[arg(long, group = "type")]
172 source: bool,
173
174 #[arg(short, long)]
176 output_dir: Option<PathBuf>
177 },
178
179 #[command(
181 alias = "sy",
182 long_about = "Clones the official package database from GitLab to \
183 your local machine (~/.zoi/pkgs/db). If the database \
184 already exists, it verifies the remote URL and pulls \
185 the latest changes."
186 )]
187 Sync {
188 #[command(subcommand)]
190 command: Option<SyncCommands>,
191
192 #[arg(short, long)]
194 verbose: bool,
195
196 #[arg(long)]
198 fallback: bool,
199
200 #[arg(long = "no-pm")]
202 no_package_managers: bool,
203
204 #[arg(long)]
207 force: bool,
208
209 #[arg(long)]
212 local: bool,
213
214 #[arg(long)]
217 frozen: bool,
218
219 #[arg(long, value_enum, conflicts_with = "local")]
221 scope: Option<SetupScope>
222 },
223
224 Migrate(cmd::migrate::MigrateCommand),
226
227 #[command(alias = "ls")]
229 List {
230 #[arg(short, long)]
232 all: bool,
233 #[arg(short, long)]
235 outdated: bool,
236 #[arg(long)]
238 registry: Option<String>,
239 #[arg(long)]
241 repo: Option<String>,
242 #[arg(short = 't', long = "type")]
244 package_type: Option<String>,
245 #[arg(short = 'm', long)]
247 foreign: bool,
248 #[arg(long, hide = true)]
250 names: bool,
251 #[arg(long, hide = true)]
253 completion: bool
254 },
255
256 Show {
258 #[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
260 package_name: String,
261 #[arg(long)]
263 raw: bool,
264 #[arg(long)]
266 purl: bool
267 },
268
269 Pin {
271 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
273 package: String,
274 version: String
276 },
277
278 Provides {
280 term: String
282 },
283
284 Tree {
286 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
288 packages: Vec<String>
289 },
290
291 Unpin {
293 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
295 package: String
296 },
297
298 #[command(
300 alias = "m",
301 long_about = "Changes whether a package is considered explicitly installed or a dependency. Explicit packages are not removed by 'autoremove', while dependencies are if no other package requires them.",
302 group(clap::ArgGroup::new("mode").required(true).args(["as_dependency", "as_explicit"]))
303 )]
304 Mark {
305 #[arg(value_name = "INST_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
307 packages: Vec<String>,
308
309 #[arg(long, aliases = ["asdeps"])]
311 as_dependency: bool,
312
313 #[arg(long, aliases = ["asexpl"], conflicts_with = "as_dependency")]
315 as_explicit: bool
316 },
317
318 #[command(
320 alias = "up",
321 arg_required_else_help = true,
322 group(clap::ArgGroup::new("target").required(true).args(["package_names", "all"]))
323 )]
324 Update {
325 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
327 package_names: Vec<String>,
328
329 #[arg(long, conflicts_with = "package_names")]
331 all: bool,
332
333 #[arg(long)]
335 dry_run: bool,
336 #[arg(long)]
338 explain: bool,
339 #[arg(long)]
341 plan_json: bool,
342 #[arg(long, requires = "all")]
345 interactive: bool
346 },
347
348 #[command(aliases = ["i", "in", "add"])]
351 Install(cmd::install::args::InstallArgs),
352
353 #[command(alias = "u")]
355 Use {
356 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
358 packages: Vec<String>,
359
360 #[arg(short, long)]
362 global: bool
363 },
364
365 #[command(
367 aliases = ["un", "rm", "remove"],
368 long_about = "Removes one or more packages' files from the Zoi store and deletes their symlinks from the bin directory. This command will fail if a package was not installed by Zoi."
369 )]
370 Uninstall(cmd::uninstall::args::UninstallArgs),
371
372 #[command(long_about = "Execute a command from zoi.yaml. If no command \
374 is specified, it will launch an interactive \
375 prompt to choose one.")]
376 Run {
377 cmd_alias: Option<String>,
379 args: Vec<String>
381 },
382
383 #[command(long_about = "Checks for required packages and runs setup \
385 commands for a defined environment. If no \
386 environment is specified, it launches an \
387 interactive prompt.")]
388 Env {
389 env_alias: Option<String>,
391
392 #[arg(long, value_enum, hide = true)]
394 export_shell: Option<Shell>
395 },
396
397 #[command(
399 alias = "develop",
400 long_about = "Loads the project configuration from zoi.yaml, ensures \
401 all required packages are installed locally, sets up \
402 environment variables (PATH, LD_LIBRARY_PATH, etc.), \
403 and drops you into a subshell."
404 )]
405 Dev {
406 #[arg(short, long)]
408 run: Option<String>,
409 #[arg(long)]
411 repo: Option<String>
412 },
413
414 #[command(
416 alias = "ug",
417 long_about = "Upgrades Zoi to the latest version. By default, it \
418 attempts a delta upgrade (bsdiff) to minimize download \
419 size. If the delta upgrade is unavailable or fails, it \
420 automatically falls back to a full download."
421 )]
422 Upgrade {
423 #[arg(long)]
425 force: bool,
426
427 #[arg(long)]
429 tag: Option<String>,
430
431 #[arg(long)]
433 branch: Option<String>
434 },
435
436 Autoremove {
439 #[arg(long)]
441 dry_run: bool
442 },
443
444 Why {
446 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
448 package_name: String
449 },
450
451 #[command(alias = "owns")]
453 Owner {
454 #[arg(value_hint = ValueHint::FilePath)]
456 path: std::path::PathBuf
457 },
458
459 Files {
461 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
463 package: String
464 },
465
466 History {
468 #[arg(long, conflicts_with = "export")]
471 verify: bool,
472 #[arg(long, value_hint = ValueHint::FilePath, conflicts_with = "verify")]
475 export: Option<std::path::PathBuf>,
476 #[arg(long, requires = "export")]
478 ndjson: bool
479 },
480
481 #[command(
483 alias = "s",
484 long_about = "Searches for a case-insensitive term in the name, \
485 description, and tags of all available packages in the \
486 database. Filter by repo, type, or tags."
487 )]
488 Search {
489 search_term: String,
491 #[arg(long)]
493 registry: Option<String>,
494 #[arg(long)]
496 repo: Option<String>,
497 #[arg(long = "type")]
499 package_type: Option<String>,
500 #[arg(short = 't', long = "tag", value_delimiter = ',', num_args = 1..)]
502 tags: Option<Vec<String>>,
503 #[arg(long, default_value = "name")]
505 sort: String,
506 #[arg(short, long)]
508 files: bool,
509 #[arg(short = 'i', long)]
511 interactive: bool
512 },
513
514 #[command(alias = "svc")]
516 Service(cmd::service::ServiceCommand),
517
518 #[command(
521 long_about = "If a shell is provided, it installs completion scripts. If 'hook' is provided, it outputs shell-specific hook scripts for auto-activation. If packages are provided via --package/-p, it enters a temporary subshell with those packages available in PATH.",
522 arg_required_else_help = true,
523 group(clap::ArgGroup::new("shell_action").required(true).args(["shell", "hook", "packages"]).multiple(true))
524 )]
525 Shell {
526 #[arg(value_enum)]
528 shell: Option<Shell>,
529 #[arg(long)]
531 hook: bool,
532 #[arg(long, value_enum, default_value = "user")]
534 scope: SetupScope,
535 #[arg(short, long = "package", value_name = "ALL_PACKAGES")]
537 packages: Vec<String>,
538 #[arg(short, long)]
541 run: Option<String>,
542 #[arg(long, short)]
544 verbose: bool
545 },
546
547 #[command(
549 alias = "x",
550 long_about = "Resolves a package and its dependencies, installs them \
551 if needed, then runs the requested binary directly. By \
552 default runs the first binary the package provides. \
553 Uses bwrap for sandboxed packages."
554 )]
555 Exec {
556 #[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
558 source: String,
559
560 #[arg(long)]
563 bin: Option<String>,
564
565 #[arg(long, short)]
567 verbose: bool,
568
569 #[arg(value_name = "ARGS")]
571 args: Vec<String>
572 },
573
574 Clean {
576 #[arg(long)]
578 dry_run: bool
579 },
580
581 Clone {
583 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
585 package: String,
586 #[arg(value_name = "LOCATION")]
588 location: Option<String>
589 },
590
591 Cache {
593 #[command(subcommand)]
595 command: CacheCommands
596 },
597
598 #[command(alias = "tx")]
600 Transaction {
601 #[command(subcommand)]
603 command: TransactionCommands
604 },
605
606 #[command(alias = "reg")]
608 Registry(cmd::registry::RegistryCommand),
609
610 Home(cmd::home::HomeCommand),
612
613 System(cmd::system::SystemCommand),
615
616 #[command(
618 aliases = ["repositories"],
619 long_about = "Manages the list of package repositories used by Zoi.\n\nCommands:\n- add (alias: a): Add an official repo by name or clone from a git URL.\n- remove|rm: Remove a repo from active list (repo rm <name>).\n- list|ls: Show active repositories by default; use 'list all' to show all available repositories.\n- git: Manage cloned git repositories (git ls, git rm <repo-name>)."
620 )]
621 Repo(cmd::repo::RepoCommand),
622
623 #[command(long_about = "Manage opt-in anonymous telemetry used to \
625 understand package popularity. Default is \
626 disabled.")]
627 Telemetry {
628 #[arg(value_enum)]
630 action: TelemetryAction
631 },
632
633 Create {
635 #[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
637 source: String,
638 app_name: Option<String>
640 },
641
642 #[command(
644 alias = "dg",
645 long_about = "Interactively choose and install an older version of a \
646 package from the local store or archive cache. This is \
647 useful if a recent update has introduced bugs or \
648 compatibility issues."
649 )]
650 Downgrade {
651 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
653 package: String
654 },
655
656 #[command(alias = "ext")]
658 Extension(ExtensionCommand),
659
660 Rollback {
662 #[arg(value_name = "INST_PACKAGES", required_unless_present = "last_transaction", help = PKG_SOURCE_HELP)]
664 package: Option<String>,
665
666 #[arg(long, conflicts_with = "package")]
668 last_transaction: bool
669 },
670
671 Man {
673 #[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
675 package_name: String,
676 #[arg(long)]
678 upstream: bool,
679 #[arg(long)]
681 raw: bool,
682 #[arg(long)]
684 no_tui: bool
685 },
686
687 #[command(alias = "pkg")]
689 Package(cmd::package::PackageCommand),
690
691 Pgp(cmd::pgp::PgpCommand),
693
694 Helper(cmd::helper::HelperCommand),
696
697 Doctor,
699
700 Audit {
702 #[arg(short, long)]
705 all: bool,
706 #[arg(long)]
708 registry: Option<String>,
709 #[arg(long)]
711 repo: Option<String>
712 },
713
714 #[command(external_subcommand)]
716 External(Vec<String>)
717}
718
719#[derive(clap::Parser, Debug)]
721pub struct ExtensionCommand {
722 #[command(subcommand)]
724 pub command: ExtensionCommands
725}
726
727#[derive(clap::Subcommand, Debug)]
729pub enum ExtensionCommands {
730 Add {
732 #[arg(required = true)]
734 name: String
735 },
736 Remove {
738 #[arg(required = true)]
740 name: String
741 }
742}
743
744#[derive(clap::Subcommand, Clone)]
746pub enum SyncCommands {
747 Add {
749 url: String
751 },
752 Remove {
754 handle: String
756 },
757 #[command(alias = "ls")]
759 List,
760 Set {
762 url: String
764 }
765}
766
767#[derive(clap::Subcommand)]
769pub enum CacheCommands {
770 Add {
772 #[arg(required = true)]
774 files: Vec<std::path::PathBuf>
775 },
776 #[command(alias = "clean")]
778 Clear {
779 #[arg(long)]
781 dry_run: bool
782 },
783 #[command(alias = "ls")]
785 List,
786 Mirror {
788 #[command(subcommand)]
790 command: CacheMirrorCommands
791 }
792}
793
794#[derive(clap::Subcommand)]
796pub enum CacheMirrorCommands {
797 Add {
799 url: String
801 },
802 Remove {
804 url: String
806 },
807 #[command(alias = "ls")]
809 List
810}
811
812#[derive(clap::Subcommand)]
814pub enum TransactionCommands {
815 #[command(alias = "ls")]
817 List,
818 Show {
820 id: String
822 },
823 Files {
825 id: String
827 }
828}
829
830#[derive(clap::ValueEnum, Clone)]
832enum TelemetryAction {
833 Status,
835 Enable,
837 Disable
839}
840
841pub fn run() -> anyhow::Result<()> {
848 let styles = styling::Styles::styled()
849 .header(
850 styling::AnsiColor::Yellow.on_default() | styling::Effects::BOLD
851 )
852 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
853 .literal(styling::AnsiColor::Green.on_default())
854 .placeholder(styling::AnsiColor::Cyan.on_default());
855
856 let commit: &str = option_env!("ZOI_COMMIT_HASH").unwrap_or("dev");
857 let cmd = Cli::command().styles(styles.clone());
858 let matches = cmd.clone().get_matches();
859 let cli = match Cli::from_arg_matches(&matches) {
860 Ok(cli) => cli,
861 Err(err) => {
862 err.print()?;
863 return Err(anyhow::anyhow!("Failed to parse arguments"));
864 }
865 };
866
867 if let Some(root) = cli.root {
868 crate::pkg::sysroot::set_sysroot(root);
869 }
870
871 let config = crate::pkg::config::read_config().unwrap_or_default();
872
873 let is_offline = cli.offline || config.offline_mode;
874 crate::pkg::offline::set_offline(is_offline);
875
876 let mut all_pkg_dirs = cli.pkg_dirs;
877 for dir in config.pkg_dirs {
878 let path = std::path::PathBuf::from(dir);
879 if !all_pkg_dirs.contains(&path) {
880 all_pkg_dirs.push(path);
881 }
882 }
883 crate::pkg::pkgdir::set_pkg_dirs(all_pkg_dirs);
884
885 utils::check_path();
886
887 if let Err(e) = crate::pkg::pgp::ensure_builtin_keys() {
888 eprintln!(
889 "{}: Failed to ensure builtin PGP keys: {}",
890 "Warning".yellow(),
891 e
892 );
893 }
894
895 let plugin_manager = crate::pkg::plugin::PluginManager::new()?;
896 if let Err(e) = plugin_manager.load_all(cli.yes) {
897 eprintln!("{}: Failed to load plugins: {}", "Warning".yellow(), e);
898 }
899
900 if cli.version_flag {
901 cmd::version::run(BRANCH, STATUS, NUMBER, commit);
902 return Ok(());
903 }
904
905 if let Some(command) = cli.command {
906 let needs_lock = matches!(
907 command,
908 Commands::Install { .. }
909 | Commands::Uninstall { .. }
910 | Commands::Update { .. }
911 | Commands::Autoremove { .. }
912 | Commands::Rollback { .. }
913 | Commands::Package(_)
914 );
915
916 let _lock_guard = if needs_lock {
917 Some(lock::acquire_lock()?)
918 } else {
919 None
920 };
921
922 let result = match command {
923 Commands::GenerateCompletions { shell } => {
924 let mut cmd = Cli::command();
925 let bin_name = cmd.get_name().to_string();
926 generate(shell, &mut cmd, bin_name, &mut io::stdout());
927 Ok(())
928 }
929 Commands::Complete {
930 shell,
931 index,
932 words
933 } => cmd::complete::run(shell, index, &words),
934 Commands::GenerateManual => cmd::gen_man::run().map_err(Into::into),
935 Commands::Version => {
936 cmd::version::run(BRANCH, STATUS, NUMBER, commit);
937 Ok(())
938 }
939 Commands::About => {
940 cmd::about::run(BRANCH, STATUS, NUMBER, commit);
941 Ok(())
942 }
943 Commands::Info => cmd::info::run(BRANCH, STATUS, NUMBER, commit),
944 Commands::Sync {
945 command,
946 verbose,
947 fallback,
948 no_package_managers,
949 force,
950 local,
951 frozen,
952 scope
953 } => {
954 if let Some(cmd) = command {
955 match cmd {
956 SyncCommands::Add { url } => {
957 cmd::sync::add_registry(&url)
958 }
959 SyncCommands::Remove { handle } => {
960 cmd::sync::remove_registry(&handle)
961 }
962 SyncCommands::List => cmd::sync::list_registries(),
963 SyncCommands::Set { url } => {
964 cmd::sync::set_registry(&url)
965 }
966 }
967 } else if local {
968 plugin_manager.trigger_hook("on_pre_sync", None)?;
969 let res =
970 cmd::sync::run_local(verbose, fallback, force, frozen);
971 plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
972 res
973 } else {
974 plugin_manager.trigger_hook("on_pre_sync", None)?;
975 let res = cmd::sync::run(
976 verbose,
977 fallback,
978 no_package_managers,
979 force,
980 scope
981 );
982 plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
983 res
984 }
985 }
986 Commands::Migrate(args) => cmd::migrate::run(args),
987 Commands::List {
988 all,
989 outdated,
990 registry,
991 repo,
992 package_type,
993 foreign,
994 names,
995 completion
996 } => cmd::list::run(
997 all,
998 outdated,
999 registry.as_deref(),
1000 repo.as_deref(),
1001 package_type.as_deref(),
1002 foreign,
1003 names,
1004 completion
1005 ),
1006 Commands::Show {
1007 package_name,
1008 raw,
1009 purl
1010 } => cmd::show::run(&package_name, raw, purl),
1011 Commands::Pin { package, version } => {
1012 cmd::pin::run(&package, &version)
1013 }
1014 Commands::Provides { term } => cmd::provides::run(&term),
1015 Commands::Tree { packages } => cmd::tree::run(&packages),
1016 Commands::Unpin { package } => cmd::unpin::run(&package),
1017 Commands::Mark {
1018 packages,
1019 as_dependency,
1020 as_explicit
1021 } => cmd::mark::run(&packages, as_dependency, as_explicit),
1022 Commands::Update {
1023 package_names,
1024 all,
1025 dry_run,
1026 explain,
1027 plan_json,
1028 interactive
1029 } => cmd::update::run(
1030 all,
1031 &package_names,
1032 cli.yes,
1033 dry_run,
1034 explain,
1035 plan_json,
1036 interactive
1037 )
1038 .map_err(|e| cmd::ux::with_failure_hint("update", e)),
1039 Commands::Install(args) => args
1040 .run(cli.yes)
1041 .map_err(|e| cmd::ux::with_failure_hint("install", e)),
1042 Commands::Use { packages, global } => {
1043 cmd::use_cmd::run(&packages, global)
1044 }
1045 Commands::Uninstall(args) => args
1046 .run(cli.yes)
1047 .map_err(|e| cmd::ux::with_failure_hint("uninstall", e)),
1048 Commands::Run { cmd_alias, args } => {
1049 cmd::run::run(cmd_alias.as_deref(), &args)
1050 }
1051 Commands::Env {
1052 env_alias,
1053 export_shell
1054 } => cmd::env::run(env_alias.as_deref(), export_shell),
1055 Commands::Dev { run, repo } => cmd::dev::run(run, repo),
1056 Commands::Upgrade { force, tag, branch } => {
1057 match cmd::upgrade::run(
1058 BRANCH, STATUS, NUMBER, force, tag, branch
1059 ) {
1060 Ok(()) => {
1061 println!(
1062 "\n{}",
1063 "Zoi upgraded successfully! Please restart your \
1064 shell for changes to take effect."
1065 .green()
1066 );
1067 println!(
1068 "\n{}: https://github.com/zillowe/zoi/blob/main/CHANGELOG.md",
1069 "Changelog".cyan().bold()
1070 );
1071 println!(
1072 "\n{}: To update shell completions, run 'zoi \
1073 shell <your-shell>'.",
1074 "Hint".cyan().bold()
1075 );
1076 }
1077 Err(e) if e.to_string() == "already_on_latest" => {}
1078 Err(e) if e.to_string() == "managed_by_package_manager" => {
1079 }
1080 Err(e) => return Err(e)
1081 }
1082 Ok(())
1083 }
1084 Commands::Autoremove { dry_run } => {
1085 cmd::autoremove::run(cli.yes, dry_run)
1086 }
1087 Commands::Why { package_name } => cmd::why::run(&package_name),
1088 Commands::Owner { path } => cmd::owner::run(&path),
1089 Commands::Files { package } => cmd::files::run(&package),
1090 Commands::History {
1091 verify,
1092 export,
1093 ndjson
1094 } => cmd::history::run(verify, export, ndjson),
1095 Commands::Search {
1096 search_term,
1097 registry,
1098 repo,
1099 package_type,
1100 tags,
1101 sort,
1102 files,
1103 interactive
1104 } => cmd::search::run(
1105 &search_term,
1106 registry.as_deref(),
1107 repo.as_deref(),
1108 package_type.as_deref(),
1109 tags,
1110 &sort,
1111 files,
1112 interactive
1113 ),
1114 Commands::Service(args) => cmd::service::run(args),
1115 Commands::Shell {
1116 shell,
1117 hook,
1118 scope,
1119 packages,
1120 run,
1121 verbose
1122 } => {
1123 let target_shell = shell
1124 .or_else(crate::pkg::utils::get_current_shell)
1125 .unwrap_or(Shell::Bash);
1126 if hook {
1127 cmd::shell::print_hook(target_shell)
1128 } else if !packages.is_empty() {
1129 cmd::shell::enter_ephemeral_shell(
1130 &packages,
1131 run,
1132 verbose,
1133 Some(&plugin_manager)
1134 )
1135 } else {
1136 cmd::shell::run(target_shell, scope)
1137 }
1138 }
1139 Commands::Exec {
1140 source,
1141 bin,
1142 verbose,
1143 args
1144 } => cmd::exec::run(&source, bin, &args, verbose),
1145 Commands::Download {
1146 package,
1147 archive: _,
1148 source,
1149 output_dir
1150 } => {
1151 let download_type = if source {
1152 cmd::download::DownloadType::Source
1153 } else {
1154 cmd::download::DownloadType::Archive
1155 };
1156 cmd::download::run(&package, download_type, output_dir)
1157 }
1158 Commands::Clean { dry_run } => cmd::clean::run(dry_run),
1159 Commands::Clone { package, location } => {
1160 cmd::clone::run(&package, location, cli.yes)
1161 }
1162 Commands::Cache { command } => match command {
1163 CacheCommands::Add { files } => cmd::cache::add(&files),
1164 CacheCommands::Clear { dry_run } => cmd::cache::clear(dry_run),
1165 CacheCommands::List => cmd::cache::list(),
1166 CacheCommands::Mirror { command } => match command {
1167 CacheMirrorCommands::Add { url } => {
1168 cmd::cache::add_mirror(&url)
1169 }
1170 CacheMirrorCommands::Remove { url } => {
1171 cmd::cache::remove_mirror(&url)
1172 }
1173 CacheMirrorCommands::List => cmd::cache::list_mirrors()
1174 }
1175 },
1176 Commands::Transaction { command } => match command {
1177 TransactionCommands::List => cmd::transaction::list(),
1178 TransactionCommands::Show { id } => cmd::transaction::show(&id),
1179 TransactionCommands::Files { id } => {
1180 cmd::transaction::files(&id)
1181 }
1182 },
1183 Commands::Repo(args) => cmd::repo::run(args),
1184 Commands::Registry(args) => cmd::registry::run(args),
1185 Commands::Home(args) => cmd::home::run(args),
1186 Commands::System(args) => cmd::system::run(args, cli.yes),
1187 Commands::Telemetry { action } => {
1188 use cmd::telemetry::{TelemetryCommand, run};
1189 let cmd = match action {
1190 TelemetryAction::Status => TelemetryCommand::Status,
1191 TelemetryAction::Enable => TelemetryCommand::Enable,
1192 TelemetryAction::Disable => TelemetryCommand::Disable
1193 };
1194 run(cmd)
1195 }
1196 Commands::Create { source, app_name } => cmd::create::run(
1197 cmd::create::CreateCommand { source, app_name },
1198 cli.yes,
1199 Some(&plugin_manager)
1200 ),
1201 Commands::Downgrade { package } => {
1202 cmd::downgrade::run(&package, cli.yes, Some(&plugin_manager))
1203 }
1204 Commands::Extension(args) => {
1205 cmd::extension::run(args, cli.yes, Some(&plugin_manager))
1206 }
1207 Commands::Rollback {
1208 package,
1209 last_transaction
1210 } => {
1211 if last_transaction {
1212 cmd::rollback::run_transaction_rollback(
1213 cli.yes,
1214 Some(&plugin_manager)
1215 )
1216 } else if let Some(pkg) = package {
1217 cmd::rollback::run(&pkg, cli.yes, Some(&plugin_manager))
1218 } else {
1219 Ok(())
1220 }
1221 }
1222 Commands::Man {
1223 package_name,
1224 upstream,
1225 raw,
1226 no_tui
1227 } => cmd::man::run(&package_name, upstream, raw, no_tui),
1228 Commands::Package(args) => cmd::package::run(args),
1229 Commands::Pgp(args) => cmd::pgp::run(args),
1230 Commands::Helper(args) => cmd::helper::run(args),
1231 Commands::Doctor => cmd::doctor::run(),
1232 Commands::Audit {
1233 all,
1234 registry,
1235 repo
1236 } => cmd::audit::run(all, registry, repo.as_deref()),
1237 Commands::External(args) => {
1238 let (cmd_name, cmd_args) =
1239 if let Some((first, rest)) = args.split_first() {
1240 (first, rest.to_vec())
1241 } else {
1242 return Err(anyhow::anyhow!("No command specified"));
1243 };
1244
1245 match plugin_manager.run_command(cmd_name, cmd_args) {
1246 Ok(true) => Ok(()),
1247 Ok(false) => {
1248 let mut shadow_cmd = Cli::command().styles(styles);
1249 shadow_cmd =
1250 shadow_cmd.allow_external_subcommands(false);
1251
1252 let err = shadow_cmd
1253 .clone()
1254 .try_get_matches_from(std::env::args())
1255 .err()
1256 .unwrap_or_else(|| {
1257 shadow_cmd.error(
1258 clap::error::ErrorKind::InvalidSubcommand,
1259 format!(
1260 "unrecognized subcommand '{cmd_name}'"
1261 )
1262 )
1263 });
1264
1265 let plugin_cmds = plugin_manager.list_commands()?;
1266 if !plugin_cmds.is_empty() {
1267 eprintln!(
1268 "{}:",
1269 "Available Plugin Commands".cyan().bold()
1270 );
1271 for (pcmd, pdesc) in plugin_cmds {
1272 if pdesc.is_empty() {
1273 eprintln!(" {pcmd}");
1274 } else {
1275 eprintln!(
1276 " {:<12} {}",
1277 pcmd,
1278 pdesc.dimmed()
1279 );
1280 }
1281 }
1282 eprintln!();
1283 }
1284
1285 err.exit();
1286 }
1287 Err(e) => Err(e)
1288 }
1289 }
1290 };
1291
1292 if let Err(e) = result {
1293 eprintln!("Error: {e}");
1294 std::process::exit(1);
1295 }
1296 }
1297 Ok(())
1298}