diff --git a/astro.config.ts b/astro.config.ts index f0eb400..7d15a89 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -111,6 +111,10 @@ export default defineConfig({ }, autogenerate: { directory: "configuration" }, }, + { + label: "Commands", + autogenerate: { directory: "commands" }, + }, { label: "Development", translations: { diff --git a/src/content/docs/commands/overview.md b/src/content/docs/commands/overview.md new file mode 100644 index 0000000..629d431 --- /dev/null +++ b/src/content/docs/commands/overview.md @@ -0,0 +1,42 @@ +--- +title: Command Reference +description: Conventions and detailed references for SteelMC commands. +sidebar: + order: 1 +--- + +This section documents SteelMC command families that need more than a short syntax list. Each reference explains what an operation changes, which permissions it requires, and how to use it safely. + +## Command Index + +- [`/perms`](../permissions) — inspect and manage player rules, groups, inheritance, defaults, and metadata + +Vanilla commands follow their normal Minecraft syntax. Until a command has Steel-specific behavior that needs its own reference, use the [Minecraft Wiki command list](https://minecraft.wiki/w/Commands#List_and_summary_of_commands). + +## Reading Command Syntax + +Command references use angle brackets for values you must provide: + +```text +/command +``` + +Do not type the brackets. For example, `/perms group info` becomes: + +```text +/perms group moderator info +``` + +Player target arguments can accept a player name or a compatible Minecraft target selector. Commands that use profile targets can also resolve known offline players. + +## Permissions + +Most commands derive a permission from their namespaced command ID: + +```text +.command. +``` + +Some command families publish more specific permissions for individual operations. The root permission grants those operations unless a more specific rule denies one. Console and RCON bypass player permission checks. + +For details about permission keys, groups, contexts, and conflict resolution, see [Permission configuration](../../configuration/permissions). diff --git a/src/content/docs/commands/permissions.md b/src/content/docs/commands/permissions.md new file mode 100644 index 0000000..a11a903 --- /dev/null +++ b/src/content/docs/commands/permissions.md @@ -0,0 +1,407 @@ +--- +title: Permission Commands +description: Inspect and manage SteelMC permissions, groups, inheritance, and metadata with /perms. +sidebar: + order: 2 +--- + +`/perms` manages permissions while the server is running. It has three scopes: + +```text +/perms user +/perms group +/perms groups +``` + +- `user` manages direct player overrides and group assignments. +- `group` manages one named group's rules, priority, inheritance, and metadata. +- `groups` lists groups and manages which groups every player receives by default. + +This reference assumes familiarity with permission keys, wildcards, groups, and contexts. Start with [Permission configuration](../../configuration/permissions) if those concepts are new to you. + +:::tip +Inspect a player or group before editing it. Direct rules, inherited rules, contextual rules, and group priority can make the effective result different from a single configuration entry. +::: + +## How Authorization Works + +Every operation has a granular command permission. For example, player inspection requires `steel.command.perms.user.info`. The root `steel.command.perms` permission grants every `/perms` operation unless a more specific operation is denied. + +Changing or displaying managed data may require additional authority: + +| Authority | Purpose | +| --- | --- | +| `steel.permission.manage.` | Inspect, check, or change rules for that permission key | +| `steel.permission.group.` | Inspect, assign, or change that group | +| `steel.permission.metadata` | Display, check, or change permission metadata | + +Wildcards can grant a range of authority. For example, `steel.permission.manage.minecraft.command.*` permits management of descendant keys such as `minecraft.command.give`, while `steel.permission.manage.*` permits management of every permission key. Similarly, `steel.permission.group.*` permits management of every group. + +Information commands filter their output to the sender's authority. For example, user info omits metadata without `steel.permission.metadata` and omits rules or groups the sender cannot manage. + +Console and RCON bypass these player permission checks. + +## Inspect Current Settings + +These are the best commands to run before making changes. + +### Inspect a player + +```text +/perms user info +``` + +Shows each target's assigned groups, direct permission rules, and direct metadata overrides. This reports stored player settings, not every inherited effective rule. + +Command permission: `steel.command.perms.user.info` + +The output is filtered by the sender's permission-management, group-management, and metadata authority. + +```text +/perms user Steve info +``` + +### Check a player's effective permission + +```text +/perms user check +``` + +Resolves the permission for each target, including default groups, assigned groups, direct overrides, inheritance, priority, and context. The result identifies the winning rule and its source, or reports that the permission is unset. + +Command permission: `steel.command.perms.user.check` + +Additional authority: `steel.permission.manage.` for the checked key. + +```text +/perms user Steve check minecraft.command.gamemode.creative +/perms user Steve check minecraft.command.gamemode{domain=lobby} +``` + +### Inspect a group + +```text +/perms group info +``` + +Shows the group's priority, parent groups, allow rules, deny rules, and metadata. Entries outside the sender's management authority are omitted. + +Command permission: `steel.command.perms.group.info` + +Additional authority: `steel.permission.group.`. Metadata also requires `steel.permission.metadata`; individual permission rules require matching `steel.permission.manage.` authority. + +```text +/perms group moderator info +``` + +### List groups + +```text +/perms groups list +``` + +Lists the groups the sender has authority to manage and indicates which are default groups. + +Command permission: `steel.command.perms.groups.list` + +Only groups covered by the sender's `steel.permission.group.` authority are shown. + +## Manage Player Permission Rules + +A permission expression is a permission key with an optional context selector: + +```text +{=,...} +``` + +See [Contextual rules](../../configuration/permissions#contextual-rules) for the complete syntax and resolution behavior. + +### Allow a permission + +```text +/perms user allow +``` + +Adds or replaces the exact direct rule with an allow. Other contexts for the same permission remain unchanged. + +Command permission: `steel.command.perms.user.allow` + +Additional authority: `steel.permission.manage.`. + +```text +/perms user Steve allow minecraft.command.teleport +/perms user Steve allow minecraft.command.gamemode{domain=lobby} +``` + +### Deny a permission + +```text +/perms user deny +``` + +Adds or replaces the exact direct rule with a deny. A specific deny can override a broader allow. + +Command permission: `steel.command.perms.user.deny` + +Additional authority: `steel.permission.manage.`. + +```text +/perms user Steve deny minecraft.command.gamemode.creative +``` + +### Remove a direct rule + +```text +/perms user unset +``` + +Removes only the direct rule with the same permission key and exact context. Rules inherited from groups are not removed. + +Command permission: `steel.command.perms.user.unset` + +Additional authority: `steel.permission.manage.`. + +```text +/perms user Steve unset minecraft.command.gamemode{domain=lobby} +``` + +## Assign Player Groups + +### Add a group + +```text +/perms user group add +``` + +Assigns the group to each target. Assigning an already present group makes no change. + +Command permission: `steel.command.perms.user.group.add` + +Additional authority: `steel.permission.group.`. + +```text +/perms user Steve group add moderator +``` + +### Remove a group + +```text +/perms user group remove +``` + +Removes the assigned group from each target. This does not remove a group received through `default_groups` or inheritance. + +Command permission: `steel.command.perms.user.group.remove` + +Additional authority: `steel.permission.group.`. + +```text +/perms user Steve group remove moderator +``` + +## Manage Groups + +### Create or delete a group + +```text +/perms group create +/perms group delete +``` + +`create` adds an empty group. `delete` removes the group from the group configuration. Steel rejects deletion if the group is `op`, is still a default, or is inherited by another group; remove those references first. + +Command permissions: `steel.command.perms.group.create` and `steel.command.perms.group.delete`. + +Additional authority: `steel.permission.group.`. + +```text +/perms group moderator create +/perms group retired-staff delete +``` + +### Allow, deny, or unset a group rule + +```text +/perms group allow +/perms group deny +/perms group unset +``` + +`allow` and `deny` add or replace the exact group rule. `unset` removes only the rule with the same permission key and exact context. + +Command permissions: + +- `steel.command.perms.group.allow` +- `steel.command.perms.group.deny` +- `steel.command.perms.group.unset` + +Additional authority: both `steel.permission.group.` and `steel.permission.manage.`. + +```text +/perms group moderator allow minecraft.command.teleport +/perms group moderator deny minecraft.command.stop +/perms group moderator unset minecraft.command.teleport +``` + +### Change group priority + +```text +/perms group priority +``` + +Sets the signed 32-bit priority used when equally specific rules from different groups conflict. Higher priority wins; specificity still takes precedence over priority. + +Command permission: `steel.command.perms.group.priority` + +Additional authority: `steel.permission.group.`. + +```text +/perms group moderator priority 10 +``` + +## Manage Group Inheritance + +### List parent groups + +```text +/perms group inherit list +``` + +Lists the group's direct parents. Parent entries outside the sender's group-management authority are omitted. + +Command permission: `steel.command.perms.group.inherit.list` + +Additional authority: `steel.permission.group.`. + +```text +/perms group moderator inherit list +``` + +### Add or remove a parent + +```text +/perms group inherit add +/perms group inherit remove +``` + +Adds or removes a direct inheritance relationship. Adding a relationship that would create a cycle is rejected. Inherited rules keep the priority of the group where they were defined. + +Command permissions: `steel.command.perms.group.inherit.add` and `steel.command.perms.group.inherit.remove`. + +Additional authority: `steel.permission.group.` and `steel.permission.group.`. + +```text +/perms group moderator inherit add helper +/perms group moderator inherit remove helper +``` + +## Manage Metadata + +Metadata expressions use a namespaced key and the same optional context syntax as permission expressions: + +```text +:{=,...} +``` + +Values are explicitly typed as `int`, `bool`, or `string`. See [Metadata](../../configuration/permissions#metadata) for resolution rules. + +Quote string values that contain spaces: + +```text +/perms user Steve metadata set string "Senior Builder" plugin:rank +``` + +### Set player metadata + +```text +/perms user metadata set int +/perms user metadata set bool +/perms user metadata set string +``` + +Sets a direct metadata override for each target. It replaces only the value with the same metadata key and exact context. + +Command permission: `steel.command.perms.user.metadata.set` + +Additional authority: `steel.permission.metadata`. + +```text +/perms user Steve metadata set int 5 plugin:max_homes +/perms user Steve metadata set string gold plugin:chat/color{domain=lobby} +``` + +### Check player metadata + +```text +/perms user metadata check +``` + +Resolves the effective value for each target at the requested context and reports the winning source. This includes group metadata and direct overrides. + +Command permission: `steel.command.perms.user.metadata.check` + +Additional authority: `steel.permission.metadata`. + +```text +/perms user Steve metadata check plugin:max_homes{world=survival:overworld} +``` + +### Remove player metadata + +```text +/perms user metadata unset +``` + +Removes only the direct metadata entry with the same key and exact context. Inherited group metadata remains unchanged. + +Command permission: `steel.command.perms.user.metadata.unset` + +Additional authority: `steel.permission.metadata`. + +```text +/perms user Steve metadata unset plugin:max_homes +``` + +### Set or remove group metadata + +```text +/perms group metadata set int +/perms group metadata set bool +/perms group metadata set string +/perms group metadata unset +``` + +Sets or removes an exact metadata entry on the group. Use `/perms group info` to inspect configured group metadata; there is no separate group metadata `check` operation. + +Command permissions: `steel.command.perms.group.metadata.set` and `steel.command.perms.group.metadata.unset`. + +Additional authority: `steel.permission.group.` and `steel.permission.metadata`. + +```text +/perms group vip metadata set int 10 plugin:max_homes +/perms group vip metadata unset plugin:max_homes +``` + +## Manage Default Groups + +```text +/perms groups default add +/perms groups default remove +``` + +Adds or removes a group from `default_groups`. Default groups contribute to every player's effective permissions and metadata. Removing a default does not delete the group. + +Command permissions: `steel.command.perms.groups.default.add` and `steel.command.perms.groups.default.remove`. + +Additional authority: `steel.permission.group.`. + +```text +/perms groups default add member +/perms groups default remove member +``` + +## Persistence and Offline Players + +Group operations update `config/groups.toml`. Player operations update `/global/player_permissions.toml` and can resolve online players, known offline players, or profile names available through the server's profile lookup. + +These operations run without blocking the server tick. Command execution remains suspended until an operation finishes, then the sender receives its result and feedback. diff --git a/src/content/docs/configuration/overview.md b/src/content/docs/configuration/overview.md index a8c369a..dcb5a80 100644 --- a/src/content/docs/configuration/overview.md +++ b/src/content/docs/configuration/overview.md @@ -12,6 +12,7 @@ Configure your SteelMC server with custom settings and features. ## Available Guides - [**Server Links**](../server-links) - Configure server links displayed in the multiplayer menu +- [**Permission Configuration**](../permissions) - Configure groups, command permissions, operators and runtime permission edits ## Reference Documentation diff --git a/src/content/docs/configuration/permissions.md b/src/content/docs/configuration/permissions.md new file mode 100644 index 0000000..2e7eeb7 --- /dev/null +++ b/src/content/docs/configuration/permissions.md @@ -0,0 +1,322 @@ +--- +title: Permission Configuration +description: Configure command permissions, groups, player overrides, and permission metadata in SteelMC. +sidebar: + order: 4 +--- + +SteelMC permissions are configured through `config/groups.toml` and can also be edited at runtime with `/perms`. + +Permissions control command access, command suggestions, and the command tree sent to each client. The same system also supports contextual rules and typed metadata values for server features and plugins. + +## Files + +Steel uses two permission files: + +| File | Purpose | +| ---- | ------- | +| `config/groups.toml` | Server-wide permission groups and default groups | +| `/global/player_permissions.toml` | Per-player assigned groups, direct overrides, and metadata | + +Edit `groups.toml` for normal server policy. The player permissions file is under the configured world `save_path`, which defaults to `saves`. It is managed by Steel and `/perms`; manual edits should only be needed for recovery or bulk migration while the server is offline. + +## Default Groups + +A new server has two groups: `default` and `op`. Every player receives `default` because it is listed in `default_groups`. The required `op` group grants `*` and is assigned by `/op`. + +Add server roles as tables named `[groups.]`. Each group can contain permission rules, metadata, and parent groups to inherit from. A [complete example](#complete-example) later on this page shows how these pieces fit together. + +:::caution +Do not put `op` in `default_groups` unless every player should have every permission. +::: + +## Permission Keys + +Permission keys are dotted lowercase strings: + +```text +minecraft.command.give +steel.command.fly +plugin.region.build +``` + +:::caution +Permission key syntax is strict. Segments can contain only lowercase ASCII letters, digits, `_`, and `-`. Empty segments and uppercase letters are invalid. +::: + +Wildcards are allowed only as the final segment: + +| Key | Meaning | +| --- | ------- | +| `*` | Matches every permission | +| `minecraft.command.*` | Matches descendants such as `minecraft.command.give` | +| `minecraft.command.give` | Matches only that exact key | + +`minecraft.command.*` matches `minecraft.command.give`, but it does not match `minecraft.command` itself. + +:::note +A wildcard matches descendants only. Grant both `minecraft.command` and `minecraft.command.*` if a system defines meaningful permissions at both levels. +::: + +## Command Permissions + +Most built-in commands automatically use this permission shape: + +```text +.command. +``` + +Examples: + +| Command | Permission | +| ------- | ---------- | +| `/give` | `minecraft.command.give` | +| `/fly` | `steel.command.fly` | +| `/tp` and `/teleport` | `minecraft.command.teleport` | +| `/xp` and `/experience` | `minecraft.command.experience` | +| `/perms` | `steel.command.perms` | + +Default-access commands, such as `/list`, work when their permission is unset but can still be disabled with an explicit deny. + +Some commands expose narrower subcommand or value permissions. For example, `/tick freeze` can be granted with either `minecraft.command.tick` or `minecraft.command.tick.freeze`. + +`/gamemode` has value-specific permissions: + +| Action | Permission | +| ------ | ---------- | +| Any gamemode change | `minecraft.command.gamemode` | +| Survival only | `minecraft.command.gamemode.survival` | +| Creative only | `minecraft.command.gamemode.creative` | +| Adventure only | `minecraft.command.gamemode.adventure` | +| Spectator only | `minecraft.command.gamemode.spectator` | + +A broader permission grants the child action, but a more specific deny can override it. For example, allow `minecraft.command.gamemode` and deny `minecraft.command.gamemode.creative` to allow all gamemode changes except creative. + +## Group Configuration + +Groups are tables under `[groups.]`. + +```toml +[groups.moderator] +priority = 10 +inherits = [] +allow = [ + "minecraft.command.teleport", + "minecraft.command.gamemode.spectator", +] +deny = [ + "minecraft.command.stop", +] +metadata = [] +``` + +Group names must be valid permission segments: lowercase letters, digits, `_`, and `-`. + +### Group Inheritance + +Groups can inherit the permissions and metadata of other groups: + +```toml +[groups.helper] +priority = 5 +inherits = [] +allow = ["minecraft.command.teleport"] +deny = [] +metadata = [] + +[groups.moderator] +priority = 10 +inherits = ["helper"] +allow = ["minecraft.command.gamemode.spectator"] +deny = [] +metadata = [] +``` + +Members of `moderator` receive both permissions. The inherited teleport rule keeps the `helper` priority of `5`; it does not take on the `moderator` priority of `10`. + +Inheritance is transitive, and each inherited group contributes at most once. Cycles and references to missing groups make the configuration invalid. + +### Contextual Rules + +Permission and metadata rules can include context selectors: + +```toml +[groups.builder] +priority = 5 +inherits = [] +allow = [ + "plugin.region.build{world=lobby:spawn,plugin:region=market}", + "minecraft.command.gamemode{domain=lobby}", +] +deny = [ + "minecraft.command.gamemode.creative{world=lobby:spawn}", +] +metadata = [] +``` + +Built-in context keys: + +| Context | Example | Meaning | +| ------- | ------- | ------- | +| `domain` | `domain=lobby` | Applies only inside one [domain](../../reference/terminology#domain) | +| `world` | `world=lobby:spawn` | Applies only inside one loaded [world](../../reference/terminology#world) | + +Custom contexts can be provided by plugins or server subsystems. Unqualified keys such as `region=spawn` are valid, but namespaced keys such as `plugin:region=spawn` are preferred for plugin-owned contexts. + +Multiple selectors are combined with AND: + +```text +plugin.region.build{world=lobby:spawn,plugin:region=market} +``` + +:::caution +Context syntax is strict. Each key can appear only once in an expression. + +`world=lobby:spawn` already includes the `lobby` domain and therefore matches rules scoped to `domain=lobby`. Writing both is unnecessary. If both are present, they must agree; `{domain=survival,world=lobby:spawn}` is invalid. + +Values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. Domain names use Minecraft identifier-namespace syntax, and worlds must be written as `:`. +::: + +Context specificity is additive. Global rules are least specific, a domain or custom selector adds one level, and a world adds two because it identifies both a domain and a loaded world. A rule with several matching selectors is therefore more specific than a rule with only one of them. + +## Metadata + +Metadata is typed data resolved by the same group and context model as permissions. Values can be booleans, integers, or strings. + +```toml +[groups.vip] +priority = 20 +inherits = [] +allow = [] +deny = [] +metadata = [ + { key = "plugin:homes", value = 10 }, + { key = "plugin:homes{domain=lobby}", value = 3 }, + { key = "plugin:chat/color", value = "gold" }, + { key = "plugin:can_fly", value = true }, +] +``` + +Metadata keys must be namespaced identifiers such as `plugin:homes` or `plugin:chat/color`. The system that owns a key decides what its value means: for example, a homes plugin could interpret these rules as a limit of 10 homes normally and 3 inside the `lobby` domain. Steel stores and resolves the values but does not implement homes, chat colors, or flight merely because these example keys exist. + +Metadata resolution considers only entries with the requested exact metadata key. Among matching entries, Steel chooses: + +1. The most specific context. +2. A direct player value over a group value when specificity ties. +3. The higher group priority when group values tie. +4. The last entry in the effective metadata set when everything else ties. + +Unlike permissions, metadata has no allow/deny state and metadata keys do not use wildcard matching. Setting a value replaces only the entry with the same metadata key and exact context; unsetting it likewise removes only that exact entry. A global value and a contextual value for the same key can coexist. + +## Conflict Resolution + +Unset permissions are denied. When multiple rules match, Steel chooses the winning rule in this order: + +1. More specific permission key wins. +2. More specific context wins. +3. Direct player overrides beat group rules when specificity ties. +4. Higher group priority wins when group rules tie. +5. Deny wins when everything else ties. + +This means a specific deny can override a broad allow: + +```toml +[groups.staff] +priority = 10 +inherits = [] +allow = ["minecraft.command.*"] +deny = ["minecraft.command.stop"] +metadata = [] +``` + +The player can use most Minecraft commands, but not `/stop`. + +## Complete Example + +This configuration provides a practical starting point for a small server: + +```toml +default_groups = ["default"] + +[groups.default] +priority = 0 +inherits = [] +allow = [] +deny = [] +metadata = [] + +[groups.vip] +priority = 5 +inherits = [] +allow = [] +deny = [] +metadata = [ + { key = "example_homes:max", value = 5 }, + { key = "example_chat:color", value = "gold" }, +] + +[groups.moderator] +priority = 10 +inherits = [] +allow = [ + "minecraft.command.teleport", + "minecraft.command.gamemode.spectator", + "minecraft.command.tick.freeze", +] +deny = [] +metadata = [] + +[groups.op] +priority = 0 +inherits = [] +allow = ["*"] +deny = [] +metadata = [] +``` + +- Everyone receives `default`. +- Assign `vip` to players who should receive plugin-provided benefits. +- Assign `moderator` to staff who need the listed commands. +- `/op` assigns the required `op` group, which grants every permission by default. + +The `example_homes:max` and `example_chat:color` entries are illustrative. They have an effect only when plugins or server subsystems that own those keys read them. Replace them with metadata supported by the plugins you actually install. + +## Runtime Commands + +Use `/perms` to inspect and edit permissions while the server is running. Its general shape is: + +```text +/perms ... +``` + +Start by inspecting the current configuration: + +```text +/perms user info +/perms group info +/perms groups list +``` + +These commands show direct player settings, group rules and inheritance, and the available groups before you make changes. See [Permission commands](../../commands/permissions) for every operation, required permission, and example. + +## Operators + +`/op ` and `/deop ` are backed by the permission system: + +- `/op` adds the `op` group. +- `/deop` removes the `op` group. +- The default `op` group grants `*`. +- The `op` group is required and cannot be deleted with `/perms`. + +Targets can be online players, known offline players, or profile names the server can resolve. + +## Client Command Tree + +Players only receive command nodes they are allowed to use. This affects: + +- visible commands in the client command tree +- tab completion +- server-side command suggestions +- the client gamemode switcher + +Console and RCON bypass permission checks. diff --git a/src/content/docs/development/commands.md b/src/content/docs/development/commands.md new file mode 100644 index 0000000..c0e36e6 --- /dev/null +++ b/src/content/docs/development/commands.md @@ -0,0 +1,181 @@ +--- +title: Command Registration +description: How SteelMC commands are built, registered, and connected to permissions. +--- + +SteelMC uses a Brigadier-style graph of literal and argument nodes. A separate `CommandRegistration` gives each graph a stable namespaced identity, aliases, and its permission policy. + +## Command Modules + +Built-in commands live in `steel-core/src/command/builtins/`. Each module exposes a registration function and builds its command graph separately: + +```rust +use steel_utils::Identifier; + +use super::super::{ + brigadier::{CommandNodeBuilder, CommandSyntaxError}, + execution::{ + CommandSource, SteelCommandContext, SteelCommandRuntime, literal, + }, + registration::CommandRegistration, +}; + +pub(super) fn registration() -> CommandRegistration { + CommandRegistration::new(Identifier::vanilla_static("example"), |_| command()) +} + +fn command() -> CommandNodeBuilder { + literal("example").executes(run) +} + +fn run( + context: &SteelCommandContext, +) -> Result { + context.source().send_success(&"Example ran".into()); + Ok(1) +} +``` + +Add the module and its `registration()` call to `steel-core/src/command/builtins/mod.rs`. Registration is explicit; the build script no longer discovers command modules. + +The registration ID is authoritative. Its path must equal the root literal (`minecraft:example` has the root `example`), and its namespace owns the default permission. + +## Building a Command Graph + +Use `literal(...)` for fixed words and `argument(...)` for parsed values: + +```rust +fn command() -> CommandNodeBuilder { + literal("example") + .then(literal("reload").executes(reload)) + .then( + literal("inspect").then( + argument("target", SteelArgumentType::players()) + .executes(inspect), + ), + ) +} +``` + +The most common builder methods are: + +| Method | Purpose | +| --- | --- | +| `.then(child)` | Adds a child node | +| `.executes(handler)` | Adds a synchronous executor | +| `.executes_suspended(handler)` | Adds an executor that can finish asynchronously | +| `.requires(requirement)` | Replaces the node requirement | +| `.also_requires(requirement)` | Combines another requirement with the existing one | +| `.redirects(target)` | Redirects parsing without changing the source | +| `.redirects_with_modifier(...)` | Redirects with a source transformation or fork | + +Executors receive `&SteelCommandContext` and return `Result`. The integer is the command result used by features such as `/execute store` and return-value propagation. + +## Arguments + +Built-in Minecraft-aware parsers are exposed through `SteelArgumentType`; primitive Brigadier parsers use `ArgumentType`: + +```rust +literal("rate").then( + argument("rate", ArgumentType::float(1.0, 10_000.0)) + .executes(set_rate), +) +``` + +Read parsed values through typed `SteelCommandContext` helpers: + +```rust +fn inspect( + context: &SteelCommandContext, +) -> Result { + let targets = context.players("target")?; + i32::try_from(targets.len()) + .map_err(|_| CommandSyntaxError::dynamic("Too many targets")) +} +``` + +Use the same argument name in the graph and accessor. Some accessors return `Option`; convert a missing value into `CommandSyntaxError` instead of assuming it exists. + +Argument types provide server parsing, client parser metadata, and suggestions. Steel-owned keyed argument payloads keep custom argument values extensible without relying on Rust `TypeId` identity. + +## Automatic Root Permissions + +Unless overridden, a registration derives this permission from its ID: + +```text +.command. +``` + +For example, `minecraft:give` derives `minecraft.command.give`, while `steel:fly` derives `steel.command.fly`. The root requirement controls parsing, the client command tree, and suggestions. + +Unset permissions are normally denied. For a generally available command, use `.default_access()`: + +```rust +pub(super) fn registration() -> CommandRegistration { + CommandRegistration::new(Identifier::vanilla_static("list"), |_| command()) + .default_access() +} +``` + +Default access allows an unset permission but still respects an explicit deny. This differs from bypassing permission checks. + +Use `.permission(expression)` when a command needs a custom or compound root policy: + +```rust +let permission = PermissionExpr::key(PermissionKey::parse("plugin.command.inspect")?); +CommandRegistration::new(Identifier::new_static("plugin", "inspect"), |_| command()) + .permission(permission) +``` + +An explicit permission expression cannot be combined with derived subcommand permissions on the same registration. + +## Aliases and Collisions + +The ID path is the canonical root. Add unqualified aliases with `.alias(...)`: + +```rust +CommandRegistration::new(Identifier::vanilla_static("teleport"), |_| command()) + .alias("tp") +``` + +Both `/teleport` and `/tp` use `minecraft.command.teleport`; aliases do not create new permission keys. + +Command IDs must be unique. Earlier registrations win an unqualified root or alias collision. If a command has a collision, Steel also registers its namespaced ID (for example, `/minecraft:teleport`) as a fallback. Namespaced aliases are reserved for these collision fallbacks and cannot be declared manually. + +## Subcommand Permissions + +Declare independently grantable literal paths on the registration: + +```rust +CommandRegistration::new(Identifier::vanilla_static("tick"), |_| command()) + .subcommand_permission(["rate"]) + .subcommand_permission(["step"]) + .subcommand_permission(["freeze"]) +``` + +This publishes `minecraft.command.tick.rate`, `minecraft.command.tick.step`, and `minecraft.command.tick.freeze`. A user may hold either the root permission or the relevant child permission. A specific child deny can override a broad root allow because each node uses a scoped permission expression. + +Paths contain literal names only. Registration walks through argument nodes while matching them, so a declaration such as `["user", "info"]` can match `/perms user info`. Missing, duplicate, ambiguous, empty, or invalid paths fail dispatcher construction. + +Dynamic value permissions are expressed directly when building the registration policy and checked by the executor. `/gamemode` is the built-in example: its visible root policy is an `Any` expression over scoped permissions such as `minecraft.command.gamemode.creative`, and execution checks the selected mode again. + +## Permission Expressions + +`PermissionExpr` composes command authorization: + +| Expression | Meaning | +| --- | --- | +| `PermissionExpr::key(key)` | Checks one key | +| `PermissionExpr::scoped_key(parent, key)` | Lets the parent grant a child while preserving a more-specific child rule | +| `left & right` | Requires every expression | +| `left \| right` | Requires at least one expression | + +Configuration and `/perms` rule arguments are not compound command expressions. They contain one permission key and an optional context selector, such as `minecraft.command.gamemode{domain=lobby}`. + +## Validation and Discovery + +`CommandDispatcherBuilder` validates and constructs the complete dispatcher atomically. It rejects invalid IDs and aliases, duplicate IDs, mismatched or non-literal roots, invalid subcommand paths, and invalid explicit permission keys. A failed build does not expose a partially registered dispatcher. + +The builder also collects root and subcommand permission keys for `/perms` suggestions. Register non-command permissions explicitly with `declare_permission(...)` when they should be discoverable. + +The filtered graph is used consistently for parsing, the client command tree, and server suggestions. Nodes whose authorization requirements fail are omitted. Permission context comes from the command source, including its world and domain; console and RCON sources bypass player permission rules.