Command Framework — Overview
The EriAPI Command Framework provides a fluent, declarative API for creating Minecraft commands. Instead of manually parsing strings, handling tab completion, and managing permissions, you describe your command structure and the framework does the rest.
Key Features
- Fluent Builder API — describe commands as readable chains of method calls
- Type-safe Arguments — 7 built-in argument types with automatic parsing and validation
- Auto Tab Completion — every argument type provides contextual suggestions out of the box
- Permissions & Cooldowns — built-in support for permission levels, custom predicates, and per-player cooldowns
- Auto-generated Help —
/yourcommand helpis created automatically with usage and descriptions - Sub-command Trees — nest sub-commands to any depth with isolated arguments and executors
- Brigo Compatible — works alongside Brigo for Brigadier-style suggestions on 1.12.2
This framework wraps Forge's ICommand system. You don't need to know anything
about ICommand — the framework handles registration, parsing, and dispatching for you.
Quick Start
Here is everything you need to create and register a simple command in two steps.
Step 1 — Define your command
import fr.eri.eriapi.command.EriCommand;
import fr.eri.eriapi.command.arg.StringArg;
public class MyCommand {
public static void register() {
EriCommand.create("hello")
.description("Greets the player")
.rootAliases("hi", "greet")
.arg(StringArg.of("name").optional("World"))
.runs(ctx -> {
String name = ctx.getString("name");
ctx.reply("Hello, " + name + "!");
})
.register();
}
}
Step 2 — Hook into your mod lifecycle
@Mod(modid = "yourmod")
public class YourMod {
@EventHandler
public void init(FMLInitializationEvent event) {
// Register command definitions
MyCommand.register();
}
@EventHandler
public void onServerStarting(FMLServerStartingEvent event) {
// Register all commands with the server
CommandRegistry.getInstance().onServerStarting(event);
}
}
That's it! Players can now type /hello, /hello Steve,
/hi, or /greet in-game. Tab completion and help are automatic.
EriCommand
EriCommand is the entry point for building commands. It uses a fluent builder
pattern — every method returns the builder so you can chain calls.
Builder Methods
| Method | Description |
|---|---|
create(String name) |
Static factory — creates a new command builder with the given name |
description(String desc) |
Sets the description shown in auto-help |
permission(int level) |
Requires a minimum permission level (0=all, 2=op, 4=server) |
permission(Predicate<ICommandSender>) |
Custom permission check (e.g., team membership, item in hand) |
rootAliases(String...) |
Alternative top-level names for the command |
autoHelp(boolean) |
Enable/disable auto-generated /cmd help (default: true) |
cooldown(int ticks) |
Per-player cooldown in ticks (20 ticks = 1 second) |
arg(Argument) |
Adds a typed argument to this command or sub-command |
sub(String name) |
Begins a sub-command definition (returns the sub-command builder) |
runs(Consumer<CommandContext>) |
Sets the executor — the code that runs when the command is invoked |
end() |
Returns to the parent builder (used after sub()) |
register() |
Finalizes the command and registers it with CommandRegistry |
Minimal Example
EriCommand.create("ping")
.description("Replies with pong")
.runs(ctx -> ctx.reply("Pong!"))
.register();
Argument Types
Arguments define what data a command expects. Each argument type handles parsing, validation,
and tab completion automatically. All arguments support .optional(defaultValue)
to make them non-required.
IntArg
Parses an integer value. Supports optional range constraints.
// Required integer, any value
IntArg.of("count")
// With range validation (1 to 64)
IntArg.of("amount").range(1, 64)
// Optional with default value
IntArg.of("amount").range(1, 64).optional(1)
// In a command
EriCommand.create("give")
.arg(IntArg.of("amount").range(1, 64).optional(1))
.runs(ctx -> {
int amount = ctx.getInt("amount");
// ...
})
.register();
FloatArg
Parses a floating-point number. Supports optional range constraints.
// Required float
FloatArg.of("speed")
// With range
FloatArg.of("speed").range(0.0f, 10.0f)
// In a command
EriCommand.create("setspeed")
.arg(FloatArg.of("speed").range(0, 10))
.runs(ctx -> {
float speed = ctx.getFloat("speed");
ctx.reply("Speed set to " + speed);
})
.register();
StringArg
Parses a string value. Three modes are available: single word (default), greedy (consumes all remaining input), and quoted (captures text between quotes).
// Single word
StringArg.of("name")
// Greedy — captures everything after this argument
StringArg.greedy("message")
// Quoted — captures text between "quotes"
StringArg.quoted("text")
// In a command
EriCommand.create("say")
.arg(StringArg.greedy("message"))
.runs(ctx -> {
String msg = ctx.getString("message");
ctx.broadcastMessage(msg);
})
.register();
A greedy StringArg consumes all remaining input, so it must always be the
last argument in a command or sub-command.
PlayerArg
Parses an online player name. Tab completion suggests all online players.
EriCommand.create("heal")
.permission(2)
.arg(PlayerArg.of("target"))
.runs(ctx -> {
EntityPlayerMP player = ctx.getPlayer("target");
player.setHealth(player.getMaxHealth());
ctx.reply("Healed " + player.getName());
})
.register();
ItemArg
Parses a Minecraft item identifier (e.g., minecraft:diamond_sword).
Tab completion suggests all registered item names.
EriCommand.create("spawn")
.arg(ItemArg.of("item"))
.arg(IntArg.of("count").range(1, 64).optional(1))
.runs(ctx -> {
Item item = ctx.getItem("item");
int count = ctx.getInt("count");
// Give item to player...
})
.register();
PosArg
Parses a 3D position (x y z). Supports relative coordinates with ~ notation
(e.g., ~ ~5 ~). Tab completion suggests ~ ~ ~.
EriCommand.create("teleport")
.permission(2)
.arg(PosArg.of("pos"))
.runs(ctx -> {
BlockPos pos = ctx.getPos("pos");
ctx.getSenderPlayer().setPositionAndUpdate(
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5
);
ctx.reply("Teleported to " + pos.getX() + " " + pos.getY() + " " + pos.getZ());
})
.register();
EnumArg
Parses a value from a Java enum. Tab completion suggests all enum constant names (case-insensitive).
public enum GameMode { SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR }
EriCommand.create("gamemode")
.permission(2)
.arg(EnumArg.of("mode", GameMode.class))
.runs(ctx -> {
GameMode mode = ctx.getEnum("mode", GameMode.class);
ctx.reply("Game mode set to " + mode.name());
})
.register();
Argument Summary
| Type | Factory | Context Getter | Tab Completion |
|---|---|---|---|
IntArg |
IntArg.of("name") |
ctx.getInt("name") |
Suggests <name> |
FloatArg |
FloatArg.of("name") |
ctx.getFloat("name") |
Suggests <name> |
StringArg |
StringArg.of("name") |
ctx.getString("name") |
Suggests <name> |
PlayerArg |
PlayerArg.of("name") |
ctx.getPlayer("name") |
Online player names |
ItemArg |
ItemArg.of("name") |
ctx.getItem("name") |
Registered item IDs |
PosArg |
PosArg.of("name") |
ctx.getPos("name") |
~ ~ ~ |
EnumArg |
EnumArg.of("name", E.class) |
ctx.getEnum("name", E.class) |
Enum constant names |
CommandContext
When your command runs, it receives a CommandContext object. This object gives you
access to the parsed arguments, the sender, the server, and utility methods for responding.
Available Methods
| Method | Return Type | Description |
|---|---|---|
getString("name") |
String |
Gets a string argument value |
getInt("name") |
int |
Gets an integer argument value |
getFloat("name") |
float |
Gets a float argument value |
getPlayer("name") |
EntityPlayerMP |
Gets the resolved player entity |
getItem("name") |
Item |
Gets the resolved item |
getPos("name") |
BlockPos |
Gets the parsed block position |
getEnum("name", E.class) |
E |
Gets the parsed enum value |
getSender() |
ICommandSender |
The entity or console that sent the command |
getSenderPlayer() |
EntityPlayerMP |
The player who sent the command (throws if not a player) |
getServer() |
MinecraftServer |
The server instance |
reply(String msg) |
void |
Sends a chat message to the sender |
replyError(String msg) |
void |
Sends a red error message to the sender |
broadcastMessage(String msg) |
void |
Broadcasts a message to all online players |
Usage Example
EriCommand.create("info")
.arg(PlayerArg.of("target"))
.runs(ctx -> {
EntityPlayerMP target = ctx.getPlayer("target");
EntityPlayerMP sender = ctx.getSenderPlayer();
ctx.reply("Player: " + target.getName());
ctx.reply("Health: " + target.getHealth() + "/" + target.getMaxHealth());
ctx.reply("Position: " + target.getPosition().toString());
if (target.getHealth() < 5) {
ctx.replyError("Warning: this player is low on health!");
}
})
.register();
Sub-commands
Use .sub(name) to create nested sub-commands. Each sub-command can have its own
arguments, permissions, description, and executor. Call .end() to return to the
parent builder.
Basic Sub-commands
EriCommand.create("team")
.description("Team management commands")
.sub("create")
.description("Create a new team")
.permission(2)
.arg(StringArg.of("name"))
.runs(ctx -> {
String name = ctx.getString("name");
ctx.reply("Team '" + name + "' created!");
})
.end()
.sub("invite")
.description("Invite a player to your team")
.arg(PlayerArg.of("player"))
.runs(ctx -> {
EntityPlayerMP player = ctx.getPlayer("player");
ctx.reply("Invited " + player.getName());
})
.end()
.sub("leave")
.description("Leave your current team")
.runs(ctx -> ctx.reply("You left the team."))
.end()
.register();
This creates: /team create <name>, /team invite <player>,
and /team leave. The auto-generated /team help lists all three
sub-commands with their descriptions.
Nested Sub-commands
You can nest sub-commands within sub-commands to create deep command trees:
EriCommand.create("admin")
.permission(4)
.sub("config")
.sub("set")
.arg(StringArg.of("key"))
.arg(StringArg.of("value"))
.runs(ctx -> {
ctx.reply("Set " + ctx.getString("key") + " = " + ctx.getString("value"));
})
.end()
.sub("get")
.arg(StringArg.of("key"))
.runs(ctx -> {
ctx.reply("Value of " + ctx.getString("key") + ": ...");
})
.end()
.end()
.register();
This creates /admin config set <key> <value> and
/admin config get <key>.
A command with sub-commands does not need a .runs() on the root. If no root
executor is set and the player types just /team, the auto-help is shown automatically.
Permissions & Cooldowns
Permission Levels
Forge uses integer permission levels. You can set a minimum level on any command or sub-command:
| Level | Who can use it |
|---|---|
0 |
Everyone (default) |
1 |
Moderators |
2 |
Operators (op) |
3 |
Operators (higher) |
4 |
Server console / owner only |
EriCommand.create("ban")
.permission(2) // Only ops can use this
.arg(PlayerArg.of("target"))
.arg(StringArg.greedy("reason").optional("No reason given"))
.runs(ctx -> {
EntityPlayerMP target = ctx.getPlayer("target");
String reason = ctx.getString("reason");
// ban logic...
})
.register();
Custom Permission Predicate
For more complex checks, use a predicate:
EriCommand.create("vip")
.permission(sender -> {
// Only allow if the sender is a player with a specific tag
if (sender instanceof EntityPlayerMP) {
return ((EntityPlayerMP) sender).getTags().contains("vip");
}
return false;
})
.runs(ctx -> ctx.reply("Welcome, VIP!"))
.register();
Cooldowns
Prevent command spam with per-player cooldowns. The cooldown is specified in ticks (20 ticks = 1 second).
EriCommand.create("heal")
.cooldown(600) // 30 seconds (600 ticks)
.runs(ctx -> {
EntityPlayerMP player = ctx.getSenderPlayer();
player.setHealth(player.getMaxHealth());
ctx.reply("You have been healed!");
})
.register();
When a player triggers a command on cooldown, they automatically receive a message telling them how many seconds remain before they can use it again.
Tab Completion
Tab completion is handled automatically based on your argument types. When the player presses Tab, the framework determines which argument is being typed and provides appropriate suggestions.
Built-in Suggestions
- PlayerArg — suggests online player names
- ItemArg — suggests all registered item identifiers
- EnumArg — suggests all enum constant names
- PosArg — suggests
~ ~ ~(relative coords) - Sub-commands — suggests available sub-command names
- IntArg / FloatArg / StringArg — shows
<name>as a hint
Custom Suggestions
You can provide custom suggestions for any argument using .suggests():
EriCommand.create("warp")
.arg(StringArg.of("destination")
.suggests(() -> Arrays.asList("spawn", "arena", "shop", "hub"))
)
.runs(ctx -> {
String dest = ctx.getString("destination");
ctx.reply("Warping to " + dest + "...");
})
.register();
Brigo Compatibility
If you use Brigo (a Brigadier bridge for 1.12.2), EriCommand's tab completion integrates seamlessly. Brigo provides client-side suggestion rendering with tooltips — EriCommand supplies the suggestions, and Brigo handles the presentation.
// No extra code needed — EriCommand automatically exposes
// its argument structure to Brigo if Brigo is present.
// The player sees Brigadier-style suggestions in the chat bar.
EriCommand.create("example")
.arg(PlayerArg.of("target")) // Brigo renders player names as suggestions
.arg(IntArg.of("amount")) // Brigo shows <amount> hint
.runs(ctx -> { /* ... */ })
.register();
CommandRegistry
CommandRegistry is a singleton that collects all commands built with
EriCommand and registers them with the Forge server when it starts.
How It Works
- Build phase — calling
.register()on anEriCommandadds it toCommandRegistry. - Server start — calling
CommandRegistry.getInstance().onServerStarting(event)registers all collected commands with Forge's command manager.
// Typically in your @Mod class:
@EventHandler
public void onServerStarting(FMLServerStartingEvent event) {
CommandRegistry.getInstance().onServerStarting(event);
}
// You can also query registered commands:
CommandRegistry registry = CommandRegistry.getInstance();
int count = registry.getCommandCount();
boolean exists = registry.hasCommand("mycommand");
You can call EriCommand.create(...).register() at any time before
onServerStarting. The registry collects them and registers them all at once.
Full Example
Here is a complete, real-world example demonstrating most features of the Command Framework: sub-commands, multiple argument types, permissions, cooldowns, and custom suggestions.
import fr.eri.eriapi.command.EriCommand;
import fr.eri.eriapi.command.arg.*;
public class CommandShop {
public enum Category { WEAPONS, ARMOR, TOOLS, FOOD, BLOCKS }
public static void register() {
EriCommand.create("shop")
.description("In-game item shop")
.rootAliases("store", "market")
// /shop buy <item> [amount]
.sub("buy")
.description("Buy an item from the shop")
.cooldown(40) // 2 second cooldown
.arg(ItemArg.of("item"))
.arg(IntArg.of("amount").range(1, 64).optional(1))
.runs(ctx -> {
Item item = ctx.getItem("item");
int amount = ctx.getInt("amount");
EntityPlayerMP player = ctx.getSenderPlayer();
// Purchase logic...
ctx.reply("Bought " + amount + "x " + item.getRegistryName());
})
.end()
// /shop sell
.sub("sell")
.description("Sell the item in your hand")
.runs(ctx -> {
EntityPlayerMP player = ctx.getSenderPlayer();
ItemStack held = player.getHeldItemMainhand();
if (held.isEmpty()) {
ctx.replyError("You must hold an item to sell!");
return;
}
ctx.reply("Sold " + held.getDisplayName());
})
.end()
// /shop browse <category>
.sub("browse")
.description("Browse items by category")
.arg(EnumArg.of("category", Category.class))
.runs(ctx -> {
Category cat = ctx.getEnum("category", Category.class);
ctx.reply("Browsing " + cat.name() + " category...");
})
.end()
// /shop admin set-price <item> <price> (op only)
.sub("admin")
.permission(2)
.description("Shop administration")
.sub("set-price")
.description("Set the price of an item")
.arg(ItemArg.of("item"))
.arg(FloatArg.of("price").range(0, 10000))
.runs(ctx -> {
Item item = ctx.getItem("item");
float price = ctx.getFloat("price");
ctx.reply("Price of " + item.getRegistryName() + " set to " + price);
})
.end()
.sub("reload")
.description("Reload shop configuration")
.permission(4)
.runs(ctx -> ctx.reply("Shop config reloaded."))
.end()
.end()
.register();
}
}
This single registration creates the following command tree:
├── buy <item> [amount] — Buy an item (2s cooldown)
├── sell — Sell held item
├── browse <category> — Browse by category
├── admin (op only)
│ ├── set-price <item> <price>
│ └── reload (server only)
└── help — Auto-generated
The Command Framework is designed to let you focus on what your commands do, not on boilerplate parsing and validation. Define your structure, write your logic, and let EriAPI handle the rest.