Saxonberg Server API
    Preparing search index...

    CommandApi - Static command definition cache

    Production dispatch never queries the cache directly; it walks each giver's recency stack (CommandGiverMixin.getAffordances()) and filters by verb, keeping each match paired with its affording source. The filename-keyed map is just a 'load once, reuse' sharing layer between the recency-push helpers and the YAML preload pass.

    This Api is a thin, security-gated forwarding shell: the logic lives in the hot-reloadable CommandLogic singleton at /obj/api/command, reached synchronously via StuffApi.singletonSync. dest /obj/api/command reloads it.

    Index

    Constructors

    Methods

    • Every loaded CommandDefinition — the whole filename-keyed cache, not the per-giver affordance set. Populated by preloadAll at boot (which runs before HelpCatalogue warms), so the help index's commands projector sees the complete verb roster. Returns a snapshot array; ordering follows insertion.

      Returns CommandDefinition[]

    • Apply the cardinality / onExcess / onShortage policy to a resolved MQL candidate list.

      Decision matrix:

      type=object (implicit { exactly: 1 }): 0 matches → pass through (resolveModel lands stuff=null) 1 match → pass through >1 match → onExcess decides: 'top' → first match wins (default) 'error' → controller-rejected:ambiguous, null 'prompt' → await PromptApi.mqlObject; player picks; cancel propagates as PromptCancelledError (caught in CommandGiver). No Interactive on the context degrades to ambiguous.

      type=objects (default { min: undefined, max: undefined }): under min → onShortage='error', controller-rejected:insufficient over max → onExcess decides: 'truncate' → cut to max 'error' → controller-rejected:too-many, null 'prompt' → await PromptApi.mqlMany; player picks within bounds; cancel propagates as PromptCancelledError. in-range → pass through

      The async prompt paths only fire when an Interactive is attached to the context (Avatar dispatch). Programmatic / scripted dispatch paths fall back to the degrade-to-error branch, since there's nobody to ask.

      Returns the filtered stuff list, or null when the policy dictates failure (an error note has been added to the context). Throws PromptCancelledError when the player cancels — the caller (CommandGiver) catches and emits a cancelled-shape controller-rejected note.

      Parameters

      Returns Promise<Stuff[] | null>

    • Recency-stack delta for a successful containment move. Source- side pops, dest-side pushes; if the moving item is itself a CommandGiver, its env+peers slice is rebuilt from the new neighborhood.

      Called by ContainmentApi.move after setContainer succeeds, before notification hooks fire.

      Parameters

      Returns void

    • Recency-stack delta for a hosted-update host/unhost (the aether hosting relation). A hosted update contributes its self-bucket command definitions to its host's stack with the update Stuff as the recency source, so getAffordances() resolves commandSource to the update (the "verb dispatch routes through the augment/update" pattern). Hosting surfaces the verbs; unhosting retires them — gain/lose-post-spawn live.

      Called by AetherMixin.hostUpdate / unhostUpdate.

      Parameters

      Returns void

    • Apply a bar's input-mode prefix to a raw command line — the load-bearing pre-tokenize step on the command-entry hot path (server-authoritative input mode, per-bar).

      Pure and total: given the raw text and the resolved prefix for the submitting bar, return the text the interpreter should actually dispatch. Three rules, in order:

      1. No prefix (the bar is unset) → verbatim no-op.
      2. /-escape → strip the leading slash and run the rest raw (a one-off un-moded command; /look lexes as look).
      3. mode-management → the mode verb itself is exempt, so mode off / mode chat x always reach the interpreter un-prefixed regardless of the active mode.

      Otherwise the prefix is prepended: chat-mode + hellochat hello. Kept here (not in msh) so the tokenizer stays Stuff-unaware; the per-bar lookup happens at the call site.

      Parameters

      • rawText: string
      • modePrefix: string

      Returns string

    • Recency-stack delta for a shadow attach/detach. A shadow whose class declares commandContributions lands on the host's stack (and on reachable peers' stacks per bucket) on attach; detach pops the shadow from every giver.

      Called by ShadowApi.attach and ShadowApi.detach around the atomic install/remove.

      Parameters

      Returns void

    • Bind a ParsedCommand to a CommandDefinition.

      Two-tier option scope: tokens before the subcommand are bound against the verb-scoped options; tokens after are bound against the active subcommand's options. Positional fields fill the fields: block in insertion order. A greedy: true field grabs the slice of parsed.source from the first unconsumed positional through end-of-input, preserving whitespace and quotes-as-literal (per spec §2.4).

      Returns { error: 'shape', summary } for cases the chain should fall through on (pattern doesn't fit, unknown subcommand, leftover positionals, missing required positional). Returns { error: 'bind', summary } for cases that stop the chain (unknown option, malformed option value, repeated non-multi option, boolean given a value, value-bearing option missing its value).

      Parameters

      Returns AssembleResult

    • Build a CommandModel from a structured-form payload (widget input). The structured path skips parse/match: the client has already chosen verb/subcommand and field keys. The matcher still validates field-name legality and runs type coercion; type: object fields go through MQL in resolveAndValidate just like the text path.

      Parameters

      Returns { model: CommandModel } | { error: string }

    • Clear the filename cache. Used by tests; production should prefer invalidate(filename) to drop a single entry when a YAML changes on disk.

      Returns void

    • Collect each hosted update's self-bucket command definitions for a host, paired with the update as the affording source. Used by CommandGiverMixin's self-seeding (both postRegister and the lazy _ensureSelfEntry safety net) so a host that gained updates outside a delta (e.g. a test that hosts then reads affordances) still surfaces their verbs. Returns [] for a non-host.

      Parameters

      Returns { defs: CommandDefinition[]; source: Stuff }[]

    • Collect the self-bucket command contributions from a class chain. The concrete class wins over its mixins; mixins later in the prototype chain (closer to Object) lose to earlier ones. Used at host registration to seed the 'self' recency entry.

      Parameters

      • ctor: unknown

      Returns CommandDefinition[]

    • Emit a system.commands.{added,removed,reset} frame to a recipient. Stamps commandId / causingCommandId from the ambient ExecutionContext when present (so a recency-stack mutation triggered inside a command is auto-attributed). Skips silently when the recipient isn't a Sensor — schema delivery is best-effort.

      Parameters

      Returns void

    • Programmatic command invocation — fire text on giver exactly as if the player had typed it, but stamp forced: true on the resulting Command frame so hooks can tell the two apart.

      Used by:

      • The auto-look-on-arrival hook (look after a successful traversal), so the dispatcher's normal updates_focus path re-anchors the focus chain for the new room.
      • Future system-fired commands (event-triggered actions, NPC scripts, scheduled tasks).

      Player-typed commands continue to flow through executeCommand directly with forced defaulting to false. Hooks that need to distinguish (e.g., a cinematic-locked NPC blocking auto-look) walk the stack via ExecutionContextApi.getCommandStack and look for forced ancestors.

      Parameters

      Returns Promise<void>

    • Drop the cached CommandDefinition for one YAML so the next getCommand(filename) re-reads from disk. The escape hatch for dev edits — command YAMLs don't auto-reload (the cache outlives file edits), so the workflow is: edit YAML → call CommandApi.invalidate('foo.yaml') → next push reloads.

      Note: live recency-stack entries that already hold a reference to the old CommandDefinition keep using it until they pop. The next applyContainmentDelta / applyShadowDelta push will pick up the reloaded definition.

      Returns true if the entry existed and was removed.

      Parameters

      • filename: string

      Returns boolean

    • Overlay a structured body side-channel (fields) onto an already-bound model, restricted to the command's payload:-block fields + the designated body field (a greedy string positional arg). Options/flags and object/MQL selectors are unreachable — the structural narrowness that keeps fields from filling a selector.

      Called by CommandGiver.executeCommand after the model is bound from the parsed string and BEFORE resolveModel, so the same resolve → validate → controller → envelope chain runs. The command string is always parsed first; this is purely additive, never a string-less dispatch path. Tiebreak: fields wins when both inline-greedy and the side-channel supply the body.

      Parameters

      Returns void

    • Eager boot-time load: walk every YAML under mud/cmd/, parse it, and resolve every validator reference into a live function. After preload, every cached CommandDefinition has its FieldDefinition._resolvedValidators populated.

      Returns the count of YAMLs that loaded successfully and the list of files that failed (parse error, validator-resolve error, etc).

      Returns Promise<{ failed: string[]; loaded: number }>

    • Walk every validator attached to command (verb-level + field + verb-option + payload + per-subcommand) and await any preload hooks they declare. Idempotent — StuffApi.singleton(path) is a no-op if the singleton already exists.

      The dispatcher calls this AFTER resolveModel (so field validators get the bound MQL result, not the raw query string) and BEFORE runValidators (so sync validators see a populated singleton cache). Validators without a preload are skipped.

      Preload signatures mirror the validators' sync bodies — verb- level preloads receive (context), field-level preloads receive (value, field, context). Field preloads inspect the bound value to compute per-target deps (e.g. a requiresAnimateTarget preload reads the bound Stuff's _speciesPath).

      MQL path-literal preloading (e.g. ensuring /lib/species/... referenced in a :race(...) filter is live) is NOT covered here; it lands when a verb actually needs it. Today the only preload consumer is requiresAnimate.

      Parameters

      Returns Promise<ValidatorPreloads>

    • Back-compat wrapper: resolve MQL then run validators in a single sync call. NOT used by the dispatcher (which interleaves an async validator-preload phase between MQL resolve and the sync validator phase via preloadValidatorDeps). Kept for tests and one-off callers that want the combined sync surface.

      Parameters

      Returns Promise<{ resolved: CommandModel } | { result: "failed" }>

    • Resolve a verb-level validator spec to a live CommandValidator. Same path-resolution as resolveValidator, but the runtime signature is (context) => string | undefined rather than the field-level (value, field, context) => ….

      Parameters

      • spec: string
      • fromYaml: string

      Returns Promise<CommandValidator<void>>

    • Run MQL resolution on type: object / type: objects fields and options. Returns the bound model with MqlOneResult / MqlManyResult wrappers where strings used to be; the rest of the fields pass through.

      Does NOT run validators — that's runValidators. The split exists so the dispatcher can insert an async preload phase between MQL resolution and validation: field validator preloads need the resolved value to compute their deps (e.g. requiresAnimateTarget reads the bound target's _speciesPath).

      Async since applyCardinalityPolicy (called per resolved field) can push a PromptApi prompt and await the player's pick. The await propagates PromptCancelledError to the caller.

      Reads command from context; the active subcommand (if any) is read from model.subcommand, which the matcher stamped at bind time.

      Parameters

      Returns Promise<{ resolved: CommandModel } | { result: "failed" }>

    • Resolve a parser spec to a Parser instance.

      Spec conventions:

      • Bare name (no /) → <src>/mud/lib/command/parsers/<name>.ts.
      • Absolute path (/X) → <src>/mud/X.ts.

      The default framework parser is 'msh' (Mud SHell — the tokenizer-driven shell). Custom parsers can live anywhere under mud/; reference them by absolute path from the shell.parser setting.

      Parameters

      • spec: string

      Returns Promise<Parser>

      Error if the spec is malformed, the file isn't found, or the module's default export isn't a Parser-shaped object.

    • Resolve a YAML-declared validator reference to its FieldValidator function.

      Path conventions:

      • /X<src>/mud/X.ts (mud-rooted absolute).
      • ./X, ../X → relative to fromYaml's directory.

      Bare names and package specifiers are rejected — the path tells you exactly where the validator lives, no implicit search paths.

      The JS module cache handles repeat loads; no bespoke registry.

      Parameters

      • spec: string
      • fromYaml: string

      Returns Promise<FieldValidator<void>>

      Error if the spec is malformed, the file isn't found, or the module's default export isn't a function.

    • Run every sync validator attached to command against the already-resolved model in context. Order matches the bind pipeline: verb-level first, then field, option, payload, and per-subcommand option validators. First failure short-circuits with a structured validator-failed note on context.

      Companion to resolveModel — call this after MQL resolution (and after the dispatcher's async preload phase) so field validator sync bodies see bound values.

      Parameters

      Returns { ok: true } | { result: "failed" }

    • Validate value against a JSON Schema fragment. Returns a friendly error string on failure, null on success. Compiled validators are cached by JSON-stringified schema so repeated calls against the same fragment skip recompilation.

      Used by the matcher's struct path and by WriteController, which reads a class's static dataSchema after the (async) class load and validates with the same machinery.

      Parameters

      • schema: Record<string, unknown>
      • value: unknown

      Returns string | null