An annotation-driven command framework for Java. The core module is pure JDK + Guava; platform bindings live in their own modules (spigot-1.8.8, terminal, ...).
| Module | Purpose | JDK target |
|---|---|---|
core |
Annotation processing, argument parsing, dispatch, sub-commands. No platform deps. | 8 (compiled with toolchain 21) |
spigot-1.8.8 |
Bukkit 1.8.8 binding: CoreCommandAdapter, CoreCommandMap injection, BungeeCord help rendering. |
8 |
terminal |
Pure-JDK REPL binding — drives commands from stdin. | 21 |
Adding a future binding (e.g. paper-1.21/) means copying spigot-1.8.8/ and updating the API coordinates.
@Command(name = "ping", desc = "ping pong")
@Alias({"p"})
public class PingCommand {
@CommandExecutor
public void run(CommandSender sender) {
sender.sendMessage("pong");
}
}public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
Platform platform = SpigotPlatform.create(this);
CoreCommandMap map = CoreCommandMap.injectCommandMap(platform, getLogger());
map.registerCommand("myplugin", PingCommand.class);
}
}TerminalRuntime runtime = new TerminalRuntime();
runtime.register(PingCommand.class);
runtime.run(); // reads stdin, dispatches commands@Command(name, desc)on the class. Optional@Alias({...})for alternative labels.@CommandExecutoron the method that runs the command. Parameters are resolved from@Arg,@Sender,@Service, or@Contextannotations.@Arg(desc, defaultValue?)for positional user arguments. Built-in parsers cover primitives,UUID,java.time.Duration,Enum, andInetAddress. Spigot addsPlayer,World,ItemStack,Command.- Nested
@Command-annotated static inner classes become sub-commands automatically. External sub-commands attach via@SubCommand({Other.class}). @TabCompleteron a method matching an executor's signature provides custom completions.
Two layers, used independently or together:
- String permissions — call
sender.hasPermission("foo.bar")from within a check method, or rely on the binding's permission system. The Spigot binding'sCoreCommandAdapter.testPermissionSilentcallscommand.hasAccess(sender). @AccessCheckermethods — annotate any method on the command class that returnsbooleanand takes aCommandSender/User. The framework runs every@AccessCheckermethod before dispatch; if any returnsfalse, execution is blocked.applyTorestricts a check to specific sub-command names.@Permission("foo.bar")— sugar over@AccessChecker. On the class it gates every executor; on a method it gates only that one. Multiple values (@Permission({"a","b"})) are AND'd. Callssender.hasPermission(...)under the hood.
JAVA_HOME=$(/usr/libexec/java_home -v 17) ./gradlew testGradle 8.7 cannot launch under JDK 26, so set JAVA_HOME to a 17/21 JDK. Toolchains for the actual compile (Java 8 for core/spigot-1.8.8, Java 21 for terminal) are auto-provisioned via foojay.
core and terminal use hand-rolled stubs (FakeCommandSender, TestPlatform) — no Mockito. spigot-1.8.8 uses Mockito 4.11 (Java 8 compatible) for Bukkit interfaces. Bukkit-server-dependent paths (event firing, scheduler) are exercised by integration runs against a live server, not unit tests.