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