diff --git a/src/cli.vala b/src/cli.vala
index 912e67b3b9b635af9831582f13d66b718ebd7746..10b59a805864d1e5287e8a1d8aa4f9424a4892ff 100644
--- a/src/cli.vala
+++ b/src/cli.vala
@@ -75,6 +75,8 @@ namespace Pamac {
 						display_checkupdates_help ();
 					} else if (args[2] == "upgrade" || args[2] == "update") {
 						display_upgrade_help ();
+					} else if (args[2] == "clean") {
+						display_clean_help ();
 					} else {
 						display_help ();
 					}
@@ -467,6 +469,105 @@ namespace Pamac {
 				if (!error) {
 					try_lock_and_run (start_sysupgrade);
 				}
+			} else if (args[1] == "clean") {
+				init_transaction ();
+				bool error = false;
+				bool verbose = false;
+				bool build_files = false;
+				bool dry_run = false;
+				bool no_confirm = false;
+				int i = 2;
+				while (i < args.length) {
+					unowned string arg = args[i];
+					if (arg == "--help" || arg == "-h") {
+						display_clean_help ();
+						error = true;
+						break;
+					} else if (arg == "--build-files" || arg == "-b") {
+						build_files = true;
+					} else if (arg == "--no-confirm") {
+						no_confirm = true;
+					} else if (arg == "--keep" || arg == "-k") {
+						if (args[i + 1] != null) {
+							int64 num;
+							if (int64.try_parse (args[i + 1], out num)) {
+								database.config.clean_keep_num_pkgs = num;
+							} else {
+								display_clean_help ();
+								error = true;
+								break;
+							}
+						} else {
+							display_clean_help ();
+							error = true;
+							break;
+						}
+						i++;
+					} else if (arg.has_prefix ("-k")) {
+						string number = arg.split ("-k", 2)[1];
+						if (number.has_prefix ("=")) {
+							number = number.split ("=", 2)[1];
+							if (number == "") {
+								display_clean_help ();
+								error = true;
+								break;
+							}
+						}
+						int64 num;
+						if (int64.try_parse (number, out num)) {
+							database.config.clean_keep_num_pkgs = num;
+						} else {
+							display_clean_help ();
+							error = true;
+							break;
+						}
+					} else if (arg == "--uninstalled" || arg == "-u") {
+						database.config.clean_rm_only_uninstalled = true;
+					} else if (arg == "--dry-run" || arg == "-d") {
+						dry_run = true;
+					} else if (arg == "--verbose" || arg == "-v") {
+						verbose = true;
+					} else if (arg == "-bv" || arg == "-vb") {
+						build_files = true;
+						verbose = true;
+					} else if (arg == "-dv" || arg == "-vd") {
+						dry_run = true;
+						verbose = true;
+					} else if (arg == "-bd" || arg == "-db") {
+						dry_run = true;
+						build_files = true;
+					} else if (arg == "-du" || arg == "-ud") {
+						dry_run = true;
+						database.config.clean_rm_only_uninstalled = true;
+					} else if (arg == "-uv" || arg == "-vu") {
+						verbose = true;
+						database.config.clean_rm_only_uninstalled = true;
+					} else if (arg == "-bdv" || arg == "-bvd"
+							|| arg == "-dbv" || arg == "-dvb"
+							|| arg == "-vbd" || arg == "-vdb") {
+						build_files = true;
+						dry_run = true;
+						verbose = true;
+					} else if (arg == "-udv" || arg == "-uvd"
+							|| arg == "-duv" || arg == "-dvu"
+							|| arg == "-vud" || arg == "-vdu") {
+						database.config.clean_rm_only_uninstalled = true;
+						dry_run = true;
+						verbose = true;
+					} else {
+						display_clean_help ();
+						error = true;
+						break;
+					}
+					i++;
+				}
+				if (!error) {
+					if (build_files) {
+						clean_build_files (dry_run, verbose, no_confirm);
+					} else {
+						clean_cache (dry_run, verbose, no_confirm);
+					}
+				}
 			} else {
 				display_help ();
 			}
