CraftCommand’s command model maps directly to the structure of your Java classes — there is no YAML file, no XML configuration, and no method registry to maintain by hand. A single annotated class is the complete and authoritative description of a command, its subcommands, and all their parameters. The annotation processor reads this structure at compile time and generates a wrapper that mirrors it exactly.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ProjectUnified/CraftCommand/llms.txt
Use this file to discover all available pages before exploring further.
Root command
Place@Command on a class to declare it as a root command. The value attribute sets the primary name that users type. aliases provides alternative names that route to the same command, and description is an optional human-readable summary.
RetentionPolicy.CLASS, so it is consumed entirely by the processor and leaves no trace in the compiled bytecode of your command class.
Default handler
Annotate exactly one method with@Default to mark the handler that runs when the user types the root command name with no recognized subcommand keyword. The method must have a sender as its first parameter, followed by any argument parameters.
@Default method and no matching subcommand is found, the generated wrapper calls generateUnknownSubcommandMessage, which by default prints the list of available subcommands.
Subcommands on methods
Add@Subcommand to a method to create a subcommand. The value attribute is the keyword the user types after the root command name. aliases provides additional keywords for the same method. Every parameter after the sender maps to a positional command argument in declaration order.
Nested subcommand classes
@Subcommand can also be placed on an inner class — static or non-static — to create a subcommand sub-tree. The nested class becomes its own command scope: it can contain its own @Default method, its own @Subcommand methods, and even further nested @Subcommand classes. This enables arbitrarily deep command hierarchies without any extra configuration.
parentInstance.new InnerClass(). For static nested classes, it uses new NestedClass() directly. Either style works; static nested classes are slightly preferred for clarity.
Parameter mapping rules
The order and type of parameters in a handler method follow a fixed set of rules that the processor enforces at code-generation time:| # | Rule |
|---|---|
| 1 | First parameter = sender. The first parameter of every handler method is the command sender. Its type is resolved either by type matching (an instanceof check against the platform sender type) or by a @Resolve-annotated method in the command class. |
| 2 | Remaining parameters = positional arguments. Parameters two onwards map to consecutive words in the command input, in declaration order. |
| 3 | @Optional parameters must follow required ones. Optional parameters may have a default value string: @Optional("1") provides "1" as the fallback if the argument is absent. All required parameters must be declared before any optional ones. |
| 4 | @Greedy must be the last parameter. A @Greedy String consumes all remaining input tokens as a single joined string. Only one greedy parameter is allowed per method and it must be declared last. |
Tab completion routing
The processor generates suggestion routing that mirrors the subcommand tree exactly. When the user presses Tab, the generatedtabComplete method inspects the current argument position and routes to the matching suggestion helper.
For each parameter, the suggestion source is determined in this order:
- If the parameter has
@Suggest("methodName"), the generator emits a call to that method or field. The referenced name can be either a method (accepting 0, 1, 2, or 3 parameters in the form(),(sender),(sender, current), or(sender, String[] args, String current)) or a field of typeList<String>. For example,@Suggest("getItems")routes to a method, while@Suggest("modes")routes to apublic final List<String> modesfield. - If the parameter type has a global resolver registered via
manager.registerResolver(...), the generated code callsmanager.getResolver(Type.class).suggest(sender, args, current). - For
boolean/Booleanparameters, the generator emits a call to a privatesuggestBoolean(current)helper that returns["true", "false"]filtered by the current input. - For other built-in types (
int,double,String, etc.), the generator returns an empty list.