[{"data":1,"prerenderedAt":1331},["ShallowReactive",2],{"docs-\u002Fmodules\u002Fcommands":3},{"id":4,"title":5,"body":6,"description":1321,"extension":1322,"meta":1323,"navTitle":1324,"navigation":1325,"path":1326,"rawbody":1327,"seo":1328,"stem":1329,"__hash__":1330},"docs\u002Fmodules\u002Fcommands.md","Commands",{"type":7,"value":8,"toc":1288},"minimark",[9,13,22,36,41,48,51,62,72,78,84,88,91,115,122,126,131,134,140,143,149,153,155,161,163,169,173,175,181,183,189,193,196,202,205,209,223,261,272,278,295,299,317,323,330,336,340,401,409,437,440,480,484,487,516,519,544,550,564,605,608,648,651,657,659,662,680,683,703,709,715,737,743,749,756,760,765,768,802,805,825,828,836,853,857,864,870,873,879,888,892,903,907,914,920,923,929,933,939,945,948,953,959,962,966,973,979,982,1025,1028,1034,1038,1045,1051,1057,1070,1073,1084,1091,1112,1118,1124,1142,1156,1162,1181,1187,1203,1207,1212,1218,1228,1258,1281],[10,11,5],"h1",{"id":12},"commands",[14,15,16,17,21],"p",{},"MagicUtils commands are annotation-first, but the runtime model is registry\nbased. Each platform exposes a ",[18,19,20],"code",{},"CommandRegistry"," that owns parsers,\npermissions, and command registration for that plugin or mod.",[14,23,24,25,30,31,35],{},"See ",[26,27,29],"a",{"href":28},"commands-cheatsheet","Commands Cheat Sheet"," for a quick reference and\n",[26,32,34],{"href":33},"permissions","Permissions"," for node generation details.",[37,38,40],"h2",{"id":39},"why-an-annotation-first-command-framework","Why an annotation-first command framework",[14,42,43,44,47],{},"Registering a command by hand means parsing raw ",[18,45,46],{},"String[] args"," yourself,\nvalidating and converting each argument, writing tab-completion separately,\nchecking permissions, and repeating all of it in a different shape for every\nplatform's dispatcher. A single \"give a player an amount\" command turns into\ndozens of lines of boilerplate that has nothing to do with your feature.",[14,49,50],{},"MagicUtils lets you describe the command as a method: parameter types drive\nparsing and completion, annotations add options and permissions, and the same\ncommand class registers on Bukkit, BungeeCord, Velocity, Fabric, and NeoForge.",[14,52,53,57,58,61],{},[54,55,56],"strong",{},"Before"," (raw Bukkit ",[18,59,60],{},"onCommand",", manual parsing and checks):",[63,64,70],"pre",{"className":65,"code":67,"language":68,"meta":69},[66],"language-java","public boolean onCommand(CommandSender s, Command c, String label, String[] args) {\n    if (!s.hasPermission(\"donate.give\")) { s.sendMessage(\"No permission\"); return true; }\n    if (args.length \u003C 2) { s.sendMessage(\"Usage: \u002Fdonate give \u003Cplayer> \u003Camount>\"); return true; }\n    Player target = Bukkit.getPlayer(args[0]);\n    if (target == null) { s.sendMessage(\"Unknown player\"); return true; }\n    int amount;\n    try { amount = Integer.parseInt(args[1]); } catch (NumberFormatException e) {\n        s.sendMessage(\"Amount must be a number\"); return true;\n    }\n    \u002F\u002F ... finally, the actual logic\n    return true;\n}\n","java","",[18,71,67],{"__ignoreMap":69},[14,73,74,77],{},[54,75,76],{},"After"," (typed parameters, generated parsing\u002Fpermission\u002Fcompletion):",[63,79,82],{"className":80,"code":81,"language":68,"meta":69},[66],"@SubCommand(name = \"give\", permission = \"donate.give\")\npublic CommandResult give(\n        @Sender MagicSender sender,\n        @ParamName(\"player\") Player target,\n        int amount\n) {\n    \u002F\u002F just the logic; parsing, validation, and permission are handled\n    return CommandResult.success(\"Gave \" + amount + \" to \" + target.getName());\n}\n",[18,83,81],{"__ignoreMap":69},[37,85,87],{"id":86},"registration-models","Registration Models",[14,89,90],{},"There are three common ways to obtain a registry:",[92,93,94,98,104],"ol",{},[95,96,97],"li",{},"Bootstrap helper creates it for you.",[95,99,100,103],{},[18,101,102],{},"CommandRegistry.create(...)"," returns an instance you keep explicitly.",[95,105,106,107,110,111,114],{},"Legacy ",[18,108,109],{},"CommandRegistry.initialize(...)"," \u002F ",[18,112,113],{},"createDefault(...)"," creates the\ndefault registry for the current platform.",[14,116,117,118,121],{},"For multi-plugin or multi-mod setups, prefer an explicit registry instance or\nthe scoped static overloads. The no-arg ",[18,119,120],{},"register(...)"," methods operate on the\ndefault registry.",[37,123,125],{"id":124},"platform-registration","Platform Registration",[127,128,130],"h3",{"id":129},"bukkitpaper","Bukkit\u002FPaper",[14,132,133],{},"Bootstrap-first:",[63,135,138],{"className":136,"code":137,"language":68,"meta":69},[66],"BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin)\n        .permissionPrefix(\"myplugin\")\n        .enableCommands()\n        .configureCommands(registry -> registry.registerCommand(new DonateCommand()))\n        .buildRuntime();\n",[18,139,137],{"__ignoreMap":69},[14,141,142],{},"Manual registry:",[63,144,147],{"className":145,"code":146,"language":68,"meta":69},[66],"CommandRegistry registry = CommandRegistry.create(plugin, \"myplugin\", logger);\nregistry.registerCommand(new DonateCommand());\nregistry.registerCommand(new AdminCommand());\n",[18,148,146],{"__ignoreMap":69},[127,150,152],{"id":151},"fabric","Fabric",[14,154,133],{},[63,156,159],{"className":157,"code":158,"language":68,"meta":69},[66],"FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod(\"mymod\", () -> server)\n        .permissionPrefix(\"mymod\")\n        .enableCommands()\n        .buildRuntime();\n\nCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {\n    if (magic.commandRegistry() != null) {\n        magic.commandRegistry().registerCommand(dispatcher, new DonateCommand());\n    }\n});\n",[18,160,158],{"__ignoreMap":69},[14,162,142],{},[63,164,167],{"className":165,"code":166,"language":68,"meta":69},[66],"CommandRegistry registry = CommandRegistry.create(\"mymod\", \"mymod\", logger, 2);\n\nCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {\n    registry.registerCommand(dispatcher, new DonateCommand());\n});\n",[18,168,166],{"__ignoreMap":69},[127,170,172],{"id":171},"velocity","Velocity",[14,174,133],{},[63,176,179],{"className":177,"code":178,"language":68,"meta":69},[66],"VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, plugin, \"MyPlugin\", dataDirectory)\n        .permissionPrefix(\"myplugin\")\n        .enableCommands()\n        .configureCommands(registry -> registry.registerCommand(new DonateCommand()))\n        .buildRuntime();\n",[18,180,178],{"__ignoreMap":69},[14,182,142],{},[63,184,187],{"className":185,"code":186,"language":68,"meta":69},[66],"CommandRegistry registry = CommandRegistry.create(proxy, plugin, \"myplugin\", loggerCore);\nregistry.registerCommand(new DonateCommand());\n",[18,188,186],{"__ignoreMap":69},[127,190,192],{"id":191},"neoforge","NeoForge",[14,194,195],{},"NeoForge currently uses the manual path:",[63,197,200],{"className":198,"code":199,"language":68,"meta":69},[66],"CommandRegistry registry = CommandRegistry.create(\"mymod\", \"mymod\", loggerCore, 2);\n\n@SubscribeEvent\npublic void onRegisterCommands(RegisterCommandsEvent event) {\n    registry.registerCommand(event.getDispatcher(), new DonateCommand());\n}\n",[18,201,199],{"__ignoreMap":69},[14,203,204],{},"The second argument is always the permission prefix used when generating nodes.",[37,206,208],{"id":207},"brigadier-integration","Brigadier Integration",[14,210,211,212,214,215,218,219,222],{},"On Brigadier platforms (Fabric and NeoForge) the ",[18,213,20],{}," is backed by\n",[18,216,217],{},"BrigadierCommandRegistry\u003CS>"," from ",[18,220,221],{},"magicutils-commands-brigadier",". You rarely\ntouch it directly, but two hooks let you customise how MagicUtils maps arguments\nonto Brigadier:",[224,225,226,238],"ul",{},[95,227,228,233,234,237],{},[54,229,230],{},[18,231,232],{},"parserRegistrar"," (a ",[18,235,236],{},"Consumer\u003CTypeParserRegistry\u003CS>>",") registers your own\ntype parsers so annotation and builder commands can accept custom types.",[95,239,240,233,245,248,249,252,253,256,257,260],{},[54,241,242],{},[18,243,244],{},"brigadierRegistrar",[18,246,247],{},"Consumer\u003CBrigadierArgumentRegistry\u003CS>>",") registers\n",[18,250,251],{},"BrigadierArgumentResolver\u003CS>"," instances that map a ",[18,254,255],{},"CommandArgument"," onto a\nnative Brigadier ",[18,258,259],{},"ArgumentType",", so a parameter can use real Brigadier parsing\nand suggestions instead of MagicUtils' string-based parsing.",[14,262,263,264,267,268,271],{},"A resolver returns a ",[18,265,266],{},"BrigadierArgumentShape"," (or ",[18,269,270],{},"null"," to skip):",[63,273,276],{"className":274,"code":275,"language":68,"meta":69},[66],"registry.register(argument -> {\n    if (argument.getType() == GameProfile.class) {\n        \u002F\u002F use a native Brigadier argument type with its own suggestions\n        return BrigadierArgumentShape.nativeSuggestions(GameProfileArgument.gameProfile());\n    }\n    return null; \u002F\u002F fall back to MagicUtils parsing\n});\n",[18,277,275],{"__ignoreMap":69},[14,279,280,283,284,286,287,290,291,294],{},[18,281,282],{},"BrigadierArgumentShape.of(type)"," wraps a Brigadier ",[18,285,259],{}," without native\nsuggestions; ",[18,288,289],{},"nativeSuggestions(type)"," keeps Brigadier's own completion. Higher\n",[18,292,293],{},"priority()"," resolvers run first. Most commands never need this: the built-in\ntype parsers already cover players, worlds, enums, numbers, and booleans.",[37,296,298],{"id":297},"annotation-based-commands","Annotation-Based Commands",[14,300,301,302,305,306,309,310,313,314,316],{},"Use ",[18,303,304],{},"@CommandInfo"," on the class and ",[18,307,308],{},"@SubCommand"," on methods. A method named\n",[18,311,312],{},"execute"," without ",[18,315,308],{}," is treated as the root handler.",[63,318,321],{"className":319,"code":320,"language":68,"meta":69},[66],"@CommandInfo(\n        name = \"donate\",\n        description = \"DonateMenu main command\",\n        aliases = {\"d\"},\n        permission = \"donate.use\"\n)\npublic final class DonateCommand extends MagicCommand {\n\n    public CommandResult execute(@Sender MagicSender sender) {\n        return CommandResult.success(\"Opened menu\");\n    }\n\n    @SubCommand(name = \"give\", description = \"Give currency to a player\")\n    public CommandResult give(\n            @Sender MagicSender sender,\n            @ParamName(\"player\") Player target,\n            @Option(shortNames = {\"a\"}, longNames = {\"amount\"}) int amount,\n            @Option(shortNames = {\"s\"}, longNames = {\"silent\"}, flag = true) boolean silent\n    ) {\n        return CommandResult.success(silent ? \"\" : \"Done\");\n    }\n}\n",[18,322,320],{"__ignoreMap":69},[14,324,325,326,329],{},"Nested subcommands are supported via ",[18,327,328],{},"path",":",[63,331,334],{"className":332,"code":333,"language":68,"meta":69},[66],"@SubCommand(path = {\"npc\", \"commands\"}, name = \"add\")\npublic CommandResult addNpcCommand(...) { ... }\n",[18,335,333],{"__ignoreMap":69},[127,337,339],{"id":338},"common-annotations","Common Annotations",[224,341,342,348,358,364,370,376],{},[95,343,344,347],{},[18,345,346],{},"@ParamName"," overrides argument names for help output.",[95,349,350,353,354,357],{},[18,351,352],{},"@OptionalArgument"," or ",[18,355,356],{},"@DefaultValue(\"...\")"," marks a parameter optional.",[95,359,360,363],{},[18,361,362],{},"@Greedy"," captures the rest of the input.",[95,365,366,369],{},[18,367,368],{},"@Suggest(\"source\")"," adds completion hints.",[95,371,372,375],{},[18,373,374],{},"@Sender"," injects the sender and hides it from help output.",[95,377,378,381,382,385,386,389,390,393,394,110,397,400],{},[18,379,380],{},"@Option(shortNames = {\"a\"}, longNames = {\"amount\"})"," enables ",[18,383,384],{},"-a 5"," and\n",[18,387,388],{},"--amount 5",". Set ",[18,391,392],{},"flag = true"," for toggles such as ",[18,395,396],{},"-s",[18,398,399],{},"--silent",".",[14,402,403,405,406,329],{},[18,404,374],{}," supports sender filtering via ",[18,407,408],{},"AllowedSender",[224,410,411,423],{},[95,412,413,416,417,416,420],{},[18,414,415],{},"ANY",", ",[18,418,419],{},"PLAYER",[18,421,422],{},"CONSOLE",[95,424,425,416,428,416,431,416,434],{},[18,426,427],{},"BLOCK",[18,429,430],{},"MINECART",[18,432,433],{},"PROXIED",[18,435,436],{},"REMOTE",[14,438,439],{},"Platform-specific sender types can also be injected directly:",[224,441,442,451,460,471],{},[95,443,444,445,416,448],{},"Bukkit: ",[18,446,447],{},"CommandSender",[18,449,450],{},"Player",[95,452,453,454,416,457],{},"Fabric: ",[18,455,456],{},"ServerCommandSource",[18,458,459],{},"ServerPlayerEntity",[95,461,462,463,416,466,416,468],{},"Velocity: ",[18,464,465],{},"CommandSource",[18,467,450],{},[18,469,470],{},"ConsoleCommandSource",[95,472,473,474,416,477],{},"NeoForge: ",[18,475,476],{},"CommandSourceStack",[18,478,479],{},"ServerPlayer",[37,481,483],{"id":482},"suggestions-and-type-parsers","Suggestions And Type Parsers",[14,485,486],{},"Suggestions can come from:",[224,488,489,492,504,510],{},[95,490,491],{},"Built-in type parsers (players, worlds, enums, booleans).",[95,493,494,495,416,498,416,501,400],{},"Special sources such as ",[18,496,497],{},"@players",[18,499,500],{},"@worlds",[18,502,503],{},"@commands",[95,505,506,507,400],{},"Inline lists: ",[18,508,509],{},"@Suggest(\"{on,off,reset}\")",[95,511,512,513,400],{},"Methods on the command class: ",[18,514,515],{},"@Suggest(\"getItems\")",[14,517,518],{},"Suggestion methods can be:",[224,520,521,529,534,539],{},[95,522,523,353,526],{},[18,524,525],{},"String[] getItems()",[18,527,528],{},"List\u003CString> getItems()",[95,530,531],{},[18,532,533],{},"getItems(Player player)",[95,535,536],{},[18,537,538],{},"getItems(ServerCommandSource sender)",[95,540,541],{},[18,542,543],{},"getItems(CommandSource sender)",[14,545,546,549],{},[18,547,548],{},"@Suggest"," has two extra members:",[224,551,552,558],{},[95,553,554,557],{},[18,555,556],{},"permission = true"," filters the suggestions through the argument's permission,\nso a player only sees completions they are allowed to use.",[95,559,560,563],{},[18,561,562],{},"contextArgs = {\"world\"}"," passes the current values of earlier arguments into\nthe suggestion method, so later completions can depend on what was already\ntyped.",[565,566,569,588,594],"callout",{"title":567,"type":568},"Method arity with contextArgs","info",[14,570,571,572,575,576,579,580,583,584,587],{},"A no-argument suggestion method is tried\nfirst; only if none exists does the resolver call a method built from the\ncontext. When you use ",[18,573,574],{},"contextArgs",", the method receives ",[54,577,578],{},"each context value in\norder, followed by the current partial input"," as a trailing ",[18,581,582],{},"String",". So a\nsuggestion for an argument declared with ",[18,585,586],{},"contextArgs = {\"server\"}"," must take two\nparameters, not one:",[63,589,592],{"className":590,"code":591,"language":68,"meta":69},[66],"\u002F\u002F @Suggest(value = \"suggestWorlds\", contextArgs = {\"server\"})\npublic List\u003CString> suggestWorlds(String server, String currentInput) {\n    \u002F\u002F `server` is the already-typed value; `currentInput` is what the player\n    \u002F\u002F is typing now (use it to pre-filter, or ignore it and let the framework filter).\n    return worldsOf(server);\n}\n",[18,593,591],{"__ignoreMap":69},[14,595,596,597,600,601,604],{},"A one-parameter ",[18,598,599],{},"suggestWorlds(String server)"," silently returns nothing here,\nbecause the resolver looks for a method matching ",[18,602,603],{},"(server, currentInput)"," and\nfinds no match. The optional sender\u002Fplayer parameter, when present, comes before\nthe context values.",[14,606,607],{},"Built-in sources:",[224,609,610,620,626,633,638,642],{},[95,611,612,416,614,416,617],{},[18,613,497],{},[18,615,616],{},"@player",[18,618,619],{},"@allplayers",[95,621,622,625],{},[18,623,624],{},"@offlineplayers"," (Bukkit only)",[95,627,628,416,630],{},[18,629,500],{},[18,631,632],{},"@world",[95,634,635,625],{},[18,636,637],{},"@language_keys",[95,639,640],{},[18,641,503],{},[95,643,644,647],{},[18,645,646],{},"{a,b,c}"," inline list syntax",[14,649,650],{},"Custom parsers are registered on the registry's parser registry:",[63,652,655],{"className":653,"code":654,"language":68,"meta":69},[66],"registry.commandManager()\n        .getTypeParserRegistry()\n        .register(new MyTypeParser());\n",[18,656,654],{"__ignoreMap":69},[37,658,34],{"id":33},[14,660,661],{},"Permissions can be defined at three levels:",[224,663,664,669,674],{},[95,665,666],{},[18,667,668],{},"@CommandInfo.permission",[95,670,671],{},[18,672,673],{},"@SubCommand.permission",[95,675,676,679],{},[18,677,678],{},"@Permission"," on parameters",[14,681,682],{},"Generated nodes use this shape when you do not provide explicit values:",[224,684,685,691,697],{},[95,686,687,688],{},"Command: ",[18,689,690],{},"commands.\u003Ccommand>",[95,692,693,694],{},"Subcommand: ",[18,695,696],{},"commands.\u003Ccommand>.subcommand.\u003Cpath>",[95,698,699,700],{},"Argument: ",[18,701,702],{},"commands.\u003Ccommand>.subcommand.\u003Cpath>.argument.\u003Cname>",[14,704,705,706,708],{},"These nodes are prefixed by the registry permission prefix. See\n",[26,707,34],{"href":33}," for the platform-specific behaviour.",[14,710,711,714],{},[18,712,713],{},"MagicPermissionDefault"," controls the default access policy:",[224,716,717,722,727,732],{},[95,718,719],{},[18,720,721],{},"TRUE",[95,723,724],{},[18,725,726],{},"OP",[95,728,729],{},[18,730,731],{},"NOT_OP",[95,733,734],{},[18,735,736],{},"FALSE",[14,738,739,740,329],{},"For manual checks outside annotation processing, use ",[18,741,742],{},"MagicSender",[63,744,747],{"className":745,"code":746,"language":68,"meta":69},[66],"MagicSender sender = MagicSender.wrap(rawSender);\nif (MagicSender.hasPermission(rawSender, \"myplugin.admin\")) {\n    \u002F\u002F adapter-default fallback\n}\nif (sender != null && sender.hasPermission(\"myplugin.admin\", 4)) {\n    \u002F\u002F explicit fallback override for this check\n}\n",[18,748,746],{"__ignoreMap":69},[14,750,751,752,755],{},"The overload with ",[18,753,754],{},"fallbackOpLevel"," is mainly useful on Fabric and NeoForge,\nwhere adapters may fall back to command-source permission levels.",[37,757,759],{"id":758},"commandresult","CommandResult",[14,761,762,764],{},[18,763,759],{}," factories control whether MagicUtils sends feedback\nautomatically, and whether the logger prefix is attached.",[14,766,767],{},"Success:",[224,769,770,776,782,788],{},[95,771,772,775],{},[18,773,774],{},"CommandResult.success()"," succeeds with no reply text.",[95,777,778,781],{},[18,779,780],{},"CommandResult.success(\"Done\")"," sends a success reply (with prefix).",[95,783,784,787],{},[18,785,786],{},"CommandResult.success(\"Done\", false)"," sends the reply without the prefix.",[95,789,790,793,794,797,798,801],{},[18,791,792],{},"CommandResult.success(false, \"Done\")"," succeeds but suppresses the reply\n(the boolean is ",[18,795,796],{},"sendMessage",", so ",[18,799,800],{},"false"," means \"do not send\").",[14,803,804],{},"Failure:",[224,806,807,813,819],{},[95,808,809,812],{},[18,810,811],{},"CommandResult.failure(\"No permission\")"," sends a failure reply (with prefix).",[95,814,815,818],{},[18,816,817],{},"CommandResult.failure(\"No permission\", false)"," sends it without the prefix.",[95,820,821,824],{},[18,822,823],{},"CommandResult.failure(false)"," fails silently, sending no reply.",[14,826,827],{},"Not found:",[224,829,830],{},[95,831,832,835],{},[18,833,834],{},"CommandResult.notFound()"," returns the built-in \"command not found\" failure.",[14,837,838,839,842,843,846,847,842,850,400],{},"Note the two two-argument success overloads differ by parameter order:\n",[18,840,841],{},"success(String, boolean)"," is ",[18,844,845],{},"(message, sendPrefix)",", while\n",[18,848,849],{},"success(boolean, String)",[18,851,852],{},"(sendMessage, message)",[37,854,856],{"id":855},"threading","Threading",[14,858,859,860,863],{},"Commands run on the main thread by default. Use ",[18,861,862],{},"CommandThreading.ASYNC"," for\nIO-heavy or CPU-heavy work:",[63,865,868],{"className":866,"code":867,"language":68,"meta":69},[66],"@CommandInfo(name = \"donate\", threading = CommandThreading.ASYNC)\npublic final class DonateCommand extends MagicCommand {\n    public CommandResult execute(@Sender MagicSender sender) {\n        return CommandResult.success(\"done\");\n    }\n\n    @SubCommand(name = \"give\", threading = CommandThreading.ASYNC)\n    public CommandResult give(@Sender MagicSender sender, Player target) {\n        return CommandResult.success(\"ok\");\n    }\n}\n",[18,869,867],{"__ignoreMap":69},[14,871,872],{},"Builder equivalents:",[63,874,877],{"className":875,"code":876,"language":68,"meta":69},[66],"MagicCommand.\u003CCommandSender>builder(\"donate\")\n        .threading(CommandThreading.ASYNC)\n        .execute(ctx -> CommandResult.success(\"done\"))\n        .build();\n\nSubCommandSpec.\u003CCommandSender>builder(\"give\")\n        .threading(CommandThreading.ASYNC)\n        .execute(ctx -> CommandResult.success(\"ok\"))\n        .build();\n",[18,878,876],{"__ignoreMap":69},[14,880,881,882,353,885,400],{},"Only mark commands as async when your code is thread-safe. When you need to\ntouch platform APIs again, switch back to the main thread via\n",[18,883,884],{},"Platform.runOnMain(...)",[18,886,887],{},"Tasks.runOnMain(...)",[37,889,891],{"id":890},"help-output","Help Output",[14,893,894,895,898,899,902],{},"The help renderer respects permissions and hides commands or arguments the\nsender cannot access. It is styled through ",[18,896,897],{},"logger.{ext}"," under the ",[18,900,901],{},"help","\nsection.",[127,904,906],{"id":905},"standalone-help-command","Standalone Help Command",[14,908,909,910,913],{},"Bukkit and Fabric ship a ready-to-register ",[18,911,912],{},"HelpCommand"," wrapper:",[63,915,918],{"className":916,"code":917,"language":68,"meta":69},[66],"registry.registerCommand(new HelpCommand(logger, registry));\n",[18,919,917],{"__ignoreMap":69},[14,921,922],{},"You can rename it at runtime:",[63,924,927],{"className":925,"code":926,"language":68,"meta":69},[66],"registry.registerCommand(new HelpCommand(logger, registry)\n        .withName(\"donatehelp\")\n        .addAlias(\"dhelp\"));\n",[18,928,926],{"__ignoreMap":69},[127,930,932],{"id":931},"help-as-a-subcommand","Help As A Subcommand",[14,934,301,935,938],{},[18,936,937],{},"HelpCommandSupport"," when you want help inside another command tree or on\nplatforms that do not ship a dedicated wrapper:",[63,940,943],{"className":941,"code":942,"language":68,"meta":69},[66],"registry.registerCommand(new DonateCommand()\n        .addSubCommand(HelpCommandSupport.createHelpSubCommand(\n                \"help\",\n                loggerCore,\n                registry::commandManager\n        )));\n",[18,944,942],{"__ignoreMap":69},[37,946,742],{"id":947},"magicsender",[14,949,950,952],{},[18,951,742],{}," is the platform-neutral sender wrapper used throughout the\ncommand system:",[63,954,957],{"className":955,"code":956,"language":68,"meta":69},[66],"MagicSender sender = MagicSender.wrap(rawSender);\nif (MagicSender.hasPermission(rawSender, \"my.permission\")) {\n    \u002F\u002F ...\n}\n",[18,958,956],{"__ignoreMap":69},[14,960,961],{},"Use it when you want shared command logic across Bukkit, BungeeCord, Velocity,\nFabric, and NeoForge without branching on raw sender types.",[37,963,965],{"id":964},"builder-api","Builder API",[14,967,968,969,972],{},"Use the builder API when you need runtime composition but still want a real\n",[18,970,971],{},"MagicCommand"," instance:",[63,974,977],{"className":975,"code":976,"language":68,"meta":69},[66],"MagicCommand donateCommand = MagicCommand.\u003CCommandSender>builder(\"donate\")\n        .description(\"DonateMenu main command\")\n        .aliases(\"d\")\n        .execute(ctx -> CommandResult.success(\"Opened menu\"))\n        .subCommand(SubCommandSpec.\u003CCommandSender>builder(\"give\")\n                .description(\"Give currency\")\n                .argument(CommandArgument.builder(\"player\", Player.class).build())\n                .argument(CommandArgument.builder(\"amount\", Integer.class).build())\n                .execute(ctx -> CommandResult.success(\"ok\"))\n                .build())\n        .build();\n\nregistry.registerCommand(donateCommand);\n",[18,978,976],{"__ignoreMap":69},[14,980,981],{},"You can mix annotations with runtime overrides:",[224,983,984,995,1000,1005,1013,1019],{},[95,985,986,416,989,416,992],{},[18,987,988],{},"withName(...)",[18,990,991],{},"addAlias(...)",[18,993,994],{},"removeAlias(...)",[95,996,997],{},[18,998,999],{},"addSubCommand(SubCommandSpec\u003C?>)",[95,1001,1002],{},[18,1003,1004],{},"setExecute(...)",[95,1006,1007,110,1010],{},[18,1008,1009],{},"mount(MagicCommand)",[18,1011,1012],{},"mount(\"route\", existingCommand)",[95,1014,1015,1018],{},[18,1016,1017],{},"mountSubCommands(MagicCommand)"," — graft a carrier's sub-commands in flat",[95,1020,1021,1024],{},[18,1022,1023],{},"copy()"," — an unfrozen duplicate you can mutate and re-register",[14,1026,1027],{},"Nested builder subcommands are supported as well:",[63,1029,1032],{"className":1030,"code":1031,"language":68,"meta":69},[66],"SubCommandSpec\u003CCommandSender> npcAdd = SubCommandSpec.\u003CCommandSender>builder(\"add\")\n        .path(\"npc\", \"commands\")\n        .description(\"Add NPC command\")\n        .execute(ctx -> CommandResult.success(\"ok\"))\n        .build();\n",[18,1033,1031],{"__ignoreMap":69},[37,1035,1037],{"id":1036},"composing-existing-commands","Composing Existing Commands",[14,1039,1040,1041,1044],{},"Already-authored annotation commands can be mounted under another command tree\nwithout rewriting them into ",[18,1042,1043],{},"SubCommandSpec"," form:",[63,1046,1049],{"className":1047,"code":1048,"language":68,"meta":69},[66],"MagicCommand adminCommand = MagicCommand.\u003CCommandSender>builder(\"admin\")\n        .mount(\"punish\", new BanCommand())\n        .build();\n\nregistry.registerCommand(adminCommand);\n",[18,1050,1048],{"__ignoreMap":69},[14,1052,1053,1054,329],{},"This is intentionally different from ",[18,1055,1056],{},"withName(\"punish\")",[224,1058,1059,1064],{},[95,1060,1061,1063],{},[18,1062,988],{}," mutates the authored command instance itself",[95,1065,1066,1069],{},[18,1067,1068],{},"mount(\"punish\", command)"," keeps the source command identity intact and only\nchanges the route segment inside the parent tree",[14,1071,1072],{},"Mounting snapshots the child command structure at mount time. That means:",[224,1074,1075,1078,1081],{},[95,1076,1077],{},"changing the child name or aliases later does not rewrite the already-mounted\nparent tree",[95,1079,1080],{},"the mounted command still executes against the original child instance",[95,1082,1083],{},"when you override the mounted route, root aliases from the child are not\nexposed automatically",[127,1085,1087,1088],{"id":1086},"flat-mounting-sub-commands-with-mountsubcommands","Flat-mounting sub-commands with ",[18,1089,1090],{},"mountSubCommands",[14,1092,1093,1096,1097,1100,1101,1104,1105,1108,1109,329],{},[18,1094,1095],{},"mount(\"route\", cmd)"," nests the source command under a route segment, producing\n",[18,1098,1099],{},"\u002Froot \u003Croute> \u003Csub>",". Sometimes you instead want another command's\nsub-commands to appear ",[54,1102,1103],{},"directly"," under the root, at the same level —\n",[18,1106,1107],{},"\u002Froot \u003Csub>"," — without the source command's own name becoming a nesting level.\nThat is ",[18,1110,1111],{},"mountSubCommands(cmd)",[63,1113,1116],{"className":1114,"code":1115,"language":68,"meta":69},[66],"\u002F\u002F GreetCommand is a carrier: an @CommandInfo(name = \"greet\") class whose only\n\u002F\u002F job is to hold @SubCommand methods (e.g. `hello`). mountSubCommands grafts\n\u002F\u002F those methods straight under `root`, so `\u002Froot hello` works — `greet` never\n\u002F\u002F appears as a command level.\nMagicCommand root = manager.getCommand(\"root\").copy();\nroot.mountSubCommands(new GreetCommand());\nmanager.register(root, root.resolveInfo());\n",[18,1117,1115],{"__ignoreMap":69},[14,1119,1120,1121,329],{},"The difference from ",[18,1122,1123],{},"mount",[224,1125,1126,1135],{},[95,1127,1128,1131,1132],{},[18,1129,1130],{},"mount(\"greet\", cmd)"," → ",[18,1133,1134],{},"\u002Froot greet hello",[95,1136,1137,1131,1139],{},[18,1138,1111],{},[18,1140,1141],{},"\u002Froot hello",[14,1143,1144,1145,1147,1148,1151,1152,1155],{},"Only the source's sub-commands are carried over. Its own bare ",[18,1146,312],{}," (the\naction bound to the source's plain name) is intentionally ",[54,1149,1150],{},"not"," grafted — use\n",[18,1153,1154],{},"mount(...)"," if you want the source's top-level action too. This lets a plugin\ncontribute top-level sub-commands to a shared root command it does not own.",[127,1157,1159,1160],{"id":1158},"extending-a-registered-command-with-copy","Extending a registered command with ",[18,1161,1023],{},[14,1163,1164,1165,416,1167,1169,1170,1173,1174,1176,1177,1180],{},"Registration freezes a command, after which ",[18,1166,1123],{},[18,1168,1090],{},", and the\nother mutators throw ",[18,1171,1172],{},"IllegalStateException",". To add sub-commands to a command\ntree that is already registered, ",[18,1175,1023],{}," returns an ",[54,1178,1179],{},"unfrozen duplicate"," you\ncan mutate and re-register under the same name — registration replaces the\nprevious owner by name:",[63,1182,1185],{"className":1183,"code":1184,"language":68,"meta":69},[66],"\u002F\u002F First contributor\nMagicCommand first = manager.getCommand(\"root\").copy();\nfirst.mount(\"punish\", new BanCommand());\nmanager.register(first, first.resolveInfo());\n\n\u002F\u002F Second contributor — its copy already sees the first mount, and its own\n\u002F\u002F additions accumulate on top.\nMagicCommand second = manager.getCommand(\"root\").copy();\nsecond.mount(\"greet\", new GreetCommand());\nmanager.register(second, second.resolveInfo());\n\u002F\u002F \u002Froot now has both `punish` and `greet`; the root's own execute survives too.\n",[18,1186,1184],{"__ignoreMap":69},[14,1188,1189,1190,1192,1193,1196,1197,1199,1200,1202],{},"Because a copy preserves the source's mounted sub-commands, several plugins can\neach copy → mount → register in turn and their additions stack. The copy carries\nthe source's info, name\u002Falias overrides, mounted trees, and dynamic execute.\nSub-commands declared as ",[18,1191,308],{}," ",[54,1194,1195],{},"methods"," on a concrete subclass belong\nto that subclass, not to the copied declarative state; if such a subclass must\npreserve those methods across a copy, override ",[18,1198,1023],{}," to return an instance of\nits own type. Commands built from a spec\u002Fbuilder or assembled purely by ",[18,1201,1123],{},"\n(such as an aggregate root) copy losslessly with the default implementation.",[37,1204,1206],{"id":1205},"mutation-lifecycle","Mutation Lifecycle",[14,1208,1209,1211],{},[18,1210,971],{}," is mutable only during definition and composition:",[63,1213,1216],{"className":1214,"code":1215,"language":68,"meta":69},[66],"MagicCommand command = MagicCommand.\u003CCommandSender>builder(\"donate\")\n        .execute(ctx -> CommandResult.success(\"ok\"))\n        .build()\n        .addAlias(\"d\");\n",[18,1217,1215],{"__ignoreMap":69},[14,1219,1220,1221,353,1224,1227],{},"After ",[18,1222,1223],{},"registry.registerCommand(...)",[18,1225,1226],{},"commandManager.register(...)",", the\ncommand is frozen. Later calls to:",[224,1229,1230,1234,1238,1242,1247,1251],{},[95,1231,1232],{},[18,1233,988],{},[95,1235,1236],{},[18,1237,991],{},[95,1239,1240],{},[18,1241,994],{},[95,1243,1244],{},[18,1245,1246],{},"addSubCommand(...)",[95,1248,1249],{},[18,1250,1004],{},[95,1252,1253,110,1255],{},[18,1254,1154],{},[18,1256,1257],{},"mountSubCommands(...)",[14,1259,1260,1261,1263,1264,1266,1267,1273,1274,1277,1278,1280],{},"throw ",[18,1262,1172],{},". To extend a command after it is registered, take\na ",[18,1265,1023],{}," of it (see ",[26,1268,1270,1271],{"href":1269},"#extending-a-registered-command-with-copy","Extending a registered command with\n",[18,1272,1023],{},"), mutate the copy, and\nre-register it under the same name. If you still use ",[18,1275,1276],{},"registerSpec(...)",", it\nremains supported as a compatibility path and is internally converted into the\nsame ",[18,1279,971],{}," runtime model.",[14,1282,1283,1284,400],{},"For runtime diagnostics mounted the same way, see ",[26,1285,1287],{"href":1286},"diagnostics","Diagnostics",{"title":69,"searchDepth":1289,"depth":1289,"links":1290},3,[1291,1293,1294,1300,1301,1304,1305,1306,1307,1308,1312,1313,1314,1320],{"id":39,"depth":1292,"text":40},2,{"id":86,"depth":1292,"text":87},{"id":124,"depth":1292,"text":125,"children":1295},[1296,1297,1298,1299],{"id":129,"depth":1289,"text":130},{"id":151,"depth":1289,"text":152},{"id":171,"depth":1289,"text":172},{"id":191,"depth":1289,"text":192},{"id":207,"depth":1292,"text":208},{"id":297,"depth":1292,"text":298,"children":1302},[1303],{"id":338,"depth":1289,"text":339},{"id":482,"depth":1292,"text":483},{"id":33,"depth":1292,"text":34},{"id":758,"depth":1292,"text":759},{"id":855,"depth":1292,"text":856},{"id":890,"depth":1292,"text":891,"children":1309},[1310,1311],{"id":905,"depth":1289,"text":906},{"id":931,"depth":1289,"text":932},{"id":947,"depth":1292,"text":742},{"id":964,"depth":1292,"text":965},{"id":1036,"depth":1292,"text":1037,"children":1315},[1316,1318],{"id":1086,"depth":1289,"text":1317},"Flat-mounting sub-commands with mountSubCommands",{"id":1158,"depth":1289,"text":1319},"Extending a registered command with copy()",{"id":1205,"depth":1292,"text":1206},"Annotation-first command framework for MagicUtils with type parsers, options, permissions, and Brigadier support where the platform allows it.","md",{},null,true,"\u002Fmodules\u002Fcommands","---\ntitle: Commands\ndescription: Annotation-first command framework for MagicUtils with type parsers, options, permissions, and Brigadier support where the platform allows it.\n---\n\n# Commands\n\nMagicUtils commands are annotation-first, but the runtime model is registry\nbased. Each platform exposes a `CommandRegistry` that owns parsers,\npermissions, and command registration for that plugin or mod.\n\nSee [Commands Cheat Sheet](commands-cheatsheet.md) for a quick reference and\n[Permissions](permissions.md) for node generation details.\n\n## Why an annotation-first command framework\n\nRegistering a command by hand means parsing raw `String[] args` yourself,\nvalidating and converting each argument, writing tab-completion separately,\nchecking permissions, and repeating all of it in a different shape for every\nplatform's dispatcher. A single \"give a player an amount\" command turns into\ndozens of lines of boilerplate that has nothing to do with your feature.\n\nMagicUtils lets you describe the command as a method: parameter types drive\nparsing and completion, annotations add options and permissions, and the same\ncommand class registers on Bukkit, BungeeCord, Velocity, Fabric, and NeoForge.\n\n**Before** (raw Bukkit `onCommand`, manual parsing and checks):\n\n```java\npublic boolean onCommand(CommandSender s, Command c, String label, String[] args) {\n    if (!s.hasPermission(\"donate.give\")) { s.sendMessage(\"No permission\"); return true; }\n    if (args.length \u003C 2) { s.sendMessage(\"Usage: \u002Fdonate give \u003Cplayer> \u003Camount>\"); return true; }\n    Player target = Bukkit.getPlayer(args[0]);\n    if (target == null) { s.sendMessage(\"Unknown player\"); return true; }\n    int amount;\n    try { amount = Integer.parseInt(args[1]); } catch (NumberFormatException e) {\n        s.sendMessage(\"Amount must be a number\"); return true;\n    }\n    \u002F\u002F ... finally, the actual logic\n    return true;\n}\n```\n\n**After** (typed parameters, generated parsing\u002Fpermission\u002Fcompletion):\n\n```java\n@SubCommand(name = \"give\", permission = \"donate.give\")\npublic CommandResult give(\n        @Sender MagicSender sender,\n        @ParamName(\"player\") Player target,\n        int amount\n) {\n    \u002F\u002F just the logic; parsing, validation, and permission are handled\n    return CommandResult.success(\"Gave \" + amount + \" to \" + target.getName());\n}\n```\n\n## Registration Models\n\nThere are three common ways to obtain a registry:\n\n1. Bootstrap helper creates it for you.\n2. `CommandRegistry.create(...)` returns an instance you keep explicitly.\n3. Legacy `CommandRegistry.initialize(...)` \u002F `createDefault(...)` creates the\n   default registry for the current platform.\n\nFor multi-plugin or multi-mod setups, prefer an explicit registry instance or\nthe scoped static overloads. The no-arg `register(...)` methods operate on the\ndefault registry.\n\n## Platform Registration\n\n### Bukkit\u002FPaper\n\nBootstrap-first:\n\n```java\nBukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin)\n        .permissionPrefix(\"myplugin\")\n        .enableCommands()\n        .configureCommands(registry -> registry.registerCommand(new DonateCommand()))\n        .buildRuntime();\n```\n\nManual registry:\n\n```java\nCommandRegistry registry = CommandRegistry.create(plugin, \"myplugin\", logger);\nregistry.registerCommand(new DonateCommand());\nregistry.registerCommand(new AdminCommand());\n```\n\n### Fabric\n\nBootstrap-first:\n\n```java\nFabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod(\"mymod\", () -> server)\n        .permissionPrefix(\"mymod\")\n        .enableCommands()\n        .buildRuntime();\n\nCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {\n    if (magic.commandRegistry() != null) {\n        magic.commandRegistry().registerCommand(dispatcher, new DonateCommand());\n    }\n});\n```\n\nManual registry:\n\n```java\nCommandRegistry registry = CommandRegistry.create(\"mymod\", \"mymod\", logger, 2);\n\nCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {\n    registry.registerCommand(dispatcher, new DonateCommand());\n});\n```\n\n### Velocity\n\nBootstrap-first:\n\n```java\nVelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, plugin, \"MyPlugin\", dataDirectory)\n        .permissionPrefix(\"myplugin\")\n        .enableCommands()\n        .configureCommands(registry -> registry.registerCommand(new DonateCommand()))\n        .buildRuntime();\n```\n\nManual registry:\n\n```java\nCommandRegistry registry = CommandRegistry.create(proxy, plugin, \"myplugin\", loggerCore);\nregistry.registerCommand(new DonateCommand());\n```\n\n### NeoForge\n\nNeoForge currently uses the manual path:\n\n```java\nCommandRegistry registry = CommandRegistry.create(\"mymod\", \"mymod\", loggerCore, 2);\n\n@SubscribeEvent\npublic void onRegisterCommands(RegisterCommandsEvent event) {\n    registry.registerCommand(event.getDispatcher(), new DonateCommand());\n}\n```\n\nThe second argument is always the permission prefix used when generating nodes.\n\n## Brigadier Integration\n\nOn Brigadier platforms (Fabric and NeoForge) the `CommandRegistry` is backed by\n`BrigadierCommandRegistry\u003CS>` from `magicutils-commands-brigadier`. You rarely\ntouch it directly, but two hooks let you customise how MagicUtils maps arguments\nonto Brigadier:\n\n- **`parserRegistrar`** (a `Consumer\u003CTypeParserRegistry\u003CS>>`) registers your own\n  type parsers so annotation and builder commands can accept custom types.\n- **`brigadierRegistrar`** (a `Consumer\u003CBrigadierArgumentRegistry\u003CS>>`) registers\n  `BrigadierArgumentResolver\u003CS>` instances that map a `CommandArgument` onto a\n  native Brigadier `ArgumentType`, so a parameter can use real Brigadier parsing\n  and suggestions instead of MagicUtils' string-based parsing.\n\nA resolver returns a `BrigadierArgumentShape` (or `null` to skip):\n\n```java\nregistry.register(argument -> {\n    if (argument.getType() == GameProfile.class) {\n        \u002F\u002F use a native Brigadier argument type with its own suggestions\n        return BrigadierArgumentShape.nativeSuggestions(GameProfileArgument.gameProfile());\n    }\n    return null; \u002F\u002F fall back to MagicUtils parsing\n});\n```\n\n`BrigadierArgumentShape.of(type)` wraps a Brigadier `ArgumentType` without native\nsuggestions; `nativeSuggestions(type)` keeps Brigadier's own completion. Higher\n`priority()` resolvers run first. Most commands never need this: the built-in\ntype parsers already cover players, worlds, enums, numbers, and booleans.\n\n## Annotation-Based Commands\n\nUse `@CommandInfo` on the class and `@SubCommand` on methods. A method named\n`execute` without `@SubCommand` is treated as the root handler.\n\n```java\n@CommandInfo(\n        name = \"donate\",\n        description = \"DonateMenu main command\",\n        aliases = {\"d\"},\n        permission = \"donate.use\"\n)\npublic final class DonateCommand extends MagicCommand {\n\n    public CommandResult execute(@Sender MagicSender sender) {\n        return CommandResult.success(\"Opened menu\");\n    }\n\n    @SubCommand(name = \"give\", description = \"Give currency to a player\")\n    public CommandResult give(\n            @Sender MagicSender sender,\n            @ParamName(\"player\") Player target,\n            @Option(shortNames = {\"a\"}, longNames = {\"amount\"}) int amount,\n            @Option(shortNames = {\"s\"}, longNames = {\"silent\"}, flag = true) boolean silent\n    ) {\n        return CommandResult.success(silent ? \"\" : \"Done\");\n    }\n}\n```\n\nNested subcommands are supported via `path`:\n\n```java\n@SubCommand(path = {\"npc\", \"commands\"}, name = \"add\")\npublic CommandResult addNpcCommand(...) { ... }\n```\n\n### Common Annotations\n\n- `@ParamName` overrides argument names for help output.\n- `@OptionalArgument` or `@DefaultValue(\"...\")` marks a parameter optional.\n- `@Greedy` captures the rest of the input.\n- `@Suggest(\"source\")` adds completion hints.\n- `@Sender` injects the sender and hides it from help output.\n- `@Option(shortNames = {\"a\"}, longNames = {\"amount\"})` enables `-a 5` and\n  `--amount 5`. Set `flag = true` for toggles such as `-s` \u002F `--silent`.\n\n`@Sender` supports sender filtering via `AllowedSender`:\n\n- `ANY`, `PLAYER`, `CONSOLE`\n- `BLOCK`, `MINECART`, `PROXIED`, `REMOTE`\n\nPlatform-specific sender types can also be injected directly:\n\n- Bukkit: `CommandSender`, `Player`\n- Fabric: `ServerCommandSource`, `ServerPlayerEntity`\n- Velocity: `CommandSource`, `Player`, `ConsoleCommandSource`\n- NeoForge: `CommandSourceStack`, `ServerPlayer`\n\n## Suggestions And Type Parsers\n\nSuggestions can come from:\n\n- Built-in type parsers (players, worlds, enums, booleans).\n- Special sources such as `@players`, `@worlds`, `@commands`.\n- Inline lists: `@Suggest(\"{on,off,reset}\")`.\n- Methods on the command class: `@Suggest(\"getItems\")`.\n\nSuggestion methods can be:\n\n- `String[] getItems()` or `List\u003CString> getItems()`\n- `getItems(Player player)`\n- `getItems(ServerCommandSource sender)`\n- `getItems(CommandSource sender)`\n\n`@Suggest` has two extra members:\n\n- `permission = true` filters the suggestions through the argument's permission,\n  so a player only sees completions they are allowed to use.\n- `contextArgs = {\"world\"}` passes the current values of earlier arguments into\n  the suggestion method, so later completions can depend on what was already\n  typed.\n\n::callout{type=\"info\" title=\"Method arity with contextArgs\"}\nA no-argument suggestion method is tried\nfirst; only if none exists does the resolver call a method built from the\ncontext. When you use `contextArgs`, the method receives **each context value in\norder, followed by the current partial input** as a trailing `String`. So a\nsuggestion for an argument declared with `contextArgs = {\"server\"}` must take two\nparameters, not one:\n\n```java\n\u002F\u002F @Suggest(value = \"suggestWorlds\", contextArgs = {\"server\"})\npublic List\u003CString> suggestWorlds(String server, String currentInput) {\n    \u002F\u002F `server` is the already-typed value; `currentInput` is what the player\n    \u002F\u002F is typing now (use it to pre-filter, or ignore it and let the framework filter).\n    return worldsOf(server);\n}\n```\n\nA one-parameter `suggestWorlds(String server)` silently returns nothing here,\nbecause the resolver looks for a method matching `(server, currentInput)` and\nfinds no match. The optional sender\u002Fplayer parameter, when present, comes before\nthe context values.\n::\n\nBuilt-in sources:\n\n- `@players`, `@player`, `@allplayers`\n- `@offlineplayers` (Bukkit only)\n- `@worlds`, `@world`\n- `@language_keys` (Bukkit only)\n- `@commands`\n- `{a,b,c}` inline list syntax\n\nCustom parsers are registered on the registry's parser registry:\n\n```java\nregistry.commandManager()\n        .getTypeParserRegistry()\n        .register(new MyTypeParser());\n```\n\n## Permissions\n\nPermissions can be defined at three levels:\n\n- `@CommandInfo.permission`\n- `@SubCommand.permission`\n- `@Permission` on parameters\n\nGenerated nodes use this shape when you do not provide explicit values:\n\n- Command: `commands.\u003Ccommand>`\n- Subcommand: `commands.\u003Ccommand>.subcommand.\u003Cpath>`\n- Argument: `commands.\u003Ccommand>.subcommand.\u003Cpath>.argument.\u003Cname>`\n\nThese nodes are prefixed by the registry permission prefix. See\n[Permissions](permissions.md) for the platform-specific behaviour.\n\n`MagicPermissionDefault` controls the default access policy:\n\n- `TRUE`\n- `OP`\n- `NOT_OP`\n- `FALSE`\n\nFor manual checks outside annotation processing, use `MagicSender`:\n\n```java\nMagicSender sender = MagicSender.wrap(rawSender);\nif (MagicSender.hasPermission(rawSender, \"myplugin.admin\")) {\n    \u002F\u002F adapter-default fallback\n}\nif (sender != null && sender.hasPermission(\"myplugin.admin\", 4)) {\n    \u002F\u002F explicit fallback override for this check\n}\n```\n\nThe overload with `fallbackOpLevel` is mainly useful on Fabric and NeoForge,\nwhere adapters may fall back to command-source permission levels.\n\n## CommandResult\n\n`CommandResult` factories control whether MagicUtils sends feedback\nautomatically, and whether the logger prefix is attached.\n\nSuccess:\n\n- `CommandResult.success()` succeeds with no reply text.\n- `CommandResult.success(\"Done\")` sends a success reply (with prefix).\n- `CommandResult.success(\"Done\", false)` sends the reply without the prefix.\n- `CommandResult.success(false, \"Done\")` succeeds but suppresses the reply\n  (the boolean is `sendMessage`, so `false` means \"do not send\").\n\nFailure:\n\n- `CommandResult.failure(\"No permission\")` sends a failure reply (with prefix).\n- `CommandResult.failure(\"No permission\", false)` sends it without the prefix.\n- `CommandResult.failure(false)` fails silently, sending no reply.\n\nNot found:\n\n- `CommandResult.notFound()` returns the built-in \"command not found\" failure.\n\nNote the two two-argument success overloads differ by parameter order:\n`success(String, boolean)` is `(message, sendPrefix)`, while\n`success(boolean, String)` is `(sendMessage, message)`.\n\n## Threading\n\nCommands run on the main thread by default. Use `CommandThreading.ASYNC` for\nIO-heavy or CPU-heavy work:\n\n```java\n@CommandInfo(name = \"donate\", threading = CommandThreading.ASYNC)\npublic final class DonateCommand extends MagicCommand {\n    public CommandResult execute(@Sender MagicSender sender) {\n        return CommandResult.success(\"done\");\n    }\n\n    @SubCommand(name = \"give\", threading = CommandThreading.ASYNC)\n    public CommandResult give(@Sender MagicSender sender, Player target) {\n        return CommandResult.success(\"ok\");\n    }\n}\n```\n\nBuilder equivalents:\n\n```java\nMagicCommand.\u003CCommandSender>builder(\"donate\")\n        .threading(CommandThreading.ASYNC)\n        .execute(ctx -> CommandResult.success(\"done\"))\n        .build();\n\nSubCommandSpec.\u003CCommandSender>builder(\"give\")\n        .threading(CommandThreading.ASYNC)\n        .execute(ctx -> CommandResult.success(\"ok\"))\n        .build();\n```\n\nOnly mark commands as async when your code is thread-safe. When you need to\ntouch platform APIs again, switch back to the main thread via\n`Platform.runOnMain(...)` or `Tasks.runOnMain(...)`.\n\n## Help Output\n\nThe help renderer respects permissions and hides commands or arguments the\nsender cannot access. It is styled through `logger.{ext}` under the `help`\nsection.\n\n### Standalone Help Command\n\nBukkit and Fabric ship a ready-to-register `HelpCommand` wrapper:\n\n```java\nregistry.registerCommand(new HelpCommand(logger, registry));\n```\n\nYou can rename it at runtime:\n\n```java\nregistry.registerCommand(new HelpCommand(logger, registry)\n        .withName(\"donatehelp\")\n        .addAlias(\"dhelp\"));\n```\n\n### Help As A Subcommand\n\nUse `HelpCommandSupport` when you want help inside another command tree or on\nplatforms that do not ship a dedicated wrapper:\n\n```java\nregistry.registerCommand(new DonateCommand()\n        .addSubCommand(HelpCommandSupport.createHelpSubCommand(\n                \"help\",\n                loggerCore,\n                registry::commandManager\n        )));\n```\n\n## MagicSender\n\n`MagicSender` is the platform-neutral sender wrapper used throughout the\ncommand system:\n\n```java\nMagicSender sender = MagicSender.wrap(rawSender);\nif (MagicSender.hasPermission(rawSender, \"my.permission\")) {\n    \u002F\u002F ...\n}\n```\n\nUse it when you want shared command logic across Bukkit, BungeeCord, Velocity,\nFabric, and NeoForge without branching on raw sender types.\n\n## Builder API\n\nUse the builder API when you need runtime composition but still want a real\n`MagicCommand` instance:\n\n```java\nMagicCommand donateCommand = MagicCommand.\u003CCommandSender>builder(\"donate\")\n        .description(\"DonateMenu main command\")\n        .aliases(\"d\")\n        .execute(ctx -> CommandResult.success(\"Opened menu\"))\n        .subCommand(SubCommandSpec.\u003CCommandSender>builder(\"give\")\n                .description(\"Give currency\")\n                .argument(CommandArgument.builder(\"player\", Player.class).build())\n                .argument(CommandArgument.builder(\"amount\", Integer.class).build())\n                .execute(ctx -> CommandResult.success(\"ok\"))\n                .build())\n        .build();\n\nregistry.registerCommand(donateCommand);\n```\n\nYou can mix annotations with runtime overrides:\n\n- `withName(...)`, `addAlias(...)`, `removeAlias(...)`\n- `addSubCommand(SubCommandSpec\u003C?>)`\n- `setExecute(...)`\n- `mount(MagicCommand)` \u002F `mount(\"route\", existingCommand)`\n- `mountSubCommands(MagicCommand)` — graft a carrier's sub-commands in flat\n- `copy()` — an unfrozen duplicate you can mutate and re-register\n\nNested builder subcommands are supported as well:\n\n```java\nSubCommandSpec\u003CCommandSender> npcAdd = SubCommandSpec.\u003CCommandSender>builder(\"add\")\n        .path(\"npc\", \"commands\")\n        .description(\"Add NPC command\")\n        .execute(ctx -> CommandResult.success(\"ok\"))\n        .build();\n```\n\n## Composing Existing Commands\n\nAlready-authored annotation commands can be mounted under another command tree\nwithout rewriting them into `SubCommandSpec` form:\n\n```java\nMagicCommand adminCommand = MagicCommand.\u003CCommandSender>builder(\"admin\")\n        .mount(\"punish\", new BanCommand())\n        .build();\n\nregistry.registerCommand(adminCommand);\n```\n\nThis is intentionally different from `withName(\"punish\")`:\n\n- `withName(...)` mutates the authored command instance itself\n- `mount(\"punish\", command)` keeps the source command identity intact and only\n  changes the route segment inside the parent tree\n\nMounting snapshots the child command structure at mount time. That means:\n\n- changing the child name or aliases later does not rewrite the already-mounted\n  parent tree\n- the mounted command still executes against the original child instance\n- when you override the mounted route, root aliases from the child are not\n  exposed automatically\n\n### Flat-mounting sub-commands with `mountSubCommands`\n\n`mount(\"route\", cmd)` nests the source command under a route segment, producing\n`\u002Froot \u003Croute> \u003Csub>`. Sometimes you instead want another command's\nsub-commands to appear **directly** under the root, at the same level —\n`\u002Froot \u003Csub>` — without the source command's own name becoming a nesting level.\nThat is `mountSubCommands(cmd)`:\n\n```java\n\u002F\u002F GreetCommand is a carrier: an @CommandInfo(name = \"greet\") class whose only\n\u002F\u002F job is to hold @SubCommand methods (e.g. `hello`). mountSubCommands grafts\n\u002F\u002F those methods straight under `root`, so `\u002Froot hello` works — `greet` never\n\u002F\u002F appears as a command level.\nMagicCommand root = manager.getCommand(\"root\").copy();\nroot.mountSubCommands(new GreetCommand());\nmanager.register(root, root.resolveInfo());\n```\n\nThe difference from `mount`:\n\n- `mount(\"greet\", cmd)` → `\u002Froot greet hello`\n- `mountSubCommands(cmd)` → `\u002Froot hello`\n\nOnly the source's sub-commands are carried over. Its own bare `execute` (the\naction bound to the source's plain name) is intentionally **not** grafted — use\n`mount(...)` if you want the source's top-level action too. This lets a plugin\ncontribute top-level sub-commands to a shared root command it does not own.\n\n### Extending a registered command with `copy()`\n\nRegistration freezes a command, after which `mount`, `mountSubCommands`, and the\nother mutators throw `IllegalStateException`. To add sub-commands to a command\ntree that is already registered, `copy()` returns an **unfrozen duplicate** you\ncan mutate and re-register under the same name — registration replaces the\nprevious owner by name:\n\n```java\n\u002F\u002F First contributor\nMagicCommand first = manager.getCommand(\"root\").copy();\nfirst.mount(\"punish\", new BanCommand());\nmanager.register(first, first.resolveInfo());\n\n\u002F\u002F Second contributor — its copy already sees the first mount, and its own\n\u002F\u002F additions accumulate on top.\nMagicCommand second = manager.getCommand(\"root\").copy();\nsecond.mount(\"greet\", new GreetCommand());\nmanager.register(second, second.resolveInfo());\n\u002F\u002F \u002Froot now has both `punish` and `greet`; the root's own execute survives too.\n```\n\nBecause a copy preserves the source's mounted sub-commands, several plugins can\neach copy → mount → register in turn and their additions stack. The copy carries\nthe source's info, name\u002Falias overrides, mounted trees, and dynamic execute.\nSub-commands declared as `@SubCommand` **methods** on a concrete subclass belong\nto that subclass, not to the copied declarative state; if such a subclass must\npreserve those methods across a copy, override `copy()` to return an instance of\nits own type. Commands built from a spec\u002Fbuilder or assembled purely by `mount`\n(such as an aggregate root) copy losslessly with the default implementation.\n\n## Mutation Lifecycle\n\n`MagicCommand` is mutable only during definition and composition:\n\n```java\nMagicCommand command = MagicCommand.\u003CCommandSender>builder(\"donate\")\n        .execute(ctx -> CommandResult.success(\"ok\"))\n        .build()\n        .addAlias(\"d\");\n```\n\nAfter `registry.registerCommand(...)` or `commandManager.register(...)`, the\ncommand is frozen. Later calls to:\n\n- `withName(...)`\n- `addAlias(...)`\n- `removeAlias(...)`\n- `addSubCommand(...)`\n- `setExecute(...)`\n- `mount(...)` \u002F `mountSubCommands(...)`\n\nthrow `IllegalStateException`. To extend a command after it is registered, take\na `copy()` of it (see [Extending a registered command with\n`copy()`](#extending-a-registered-command-with-copy)), mutate the copy, and\nre-register it under the same name. If you still use `registerSpec(...)`, it\nremains supported as a compatibility path and is internally converted into the\nsame `MagicCommand` runtime model.\n\nFor runtime diagnostics mounted the same way, see [Diagnostics](diagnostics.md).\n",{"title":5,"description":1321},"modules\u002Fcommands","9zzGyreL5te2McG-z9Qde1zIPZyHo9voC5SFvHv-arU",1783944488004]