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.26.1";
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 Zoi's user data directory. If the database already \
184 exists, it verifies the remote URL and pulls the latest \
185 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, requires = "dry_run")]
341 plan_json: bool,
342 #[arg(long, short)]
344 verbose: bool,
345 #[arg(long, requires = "all")]
348 interactive: bool
349 },
350
351 #[command(aliases = ["i", "in", "add"])]
354 Install(cmd::install::args::InstallArgs),
355
356 #[command(alias = "u")]
358 Use {
359 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
361 packages: Vec<String>,
362
363 #[arg(short, long)]
365 global: bool
366 },
367
368 #[command(
370 aliases = ["un", "rm", "remove"],
371 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."
372 )]
373 Uninstall(cmd::uninstall::args::UninstallArgs),
374
375 #[command(long_about = "Execute a command from zoi.yaml. If no command \
377 is specified, it will launch an interactive \
378 prompt to choose one.")]
379 Run {
380 cmd_alias: Option<String>,
382 args: Vec<String>
384 },
385
386 #[command(long_about = "Checks for required packages and runs setup \
388 commands for a defined environment. If no \
389 environment is specified, it launches an \
390 interactive prompt.")]
391 Env {
392 env_alias: Option<String>,
394
395 #[arg(long, value_enum, hide = true)]
397 export_shell: Option<Shell>
398 },
399
400 #[command(
402 alias = "develop",
403 long_about = "Loads the project configuration from zoi.yaml, ensures \
404 all required packages are installed locally, sets up \
405 environment variables (PATH, LD_LIBRARY_PATH, etc.), \
406 and drops you into a subshell."
407 )]
408 Dev {
409 #[arg(short, long)]
411 run: Option<String>,
412 #[arg(long)]
414 repo: Option<String>
415 },
416
417 #[command(
419 alias = "ug",
420 long_about = "Upgrades Zoi to the latest version. By default, it \
421 attempts a delta upgrade (bsdiff) to minimize download \
422 size. If the delta upgrade is unavailable or fails, it \
423 automatically falls back to a full download."
424 )]
425 Upgrade {
426 #[arg(long)]
428 force: bool,
429
430 #[arg(long)]
432 tag: Option<String>,
433
434 #[arg(long)]
436 branch: Option<String>
437 },
438
439 Autoremove {
442 #[arg(long)]
444 dry_run: bool
445 },
446
447 Why {
449 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
451 package_name: String
452 },
453
454 #[command(alias = "owns")]
456 Owner {
457 #[arg(value_hint = ValueHint::FilePath)]
459 path: std::path::PathBuf
460 },
461
462 Files {
464 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
466 package: String
467 },
468
469 History {
471 #[arg(long, conflicts_with = "export")]
474 verify: bool,
475 #[arg(long, value_hint = ValueHint::FilePath, conflicts_with = "verify")]
478 export: Option<std::path::PathBuf>,
479 #[arg(long, requires = "export")]
481 ndjson: bool
482 },
483
484 #[command(
486 alias = "s",
487 long_about = "Searches for a case-insensitive term in the name, \
488 description, and tags of all available packages in the \
489 database. Filter by repo, type, or tags."
490 )]
491 Search {
492 search_term: String,
494 #[arg(long)]
496 registry: Option<String>,
497 #[arg(long)]
499 repo: Option<String>,
500 #[arg(long = "type")]
502 package_type: Option<String>,
503 #[arg(short = 't', long = "tag", value_delimiter = ',', num_args = 1..)]
505 tags: Option<Vec<String>>,
506 #[arg(long, default_value = "name")]
508 sort: String,
509 #[arg(short, long)]
511 files: bool,
512 #[arg(short = 'i', long)]
514 interactive: bool
515 },
516
517 #[command(alias = "svc")]
519 Service(cmd::service::ServiceCommand),
520
521 #[command(
524 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.",
525 arg_required_else_help = true,
526 group(clap::ArgGroup::new("shell_action").required(true).args(["shell", "hook", "packages"]).multiple(true))
527 )]
528 Shell {
529 #[arg(value_enum)]
531 shell: Option<Shell>,
532 #[arg(long)]
534 hook: bool,
535 #[arg(long, value_enum, default_value = "user")]
537 scope: SetupScope,
538 #[arg(short, long = "package", value_name = "ALL_PACKAGES")]
540 packages: Vec<String>,
541 #[arg(short, long)]
544 run: Option<String>,
545 #[arg(long, short)]
547 verbose: bool
548 },
549
550 #[command(
552 alias = "x",
553 long_about = "Resolves a package and its dependencies, installs them \
554 if needed, then runs the requested binary directly. By \
555 default runs the first binary the package provides. \
556 Uses bwrap for sandboxed packages."
557 )]
558 Exec {
559 #[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
561 source: String,
562
563 #[arg(long)]
566 bin: Option<String>,
567
568 #[arg(long, short)]
570 verbose: bool,
571
572 #[arg(value_name = "ARGS")]
574 args: Vec<String>
575 },
576
577 Clone {
579 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
581 package: String,
582 #[arg(value_name = "LOCATION")]
584 location: Option<String>
585 },
586
587 Cache {
589 #[command(subcommand)]
591 command: CacheCommands
592 },
593
594 #[command(alias = "tx")]
596 Transaction {
597 #[command(subcommand)]
599 command: TransactionCommands
600 },
601
602 #[command(alias = "reg")]
604 Registry(cmd::registry::RegistryCommand),
605
606 Home(cmd::home::HomeCommand),
608
609 System(cmd::system::SystemCommand),
611
612 #[command(
614 aliases = ["repositories"],
615 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>)."
616 )]
617 Repo(cmd::repo::RepoCommand),
618
619 #[command(long_about = "Manage opt-in anonymous telemetry used to \
621 understand package popularity. Default is \
622 disabled.")]
623 Telemetry {
624 #[arg(value_enum)]
626 action: TelemetryAction
627 },
628
629 Create {
631 #[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
633 source: String,
634 app_name: Option<String>
636 },
637
638 #[command(
640 alias = "dg",
641 long_about = "Interactively choose and install an older version of a \
642 package from the local store or archive cache. This is \
643 useful if a recent update has introduced bugs or \
644 compatibility issues."
645 )]
646 Downgrade {
647 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
649 package: String
650 },
651
652 #[command(alias = "ext")]
654 Extension(ExtensionCommand),
655
656 Rollback {
658 #[arg(value_name = "INST_PACKAGES", required_unless_present = "last_transaction", help = PKG_SOURCE_HELP)]
660 package: Option<String>,
661
662 #[arg(long, conflicts_with = "package")]
664 last_transaction: bool
665 },
666
667 Man {
669 #[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
671 package_name: String,
672 #[arg(long)]
674 upstream: bool,
675 #[arg(long)]
677 raw: bool,
678 #[arg(long)]
680 no_tui: bool
681 },
682
683 #[command(alias = "pkg")]
685 Package(cmd::package::PackageCommand),
686
687 Pgp(cmd::pgp::PgpCommand),
689
690 Helper(cmd::helper::HelperCommand),
692
693 Doctor,
695
696 Audit {
698 #[arg(short, long)]
701 all: bool,
702 #[arg(long)]
704 registry: Option<String>,
705 #[arg(long)]
707 repo: Option<String>
708 },
709
710 #[command(external_subcommand)]
712 External(Vec<String>)
713}
714
715#[derive(clap::Parser, Debug)]
717pub struct ExtensionCommand {
718 #[command(subcommand)]
720 pub command: ExtensionCommands
721}
722
723#[derive(clap::Subcommand, Debug)]
725pub enum ExtensionCommands {
726 Add {
728 #[arg(required = true)]
730 name: String
731 },
732 Remove {
734 #[arg(required = true)]
736 name: String
737 }
738}
739
740#[derive(clap::Subcommand, Clone)]
742pub enum SyncCommands {
743 Add {
745 url: String
747 },
748 Remove {
750 handle: String
752 },
753 #[command(alias = "ls")]
755 List,
756 Set {
758 url: String
760 }
761}
762
763#[derive(clap::Subcommand)]
765pub enum CacheCommands {
766 Add {
768 #[arg(required = true)]
770 files: Vec<std::path::PathBuf>
771 },
772 #[command(alias = "clean")]
774 Clear {
775 #[arg(long)]
777 dry_run: bool
778 },
779 #[command(alias = "ls")]
781 List,
782 Mirror {
784 #[command(subcommand)]
786 command: CacheMirrorCommands
787 }
788}
789
790#[derive(clap::Subcommand)]
792pub enum CacheMirrorCommands {
793 Add {
795 url: String
797 },
798 Remove {
800 url: String
802 },
803 #[command(alias = "ls")]
805 List
806}
807
808#[derive(clap::Subcommand)]
810pub enum TransactionCommands {
811 #[command(alias = "ls")]
813 List,
814 Show {
816 id: String
818 },
819 Files {
821 id: String
823 }
824}
825
826#[derive(clap::ValueEnum, Clone)]
828enum TelemetryAction {
829 Status,
831 Enable,
833 Disable
835}
836
837pub fn run() -> anyhow::Result<()> {
844 let styles = styling::Styles::styled()
845 .header(
846 styling::AnsiColor::Yellow.on_default() | styling::Effects::BOLD
847 )
848 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
849 .literal(styling::AnsiColor::Green.on_default())
850 .placeholder(styling::AnsiColor::Cyan.on_default());
851
852 let commit: &str = option_env!("ZOI_COMMIT_HASH").unwrap_or("dev");
853 let cmd = Cli::command().styles(styles.clone());
854 let matches = cmd.clone().get_matches();
855 let cli = match Cli::from_arg_matches(&matches) {
856 Ok(cli) => cli,
857 Err(err) => {
858 err.print()?;
859 return Err(anyhow::anyhow!("Failed to parse arguments"));
860 }
861 };
862
863 if let Some(root) = cli.root {
864 crate::pkg::sysroot::set_sysroot(root);
865 }
866
867 let config = crate::pkg::config::read_config().unwrap_or_default();
868
869 let is_offline = cli.offline || config.offline_mode;
870 crate::pkg::offline::set_offline(is_offline);
871
872 let mut all_pkg_dirs = cli.pkg_dirs;
873 for dir in config.pkg_dirs {
874 let path = std::path::PathBuf::from(dir);
875 if !all_pkg_dirs.contains(&path) {
876 all_pkg_dirs.push(path);
877 }
878 }
879 crate::pkg::pkgdir::set_pkg_dirs(all_pkg_dirs);
880
881 utils::check_path();
882
883 if let Err(e) = crate::pkg::pgp::ensure_builtin_keys() {
884 eprintln!(
885 "{}: Failed to ensure builtin PGP keys: {}",
886 "Warning".yellow(),
887 e
888 );
889 }
890
891 let plugin_manager = crate::pkg::plugin::PluginManager::new()?;
892 if let Err(e) = plugin_manager.load_all(cli.yes) {
893 eprintln!("{}: Failed to load plugins: {}", "Warning".yellow(), e);
894 }
895
896 if cli.version_flag {
897 cmd::version::run(BRANCH, STATUS, NUMBER, commit);
898 return Ok(());
899 }
900
901 if let Some(command) = cli.command {
902 let needs_lock = matches!(
903 command,
904 Commands::Install { .. }
905 | Commands::Uninstall { .. }
906 | Commands::Update { .. }
907 | Commands::Autoremove { .. }
908 | Commands::Rollback { .. }
909 | Commands::Package(_)
910 );
911
912 let _lock_guard = if needs_lock {
913 Some(lock::acquire_lock()?)
914 } else {
915 None
916 };
917
918 let result = match command {
919 Commands::GenerateCompletions { shell } => {
920 let mut cmd = Cli::command();
921 let bin_name = cmd.get_name().to_string();
922 generate(shell, &mut cmd, bin_name, &mut io::stdout());
923 Ok(())
924 }
925 Commands::Complete {
926 shell,
927 index,
928 words
929 } => cmd::complete::run(shell, index, &words),
930 Commands::GenerateManual => cmd::gen_man::run().map_err(Into::into),
931 Commands::Version => {
932 cmd::version::run(BRANCH, STATUS, NUMBER, commit);
933 Ok(())
934 }
935 Commands::About => {
936 cmd::about::run(BRANCH, STATUS, NUMBER, commit);
937 Ok(())
938 }
939 Commands::Info => cmd::info::run(BRANCH, STATUS, NUMBER, commit),
940 Commands::Sync {
941 command,
942 verbose,
943 fallback,
944 no_package_managers,
945 force,
946 local,
947 frozen,
948 scope
949 } => {
950 if let Some(cmd) = command {
951 match cmd {
952 SyncCommands::Add { url } => {
953 cmd::sync::add_registry(&url)
954 }
955 SyncCommands::Remove { handle } => {
956 cmd::sync::remove_registry(&handle)
957 }
958 SyncCommands::List => cmd::sync::list_registries(),
959 SyncCommands::Set { url } => {
960 cmd::sync::set_registry(&url)
961 }
962 }
963 } else if local {
964 plugin_manager.trigger_hook("on_pre_sync", None)?;
965 let res =
966 cmd::sync::run_local(verbose, fallback, force, frozen);
967 plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
968 res
969 } else {
970 plugin_manager.trigger_hook("on_pre_sync", None)?;
971 let res = cmd::sync::run(
972 verbose,
973 fallback,
974 no_package_managers,
975 force,
976 scope
977 );
978 plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
979 res
980 }
981 }
982 Commands::Migrate(args) => cmd::migrate::run(args),
983 Commands::List {
984 all,
985 outdated,
986 registry,
987 repo,
988 package_type,
989 foreign,
990 names,
991 completion
992 } => cmd::list::run(
993 all,
994 outdated,
995 registry.as_deref(),
996 repo.as_deref(),
997 package_type.as_deref(),
998 foreign,
999 names,
1000 completion
1001 ),
1002 Commands::Show {
1003 package_name,
1004 raw,
1005 purl
1006 } => cmd::show::run(&package_name, raw, purl),
1007 Commands::Pin { package, version } => {
1008 cmd::pin::run(&package, &version)
1009 }
1010 Commands::Provides { term } => cmd::provides::run(&term),
1011 Commands::Tree { packages } => cmd::tree::run(&packages),
1012 Commands::Unpin { package } => cmd::unpin::run(&package),
1013 Commands::Mark {
1014 packages,
1015 as_dependency,
1016 as_explicit
1017 } => cmd::mark::run(&packages, as_dependency, as_explicit),
1018 Commands::Update {
1019 package_names,
1020 all,
1021 dry_run,
1022 explain,
1023 plan_json,
1024 verbose,
1025 interactive
1026 } => cmd::update::run(
1027 all,
1028 &package_names,
1029 cli.yes,
1030 dry_run,
1031 explain,
1032 plan_json,
1033 verbose,
1034 interactive
1035 )
1036 .map_err(|e| cmd::ux::with_failure_hint("update", e)),
1037 Commands::Install(args) => args
1038 .run(cli.yes)
1039 .map_err(|e| cmd::ux::with_failure_hint("install", e)),
1040 Commands::Use { packages, global } => {
1041 cmd::use_cmd::run(&packages, global)
1042 }
1043 Commands::Uninstall(args) => args
1044 .run(cli.yes)
1045 .map_err(|e| cmd::ux::with_failure_hint("uninstall", e)),
1046 Commands::Run { cmd_alias, args } => {
1047 cmd::run::run(cmd_alias.as_deref(), &args)
1048 }
1049 Commands::Env {
1050 env_alias,
1051 export_shell
1052 } => cmd::env::run(env_alias.as_deref(), export_shell),
1053 Commands::Dev { run, repo } => cmd::dev::run(run, repo),
1054 Commands::Upgrade { force, tag, branch } => {
1055 match cmd::upgrade::run(
1056 BRANCH, STATUS, NUMBER, force, tag, branch
1057 ) {
1058 Ok(()) => {
1059 println!(
1060 "\n{}",
1061 "Zoi upgraded successfully! Please restart your \
1062 shell for changes to take effect."
1063 .green()
1064 );
1065 println!(
1066 "\n{}: https://github.com/zillowe/zoi/blob/main/CHANGELOG.md",
1067 "Changelog".cyan().bold()
1068 );
1069 println!(
1070 "\n{}: To update shell completions, run 'zoi \
1071 shell <your-shell>'.",
1072 "Hint".cyan().bold()
1073 );
1074 }
1075 Err(e) if e.to_string() == "already_on_latest" => {}
1076 Err(e) if e.to_string() == "managed_by_package_manager" => {
1077 }
1078 Err(e) => return Err(e)
1079 }
1080 Ok(())
1081 }
1082 Commands::Autoremove { dry_run } => {
1083 cmd::autoremove::run(cli.yes, dry_run)
1084 }
1085 Commands::Why { package_name } => cmd::why::run(&package_name),
1086 Commands::Owner { path } => cmd::owner::run(&path),
1087 Commands::Files { package } => cmd::files::run(&package),
1088 Commands::History {
1089 verify,
1090 export,
1091 ndjson
1092 } => cmd::history::run(verify, export, ndjson),
1093 Commands::Search {
1094 search_term,
1095 registry,
1096 repo,
1097 package_type,
1098 tags,
1099 sort,
1100 files,
1101 interactive
1102 } => cmd::search::run(
1103 &search_term,
1104 registry.as_deref(),
1105 repo.as_deref(),
1106 package_type.as_deref(),
1107 tags,
1108 &sort,
1109 files,
1110 interactive
1111 ),
1112 Commands::Service(args) => cmd::service::run(args),
1113 Commands::Shell {
1114 shell,
1115 hook,
1116 scope,
1117 packages,
1118 run,
1119 verbose
1120 } => {
1121 let target_shell = shell
1122 .or_else(crate::pkg::utils::get_current_shell)
1123 .unwrap_or(Shell::Bash);
1124 if hook {
1125 cmd::shell::print_hook(target_shell)
1126 } else if !packages.is_empty() {
1127 cmd::shell::enter_ephemeral_shell(
1128 &packages,
1129 run,
1130 verbose,
1131 Some(&plugin_manager)
1132 )
1133 } else {
1134 cmd::shell::run(target_shell, scope)
1135 }
1136 }
1137 Commands::Exec {
1138 source,
1139 bin,
1140 verbose,
1141 args
1142 } => cmd::exec::run(&source, bin, &args, verbose),
1143 Commands::Download {
1144 package,
1145 archive: _,
1146 source,
1147 output_dir
1148 } => {
1149 let download_type = if source {
1150 cmd::download::DownloadType::Source
1151 } else {
1152 cmd::download::DownloadType::Archive
1153 };
1154 cmd::download::run(&package, download_type, output_dir)
1155 }
1156 Commands::Clone { package, location } => {
1157 cmd::clone::run(&package, location, cli.yes)
1158 }
1159 Commands::Cache { command } => match command {
1160 CacheCommands::Add { files } => cmd::cache::add(&files),
1161 CacheCommands::Clear { dry_run } => cmd::cache::clear(dry_run),
1162 CacheCommands::List => cmd::cache::list(),
1163 CacheCommands::Mirror { command } => match command {
1164 CacheMirrorCommands::Add { url } => {
1165 cmd::cache::add_mirror(&url)
1166 }
1167 CacheMirrorCommands::Remove { url } => {
1168 cmd::cache::remove_mirror(&url)
1169 }
1170 CacheMirrorCommands::List => cmd::cache::list_mirrors()
1171 }
1172 },
1173 Commands::Transaction { command } => match command {
1174 TransactionCommands::List => cmd::transaction::list(),
1175 TransactionCommands::Show { id } => cmd::transaction::show(&id),
1176 TransactionCommands::Files { id } => {
1177 cmd::transaction::files(&id)
1178 }
1179 },
1180 Commands::Repo(args) => cmd::repo::run(args),
1181 Commands::Registry(args) => cmd::registry::run(args),
1182 Commands::Home(args) => cmd::home::run(args),
1183 Commands::System(args) => cmd::system::run(args, cli.yes),
1184 Commands::Telemetry { action } => {
1185 use cmd::telemetry::{TelemetryCommand, run};
1186 let cmd = match action {
1187 TelemetryAction::Status => TelemetryCommand::Status,
1188 TelemetryAction::Enable => TelemetryCommand::Enable,
1189 TelemetryAction::Disable => TelemetryCommand::Disable
1190 };
1191 run(cmd)
1192 }
1193 Commands::Create { source, app_name } => cmd::create::run(
1194 cmd::create::CreateCommand { source, app_name },
1195 cli.yes,
1196 Some(&plugin_manager)
1197 ),
1198 Commands::Downgrade { package } => {
1199 cmd::downgrade::run(&package, cli.yes, Some(&plugin_manager))
1200 }
1201 Commands::Extension(args) => {
1202 cmd::extension::run(args, cli.yes, Some(&plugin_manager))
1203 }
1204 Commands::Rollback {
1205 package,
1206 last_transaction
1207 } => {
1208 if last_transaction {
1209 cmd::rollback::run_transaction_rollback(
1210 cli.yes,
1211 Some(&plugin_manager)
1212 )
1213 } else if let Some(pkg) = package {
1214 cmd::rollback::run(&pkg, cli.yes, Some(&plugin_manager))
1215 } else {
1216 Ok(())
1217 }
1218 }
1219 Commands::Man {
1220 package_name,
1221 upstream,
1222 raw,
1223 no_tui
1224 } => cmd::man::run(&package_name, upstream, raw, no_tui),
1225 Commands::Package(args) => cmd::package::run(args),
1226 Commands::Pgp(args) => cmd::pgp::run(args),
1227 Commands::Helper(args) => cmd::helper::run(args),
1228 Commands::Doctor => cmd::doctor::run(),
1229 Commands::Audit {
1230 all,
1231 registry,
1232 repo
1233 } => cmd::audit::run(all, registry, repo.as_deref()),
1234 Commands::External(args) => {
1235 let (cmd_name, cmd_args) =
1236 if let Some((first, rest)) = args.split_first() {
1237 (first, rest.to_vec())
1238 } else {
1239 return Err(anyhow::anyhow!("No command specified"));
1240 };
1241
1242 match plugin_manager.run_command(cmd_name, cmd_args) {
1243 Ok(true) => Ok(()),
1244 Ok(false) => {
1245 let mut shadow_cmd = Cli::command().styles(styles);
1246 shadow_cmd =
1247 shadow_cmd.allow_external_subcommands(false);
1248
1249 let err = shadow_cmd
1250 .clone()
1251 .try_get_matches_from(std::env::args())
1252 .err()
1253 .unwrap_or_else(|| {
1254 shadow_cmd.error(
1255 clap::error::ErrorKind::InvalidSubcommand,
1256 format!(
1257 "unrecognized subcommand '{cmd_name}'"
1258 )
1259 )
1260 });
1261
1262 let plugin_cmds = plugin_manager.list_commands()?;
1263 if !plugin_cmds.is_empty() {
1264 eprintln!(
1265 "{}:",
1266 "Available Plugin Commands".cyan().bold()
1267 );
1268 for (pcmd, pdesc) in plugin_cmds {
1269 if pdesc.is_empty() {
1270 eprintln!(" {pcmd}");
1271 } else {
1272 eprintln!(
1273 " {:<12} {}",
1274 pcmd,
1275 pdesc.dimmed()
1276 );
1277 }
1278 }
1279 eprintln!();
1280 }
1281
1282 err.exit();
1283 }
1284 Err(e) => Err(e)
1285 }
1286 }
1287 };
1288
1289 if let Err(e) = result {
1290 eprintln!("Error: {e}");
1291 std::process::exit(1);
1292 }
1293 }
1294 Ok(())
1295}