diff --git a/README.md b/README.md
index 536a004..ad5ea1c 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@
### 主要部分 (`/base`)
- Main [`easyplugin-main`](base/main)
-- ~~Command*~~ (已独立项目到 [**MineCommands**](https://github.com/CarmJos/MineCommands))
+- Command [`easyplugin-command`](base/command)
- ~~Messages*~~ (已独立项目到 [**EasyMessages**](https://github.com/CarmJos/EasyMessages))
- ~~Configuration~~ (已独立项目到 [**MineConfiguration**](https://github.com/CarmJos/MineConfiguration))
- ~~Database~~ (已独立项目到 [**EasySQL**](https://github.com/CarmJos/EasySQL))
diff --git a/base/command/pom.xml b/base/command/pom.xml
new file mode 100644
index 0000000..5ad877a
--- /dev/null
+++ b/base/command/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ easyplugin-parent
+ cc.carm.lib
+ 1.4.1
+ ../../pom.xml
+
+ 4.0.0
+
+ easyplugin-command
+
+
+ 8
+ 8
+
+
+
\ No newline at end of file
diff --git a/base/command/src/main/java/cc/carm/lib/easyplugin/command/CommandHandler.java b/base/command/src/main/java/cc/carm/lib/easyplugin/command/CommandHandler.java
new file mode 100644
index 0000000..e2d4968
--- /dev/null
+++ b/base/command/src/main/java/cc/carm/lib/easyplugin/command/CommandHandler.java
@@ -0,0 +1,136 @@
+
+package cc.carm.lib.easyplugin.command;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.TabExecutor;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public abstract class CommandHandler implements TabExecutor, NamedExecutor {
+
+ protected final @NotNull JavaPlugin plugin;
+ protected final @NotNull String cmd;
+
+ protected final @NotNull Map registeredCommands = new HashMap<>();
+ protected final @NotNull Map registeredHandlers = new HashMap<>();
+
+ public CommandHandler(@NotNull JavaPlugin plugin) {
+ this(plugin, plugin.getName());
+ }
+
+ public CommandHandler(@NotNull JavaPlugin plugin, @NotNull String cmd) {
+ this.plugin = plugin;
+ this.cmd = cmd;
+ }
+
+ public abstract void noArgs(CommandSender sender);
+
+ public abstract void unknownCommand(CommandSender sender, String[] args);
+
+ public abstract void noPermission(CommandSender sender);
+
+ @Override
+ public String getName() {
+ return this.cmd;
+ }
+
+ public void registerSubCommand(SubCommand command) {
+ for (String alias : command.getAliases()) {
+ if (this.registeredCommands.containsKey(alias)) {
+ this.plugin.getLogger().warning("Conflicting command aliases '" + alias + "' for '" + command.getName() + "', overwriting.");
+ }
+ this.registeredCommands.put(alias, command);
+ }
+ }
+
+ public void registerHandler(CommandHandler handler) {
+ for (String alias : handler.getAliases()) {
+ if (this.registeredCommands.containsKey(alias)) {
+ this.plugin.getLogger().warning("Conflicting command aliases '" + alias + "' for '" + handler.getName() + "', overwriting.");
+ }
+ this.registeredHandlers.put(alias, handler);
+ }
+ }
+
+ @Override
+ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
+ if (args.length == 0) {
+ this.noArgs(sender);
+ } else {
+ String sub = args[0].toLowerCase();
+ CommandHandler handler = this.registeredHandlers.get(sub);
+ if (handler != null) {
+ if (!handler.hasPermission(sender)) {
+ this.noPermission(sender);
+ } else {
+ handler.onCommand(sender, command, label, this.shortenArgs(args));
+ }
+ } else {
+ SubCommand subCommand = this.registeredCommands.get(sub);
+ if (subCommand == null) {
+ this.unknownCommand(sender, args);
+ } else if (!subCommand.hasPermission(sender)) {
+ this.noPermission(sender);
+ } else {
+ try {
+ subCommand.execute(this.plugin, sender, this.shortenArgs(args));
+ } catch (ArrayIndexOutOfBoundsException var9) {
+ this.unknownCommand(sender, args);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
+ if (args.length == 0) return Collections.emptyList();
+
+ String input = args[0].toLowerCase();
+ if (args.length == 1) {
+ return getExecutors().stream()
+ .filter(e -> e.hasPermission(sender))
+ .map(NamedExecutor::getName)
+ .filter(s -> StringUtil.startsWithIgnoreCase(s, input))
+ .collect(Collectors.toList());
+ } else {
+
+ CommandHandler handler = this.registeredHandlers.get(input);
+ if (handler != null && handler.hasPermission(sender)) {
+ return handler.onTabComplete(sender, command, alias, this.shortenArgs(args));
+ }
+
+ SubCommand subCommand = this.registeredCommands.get(input);
+ if (subCommand != null && subCommand.hasPermission(sender)) {
+ return subCommand.tabComplete(this.plugin, sender, this.shortenArgs(args));
+ }
+
+ return Collections.emptyList();
+ }
+ }
+
+ public List getExecutors() {
+ Set executors = new HashSet<>();
+ executors.addAll(this.registeredHandlers.values());
+ executors.addAll(this.registeredCommands.values());
+ List sortedExecutors = new ArrayList<>(executors);
+ sortedExecutors.sort(Comparator.comparing(NamedExecutor::getName));
+ return sortedExecutors;
+ }
+
+ protected String[] shortenArgs(String[] args) {
+ if (args.length == 0) {
+ return args;
+ } else {
+ List argList = new ArrayList<>(Arrays.asList(args).subList(1, args.length));
+ return argList.toArray(new String[0]);
+ }
+ }
+
+}
diff --git a/base/command/src/main/java/cc/carm/lib/easyplugin/command/NamedExecutor.java b/base/command/src/main/java/cc/carm/lib/easyplugin/command/NamedExecutor.java
new file mode 100644
index 0000000..045f616
--- /dev/null
+++ b/base/command/src/main/java/cc/carm/lib/easyplugin/command/NamedExecutor.java
@@ -0,0 +1,20 @@
+package cc.carm.lib.easyplugin.command;
+
+import org.bukkit.permissions.Permissible;
+
+import java.util.Collections;
+import java.util.List;
+
+public interface NamedExecutor {
+
+ String getName();
+
+ default List getAliases() {
+ return Collections.singletonList(getName());
+ }
+
+ default boolean hasPermission(Permissible permissible) {
+ return true;
+ }
+
+}
diff --git a/base/command/src/main/java/cc/carm/lib/easyplugin/command/SimpleCompleter.java b/base/command/src/main/java/cc/carm/lib/easyplugin/command/SimpleCompleter.java
new file mode 100644
index 0000000..7ef030c
--- /dev/null
+++ b/base/command/src/main/java/cc/carm/lib/easyplugin/command/SimpleCompleter.java
@@ -0,0 +1,93 @@
+package cc.carm.lib.easyplugin.command;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.World;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.potion.PotionEffectType;
+import org.bukkit.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class SimpleCompleter {
+
+ public static @NotNull List objects(@NotNull String input, Object... objects) {
+ return objects(input, objects.length, objects);
+ }
+
+ public static @NotNull List objects(@NotNull String input, int limit, Object... objects) {
+ return objects(input, limit, Arrays.asList(objects));
+ }
+
+ public static @NotNull List objects(@NotNull String input, List objects) {
+ return objects(input, objects.size(), objects);
+ }
+
+ public static @NotNull List objects(@NotNull String input, int limit, List