@@ -618,6 +719,7 @@ namespace Pamac {
 		void display_help () {
 			string[] actions = {"--version",
 								"--help,-h",
+								"clean",
 								"checkupdates",
 								"update,upgrade",
 								"search",
@@ -628,7 +730,8 @@ namespace Pamac {
 								"clone",
 								"build",
 								"remove"};
-			string[] options_actions = {"checkupdates",
+			string[] options_actions = {"clean",
+										"checkupdates",
 										"update,upgrade",
 										"search",
 										"info",
@@ -998,6 +1101,44 @@ namespace Pamac {
 			}
 		}
 
+		void display_clean_help () {
+			stdout.printf (dgettext (null, "Clean packages cache or build files"));
+			stdout.printf ("\n\n");
+			stdout.printf ("pamac clean [%s]".printf (dgettext (null, "options")));
+			stdout.printf ("\n\n");
+			stdout.printf (dgettext (null, "options") + ":\n");
+			int max_length = 0;
+			string[] options = {"  %s <%s>".printf ("-k, --keep", dgettext (null, "number")),
+								"  -u, --uninstalled",
+								"  -b, --build-files",
+								"  -d, --dry-run",
+								"  -v, --verbose",
+								"  --no-confirm"};
+			foreach (unowned string option in options) {
+				int length = option.char_count ();
+				if (length > max_length) {
+					max_length = length;
+				}
+			}
+			string[] details = {dgettext (null, "specify how many versions of each package are kept in the cache directory"),
+								dgettext (null, "only target uninstalled packages"),
+								dgettext (null, "remove all build files, the build directory is the one specified in pamac.conf"),
+								dgettext (null, "do not remove files, only find candidate packages"),
+								dgettext (null, "also display all files names"),
+								dgettext (null, "bypass any and all confirmation messages")};
+			int i = 0;
+			foreach (unowned string option in options) {
+				string[] cuts = split_string (details[i], max_length + 3);
+				print_aligned (option, " : %s".printf (cuts[0]), max_length);
+				int j = 1;
+				while (j < cuts.length) {
+					print_aligned ("", "%s".printf (cuts[j]), max_length + 3);
+					j++;
+				}
+				i++;
+			}
+		}
+
 		async void search_pkgs (string search_string) {
 			var installed_pkgs = database.search_installed_pkgs (search_string);
 			var repos_pkgs = database.search_repos_pkgs (search_string);
@@ -1793,6 +1934,85 @@ namespace Pamac {
 			}
 		}
 
+		bool ask_user (string question) {
+			// ask user confirmation
+			stdout.printf ("%s %s ", question, dgettext (null, "[y/N]"));
+			char buf[32];
+			if (stdin.gets (buf) != null) {
+				string ans = (string) buf;
+				// remove trailing newline and uppercase
+				ans = ans.replace ("\n", "").down ();
+				// just return use default
+				if (ans != "") {
+					if (ans == dgettext (null, "y") ||
+						ans == dgettext (null, "yes")) {
+						return true;
+					}
+				}
+			}
+			return false;
+		}
+
+		void clean_cache (bool dry_run, bool verbose, bool no_confirm) {
+			HashTable<string, int64?> details = database.get_clean_cache_details (database.config.clean_keep_num_pkgs, database.config.clean_rm_only_uninstalled);
+			int64 total_size = 0;
+			var filenames = new SList<string> ();
+			var iter = HashTableIter<string, int64?> (details);
+			unowned string filename;
+			int64? size;
+			while (iter.next (out filename, out size)) {
+				total_size += size;
+				filenames.append (filename);
+			}
+			if (database.config.clean_rm_only_uninstalled) {
+				stdout.printf ("%s\n", dgettext (null, "Remove only the versions of uninstalled packages"));
+			}
+			stdout.printf ("%s: %llu\n\n", dgettext (null, "Number of versions of each package to keep in the cache"), database.config.clean_keep_num_pkgs);
+			if (verbose) {
+				filenames.sort (strcmp);
+				foreach (unowned string name in filenames) {
+					stdout.printf ("%s\n", name);
+				}
+				stdout.printf ("\n");
+			}
+			uint files_nb = filenames.length ();
+			stdout.printf ("%s: %s  (%s)\n".printf (dgettext (null, "To delete"), dngettext (null, "%u file", "%u files", files_nb).printf (files_nb), format_size (total_size)));
+			if (files_nb == 0 || dry_run) {
+				return;
+			}
+			if (no_confirm || ask_user ("%s ?".printf (dgettext (null, "Clean cache")))) {
+				transaction.clean_cache (database.config.clean_keep_num_pkgs, database.config.clean_rm_only_uninstalled);
+			}
+		}
+
+		void clean_build_files (bool dry_run, bool verbose, bool no_confirm) {
+			HashTable<string, int64?> details = database.get_build_files_details (database.config.aur_build_dir);
+			int64 total_size = 0;
+			var filenames = new SList<string> ();
+			var iter = HashTableIter<string, int64?> (details);
+			unowned string filename;
+			int64? size;
+			while (iter.next (out filename, out size)) {
+				total_size += size;
+				filenames.append (filename);
+			}
+			if (verbose) {
+				filenames.sort (strcmp);
+				foreach (unowned string name in filenames) {
+					stdout.printf ("%s\n", name);
+				}
+				stdout.printf ("\n");
+			}
+			uint files_nb = filenames.length ();
+			stdout.printf ("%s: %s  (%s)\n".printf (dgettext (null, "To delete"), dngettext (null, "%u file", "%u files", files_nb).printf (files_nb), format_size (total_size)));
+			if (files_nb == 0 || dry_run) {
+				return;
+			}
+			if (no_confirm || ask_user ("%s ?".printf (dgettext (null, "Clean build files")))) {
+				transaction.clean_build_files (database.config.aur_build_dir);
+			}
+		}
+
 		void install_pkgs (string[] targets) {
 			foreach (unowned string target in targets) {
 				bool found = false;
@@ -1868,8 +2088,8 @@ namespace Pamac {
 				}
 			}
 			int num_length = pkgs.length ().to_string ().length + 1;
-			stdout.printf ("%s:\n".printf (dngettext (null, "There is %u member in group %s",
-						"There are %u members in group %s", pkgs.length ()).printf (pkgs.length (), grpname)));
+			stdout.printf ("%s:\n".printf (dngettext (null, "There is %1u member in group %2s",
+						"There are %1u members in group %2s", pkgs.length ()).printf (pkgs.length (), grpname)));
 			int num = 1;
 			foreach (unowned Package pkg in pkgs) {
 				stdout.printf ("%*s  %-*s  %-*s  %s\n",
diff --git a/src/transaction.vala b/src/transaction.vala
index d62d019cf1658c947fdf0ed38cf55ea487ea0f1a..2f60bce347f4ac028f83d0baeee08d22b89c7454 100644
--- a/src/transaction.vala
+++ b/src/transaction.vala
@@ -242,6 +242,10 @@ namespace Pamac {
 			transaction_interface.clean_cache (keep_nb, only_uninstalled);
 		}
 
+		public void clean_build_files (string build_dir) {
+			transaction_interface.clean_build_files (build_dir);
+		}
+
 		public void start_set_pkgreason (string pkgname, uint reason) {
 			transaction_interface.set_pkgreason_finished.connect (on_set_pkgreason_finished);
 			transaction_interface.start_set_pkgreason (pkgname, reason);