On This Page

Commands

63 symbols from @neontechspace/fluxerly

Variables#

commands#

Route prefixed messages such as !ping to registered handlers

The simplest route is runBot's commands option. For an existing client, create a router, register commands and attach it

const commands: Commands

Nothing listens until attach, which uses one bounded messageCreate subscription and does not connect the client. Command handlers send replies and decide access, with optional guards, cooldowns and middleware. The router does not retry failed handlers, and a handler that returns an Err result is reported like a throw

Optional groups organize names and help text, but a protected command still needs its own guard. A guard decides whether a matched command may run, and the built-in guards cover common checks

Optional argument schemas convert raw args into frozen typed values after the guards. Conversion rejection gives rejection feedback without consuming a cooldown. The original args remain available

Help pages are strings the application can send. Hiding a command in help is not an access check

Creation, registration and help throw ConfigurationError for misuse, so a misconfigured router fails at startup

import { commands } from "@neontechspace/fluxerly"
export function commandGroupExample(client) {
    return commands
        .create({ prefix: "!" })
        .registerGroup({ name: "tools", aliases: ["t"] })
        .register({ name: "ping", execute: ({ reply }) => reply("Pong") }, { group: ["tools"] })
        .attach(client)
}
import { commands, type Client } from "@neontechspace/fluxerly"
export function commandGroupExample(client: Client) {
    return commands
        .create({ prefix: "!" })
        .registerGroup({ name: "tools", aliases: ["t"] })
        .register({ name: "ping", execute: ({ reply }) => reply("Pong") }, { group: ["tools"] })
        .attach(client)
}
import { commands } from "@neontechspace/fluxerly"

export function installPing(client) {
    return commands
        .create({
        prefix: "!",
        parse: commands.parseQuoted,
        onUnmatched: ({ reply }, unmatched) => {
            if (unmatched._tag !== "CommandUnknownName")
                return undefined
            const hint = unmatched.suggestion === undefined ? "" : `. Closest known command: ${unmatched.suggestion}`
            return reply(`Unknown command: ${unmatched.name}${hint}`)
        },
    })
        .register({ name: "ping", execute: ({ reply }) => reply("Pong") })
        .attach(client)
}
import { commands, type Client } from "@neontechspace/fluxerly"

export function installPing(client: Client) {
    return commands
        .create({
            prefix: "!",
            parse: commands.parseQuoted,
            onUnmatched: ({ reply }, unmatched) => {
                if (unmatched._tag !== "CommandUnknownName") return undefined
                const hint = unmatched.suggestion === undefined ? "" : `. Closest known command: ${unmatched.suggestion}`
                return reply(`Unknown command: ${unmatched.name}${hint}`)
            },
        })
        .register({ name: "ping", execute: ({ reply }) => reply("Pong") })
        .attach(client)
}
import { commands } from "@neontechspace/fluxerly"

export function typedCommandExample(client) {
    return commands
        .create({ prefix: "!", parse: commands.parseQuoted, onReject: "reply" })
        .register({
        name: "repeat",
        arguments: {
            mode: { type: "choice", choices: ["fast", "slow"] },
            count: { type: "integer", min: 1, max: 5, default: 1 },
            note: { type: "text", optional: true, rest: true },
        },
        cooldown: { durationMs: 10_000, per: "user" },
        execute: ({ reply, values }) => reply(`${values.count} / ${values.mode} / ${values.note ?? "No note"}`),
    })
        .attach(client)
}
import { commands, type Client } from "@neontechspace/fluxerly"

export function typedCommandExample(client: Client) {
    return commands
        .create({ prefix: "!", parse: commands.parseQuoted, onReject: "reply" })
        .register({
            name: "repeat",
            arguments: {
                mode: { type: "choice", choices: ["fast", "slow"] },
                count: { type: "integer", min: 1, max: 5, default: 1 },
                note: { type: "text", optional: true, rest: true },
            },
            cooldown: { durationMs: 10_000, per: "user" },
            execute: ({ reply, values }) => reply(`${values.count} / ${values.mode} / ${values.note ?? "No note"}`),
        })
        .attach(client)
}
import { orThrow } from "@neontechspace/fluxerly"

export async function commandHelpExample(client, router, channelId) {
    const pages = router.help({ prefix: "!", maxLength: 1_000, include: (command) => command.name !== "admin" })
    // Visibility is not authorization. Each command still needs its own policy
    for (const content of pages)
        orThrow(await client.messages.send(channelId, { content, allowedMentions: {} }))
    // Earlier pages remain sent if a later send fails. This example never retries
}
import { orThrow, type Client, type PrefixCommandRouter } from "@neontechspace/fluxerly"

export async function commandHelpExample(client: Client, router: PrefixCommandRouter, channelId: string) {
    const pages = router.help({ prefix: "!", maxLength: 1_000, include: (command) => command.name !== "admin" })
    // Visibility is not authorization. Each command still needs its own policy
    for (const content of pages) orThrow(await client.messages.send(channelId, { content, allowedMentions: {} }))
    // Earlier pages remain sent if a later send fails. This example never retries
}

guards#

Built-in command guards, namely guildOnly, dmOnly, ownerOnly and requirePermissions

const guards: Guards

Interfaces#

BotCommandsOptions#

Prefix commands for runBot: The router options of commands.create, the commands themselves, an optional failure hook and the delivery settings of the router's attach

The router is attached before the gateway starts and closed when the bot stops. The delivery settings concurrency, partition, partitionReady, overflow, maxPendingMessages and maxPendingBytes are passed to attach unchanged and checked before any client exists. Omitted settings keep the attach defaults: Eight commands at a time and overflow dropOldest, so a burst drops the oldest waiting message. With a partition, it follows the rule of the overflow option, with a Warn record. With overflow "stop", a full queue stops the bot with CriticalWorkerStoppedError. Unlike commands.create, runBot replies to a rejected command by default, such as one with a missing argument, as onReject: "reply" does. Set onReject: "silent" to send no reply, or a function to give custom feedback. An unset ignoreBots follows the runBot ignoreBots setting

interface BotCommandsOptions<
    M extends MessageCore = Message,
    S extends Readonly<Record<string, unknown>>
        = Readonly<
            Record<string, CommandArgumentSchema | undefined>
        >
>
    extends PrefixCommandsOptions<M>, HandlerOptions<M>interface BotCommandsOptions<
  M extends MessageCore = Message,
  S extends Readonly<
    Record<string, unknown>
  >
    = Readonly<
      Record<
        string,
        | CommandArgumentSchema
        | undefined
      >
    >
>
  extends PrefixCommandsOptions<
    M
  >, HandlerOptions<M>
commands#

Commands keyed by name, such as { ping: { execute: ({ reply }) => reply("Pong") } }, or a callback that receives the empty router and returns it with registrations, for groups or commands built elsewhere

The callback runs synchronously inside runBot. Invalid registrations and a return value other than the registered router throw ConfigurationError. Any other throw from the callback creates no client, and runBot returns an Err ApplicationError whose source is "runBot commands" and whose cause is the thrown value

| PrefixCommandBatch<M, S>
| ((router: PrefixCommandRouter<M>) => PrefixCommandRouter<M>)| PrefixCommandBatch<M, S>
| ((router: PrefixCommandRouter<M>)
    => PrefixCommandRouter<M>)
onError optional#

Receive command failures, and the reports of the router's own subscription, instead of the client-level onError, as the router attach option does

Those are a failing partitionReady hook, a throwing partition function and an overflow that stops the router (overflow "stop"). With overflow dropOldest, a full queue only writes a Warn record and produces no report

(report: FailureReport) => unknown
partitionReady optional#

Hold the commands of a partition key until its own setup is done, as the client.on option partitionReady does for event handlers

Valid only together with partition, otherwise runBot throws ConfigurationError before any client exists. Before the first waiting message of a key starts, the SDK calls the hook with the key and a signal that aborts when the router's subscription stops. The key's messages stay in the queue without using one of the concurrent command slots until the returned promise settles, while other keys keep running, and the held messages count toward maxPendingMessages and maxPendingBytes. A hook that throws, rejects or returns an Err result reports every waiting message of that key to the onError option of the commands, like a throwing partition function does, without a command name, and those messages run no command

(key: string, signal: AbortSignal) => unknown(key: string, signal: AbortSignal)
  => unknown

Inherited from PrefixCommandsOptions: caseSensitive, cooldowns, ignoreBots, mentionPrefix, onReject, onUnmatched, parse, prefix, use

Inherited from HandlerOptions: concurrency, maxPendingBytes, maxPendingMessages, overflow, partition

CommandArgumentBoolean#

Convert only the exact lowercase strings true and false to booleans, rejecting other spellings and numeric substitutes

interface CommandArgumentBoolean
optional optional#

Set to true to permit omission and return undefined

A supplied invalid token still rejects

true
type#

Require true or false and return the corresponding boolean

"boolean"

CommandArgumentChannel#

Channel data that a channelChoice argument can match

Registration copies only id and name, and conversion returns that frozen copy. The router does not fetch channels or observe later changes to the supplied data

interface CommandArgumentChannel
id#

Decimal ID string without leading zeroes except 0 itself

Complete channel mentions accept nonzero IDs only

string
name#

Nonempty channel name matched exactly

Decimal tokens try IDs before names, while complete channel mentions try IDs only

string

CommandArgumentChannelSelection#

Select a channel from a fixed list of candidates by decimal ID, complete channel mention or exact name

For any channel, use { type: "id", mention: "channel" } instead. Decimal text falls back to a name only when no ID matches, while a complete mention selects IDs only. Returns the one matching frozen id and name copy, rejecting absent or ambiguous matches. Channel selection does not fetch current state or check permissions

interface CommandArgumentChannelSelection<
    C extends readonly CommandArgumentChannel[]
        = readonly CommandArgumentChannel[]
>interface CommandArgumentChannelSelection<
  C extends readonly CommandArgumentChannel[]
    = readonly CommandArgumentChannel[]
>
candidates#

One to 100 channels with decimal IDs and nonempty names, copied to id and name at registration

C
optional optional#

Set to true to permit omission and return undefined

Supplied unmatched or ambiguous text still rejects

true
type#

Resolve the token against these channel candidates only

"channelChoice"

CommandArgumentChoice#

Match one of the configured nonempty strings exactly, returning that string rather than its list position

interface CommandArgumentChoice<
    C extends readonly string[] = readonly string[]
>interface CommandArgumentChoice<
  C extends readonly string[]
    = readonly string[]
>
choices#

One to 100 unique nonempty strings, copied at registration

A literal tuple preserves the allowed-string union in values

C
optional optional#

Set to true to allow omission and produce undefined

An unlisted supplied token is invalid

true
type#

Compare the token to the configured strings without case folding

"choice"

CommandArgumentCustom#

Convert one token, or the remaining tokens with rest, through an application parse function

Return the converted value, or undefined to reject the token as Invalid. A parse function that throws or returns an Err result fails the command without running it, and that error is reported with the command name like a failed handler. Parsing is synchronous in both APIs. Returning a Promise or Effect is misuse: The command does not run, and a ConfigurationError with field command is reported the same way. The outcome of a returned Promise is not awaited or reported

interface CommandArgumentCustom<T = unknown>interface CommandArgumentCustom<
  T = unknown
>
expected optional#

Short description of the expected value, such as a hex color, used in rejection replies

string
optional optional#

Set to true to produce undefined when omitted

true
parse#

Convert the token synchronously, returning undefined for input that should be rejected

Throw or return an Err to fail the command

(token: string) => T | undefined
rest optional#

Set to true to pass the remaining tokens joined with single spaces

This descriptor must be last

true
type#

Convert with the supplied parse function

"custom"

CommandArgumentDuration#

Convert a duration such as 90s, 5m, 1h30m or 2d to whole milliseconds

Units are ms, s, m, h, d and w, each after a whole number, combined from largest to smallest without spaces. A bare number is not accepted, so 10 is rejected rather than guessed as seconds or minutes

interface CommandArgumentDuration
default optional#

Milliseconds used when the argument is omitted, which makes it optional without adding undefined to its value

number
max optional#

Longest accepted duration in milliseconds, inclusive

number
min optional#

Shortest accepted duration in milliseconds, inclusive

number
optional optional#

Set to true to produce undefined when omitted

true
type#

Produce whole milliseconds, a safe integer, from unit-suffixed duration text

"duration"
wholeSeconds optional#

Set to true to reject a duration with a millisecond part, such as 1m500ms, for a destination that takes whole seconds, such as a ban's durationMs

true

CommandArgumentId#

Keep a decimal resource ID as a string without losing digits through JavaScript number conversion

Accepts digits beginning with a nonzero digit, with no sign or leading zeroes, up to 9223372036854775807, the largest 64-bit ID. Rejects 0 and larger values as Invalid. Optionally accepts one selected complete mention form and returns only its ID. Does not fetch a resource, check whether it exists or authorize an action against it

interface CommandArgumentId
mention optional#

Also accept this mention kind as a complete token

Omitted means accept decimal ID strings only

CommandArgumentMention
optional optional#

Set to true to return undefined when omitted

Supplied malformed IDs or mentions still reject

true
type#

Return the accepted nonzero decimal ID string rather than a number or resource object

"id"

CommandArgumentInteger#

Convert a decimal token such as 3 or -2 to a JavaScript safe integer

Accepts an optional minus sign and digits without leading zeroes, except 0 itself. Rejects fractions, exponent notation, a leading plus sign and numbers outside the safe-integer range

interface CommandArgumentInteger
default optional#

Safe integer used when the argument is omitted, which makes it optional without adding undefined to its value

number
max optional#

Largest accepted value, inclusive

A larger value is rejected as Invalid

number
min optional#

Smallest accepted value, inclusive

A smaller value is rejected as Invalid

number
optional optional#

Set to true to return undefined when omitted

Otherwise this argument is required and must precede optional arguments

true
type#

Require decimal integer syntax and a safe integer result

"integer"

CommandArgumentMember#

Select a member of the message's community by complete user mention or decimal user ID, without a roster lookup

The ID must be nonzero, without leading zeroes and no larger than 9223372036854775807, the largest 64-bit ID. Other tokens are rejected as Invalid. The value is a frozen { guildId, userId } reference that member operations such as members.kick accept directly. Whether that user is still a member is not checked. A message outside a community rejects this argument as Invalid

interface CommandArgumentMember
optional optional#

Set to true to produce undefined when omitted

true
type#

Produce a member reference from a user mention or ID

"member"

CommandArgumentMetadata#

Frozen description of an argument for help generation, without resource candidate data

interface CommandArgumentMetadata
choices optional#

Frozen accepted strings for a choice descriptor

Absent for other types

readonly string[]
default optional#

Value used when the argument is omitted, for descriptors that declare one

number
expected optional#

Short description of the expected value for a custom descriptor, used in rejection replies

Absent for other types

string
max optional#

Inclusive upper bound of an integer, number or duration descriptor, in milliseconds for a duration

Absent without a bound

number
mention optional#

Additional mention kind accepted by an id descriptor, if configured

Generated usage still shows the argument name

CommandArgumentMention
min optional#

Inclusive lower bound of an integer, number or duration descriptor, in milliseconds for a duration

Absent without a bound

number
name#

Schema property name, which also names its converted value

string
optional#

True when the argument can be omitted, producing undefined or its default instead of an error

boolean
rest#

True when this final text or custom argument takes all remaining tokens, joined with spaces

boolean
type#

Conversion applied to this positional token, such as integer or member

CommandArgumentType
wholeSeconds optional#

True for a duration descriptor that rejects a millisecond part

Absent otherwise

true

CommandArgumentNumber#

Convert decimal text such as 1.25, -2 or 3e2 to a finite JavaScript number

Requires an integer part without leading zeroes, with optional minus sign, fraction and exponent. Rejects .5, 1., a leading plus sign, Infinity, NaN and values that overflow to infinity. Normal JavaScript number rounding still applies, unlike the safe-integer check for integer

interface CommandArgumentNumber
default optional#

Finite number used when the argument is omitted, which makes it optional without adding undefined to its value

number
max optional#

Largest accepted value, inclusive

A larger value is rejected as Invalid

number
min optional#

Smallest accepted value, inclusive

A smaller value is rejected as Invalid

number
optional optional#

Set to true to produce undefined when omitted

Leaving this unset requires a token

true
type#

Accept decimal and exponent syntax when conversion produces a finite number

"number"

CommandArgumentParseOptions#

Settings for commands.parseArgument that a message would otherwise supply

interface CommandArgumentParseOptions
guildId optional#

Community ID that a member descriptor puts in its reference, normally the message's guildId

Without it a member token is rejected, as in a direct message

string

CommandArgumentRole#

Role data that a roleChoice argument can match

Registration copies only id and name, and conversion returns that frozen copy. Selection does not fetch roles or verify the invoking user's permissions or role hierarchy

interface CommandArgumentRole
id#

Decimal ID string without leading zeroes except 0 itself

Complete role mentions accept nonzero IDs only

string
name#

Nonempty role name matched exactly

Decimal tokens try IDs before names, while complete role mentions try IDs only

string

CommandArgumentRoleSelection#

Select a role from a fixed list of candidates by decimal ID, complete role mention or exact name

For any role, use { type: "id", mention: "role" } instead. Decimal text tries IDs first and falls back to names only when no ID matches. Complete role mentions select IDs only, with no name fallback. Returns the one matching frozen id and name copy, rejecting absent or ambiguous matches

interface CommandArgumentRoleSelection<
    C extends readonly CommandArgumentRole[]
        = readonly CommandArgumentRole[]
>interface CommandArgumentRoleSelection<
  C extends readonly CommandArgumentRole[]
    = readonly CommandArgumentRole[]
>
candidates#

One to 100 roles with decimal IDs and nonempty names

Registration copies only id and name

C
optional optional#

Set to true to allow omission and return undefined, rather than selecting any role implicitly

true
type#

Resolve the token against these role candidates only, without a permission or hierarchy check

"roleChoice"

CommandArgumentText#

Keep one nonempty parsed token as a string, or join the remaining tokens when rest is true

For example, a final rest argument converts ["hello", "world"] to "hello world". This uses parsed tokens, not rawArgs, so original quotes and separator whitespace are not retained. A supplied empty token is invalid even when the argument is optional

interface CommandArgumentText
optional optional#

Set to true to produce undefined when no token remains

Omitted means required, and false is not a supported option

true
rest optional#

Set to true to join remaining tokens with single spaces

This descriptor must be last. Omitted means consume one token

true
type#

Keep parsed text without numeric or resource conversion

"text"

CommandArgumentUser#

User data that a userChoice argument can match

Registration copies only id and username, and conversion returns that frozen copy. No cache or network search occurs, and later changes to the original candidate do not affect selection

interface CommandArgumentUser
id#

Decimal ID string, either 0 or digits beginning with a nonzero digit

May be selected directly or by a complete user mention with a nonzero ID

string
username#

Nonempty username matched exactly, including case

Decimal tokens try IDs first, then usernames. Complete user mentions try IDs only

string

CommandArgumentUserSelection#

Select a user from a fixed list of candidates by decimal ID, complete user mention or exact username

For any user, use { type: "id", mention: "user" } or { type: "member" } instead. Decimal text tries IDs before usernames, while a complete mention never falls back to a username. Exactly one match returns a frozen id and username copy. No match is invalid, and multiple matches are ambiguous, even when candidate IDs are duplicated

interface CommandArgumentUserSelection<
    C extends readonly CommandArgumentUser[]
        = readonly CommandArgumentUser[]
>interface CommandArgumentUserSelection<
  C extends readonly CommandArgumentUser[]
    = readonly CommandArgumentUser[]
>
candidates#

One to 100 users with nonempty usernames and decimal IDs

Registration copies only id and username

C
optional optional#

Set to true to return undefined when omitted, without choosing a default candidate

true
type#

Resolve a token against these user candidates only

"userChoice"

CommandContextHelpOptions#

Choose help pages for the help function of a command context

Every setting is optional: The prefix defaults to the one that matched the invoking message and the page length defaults to 2,000 UTF-16 code units, which fits one message

interface CommandContextHelpOptions
group optional#

Registered names of the group to describe, such as ["admin"]

Omit for root entries, as in router.help

readonly string[]
include optional#

Return false to leave an entry out, as in router.help

Hidden entries never appear

(command: PrefixCommandMetadata) => boolean(command: PrefixCommandMetadata)
  => boolean
maxLength optional#

Maximum page length in UTF-16 code units, a positive safe integer, default 2,000

number
prefix optional#

Prefix to display, default the prefix that matched the invoking message, such as !

string

CommandCooldownRequest#

Request to reserve a cooldown after successful guard and argument conversion, before command execution

interface CommandCooldownRequest
durationMs#

Whole milliseconds to reserve, from 1 through 2,147,483,647

number
key#

Nonempty key combining command identity with the configured key suffix

Use the complete string as the store key

string

CommandHelpOptions#

Choose the text prefix, page length and visible entries for router.help

Help reads registered metadata only, without connecting a client, executing commands or sending messages. The caller decides which returned pages to send and how to handle mentions

interface CommandHelpOptions
group optional#

Choose a group by its registered names, not aliases, such as ["admin"]

Omit or use [] for root entries. A selected group shows itself and its immediate children, not deeper descendants. Missing groups fail with ConfigurationError, and so do hidden groups and groups inside them, so help does not reveal them

readonly string[]
include optional#

Return true to show an entry or false to exclude it, for example (entry) => entry.name !== "internal"

Omitted means show each considered entry. Receives frozen metadata, with kind: "group" identifying groups. Entries registered with hidden: true, and everything inside a hidden group, are left out before this callback runs

(command: PrefixCommandMetadata) => boolean(command: PrefixCommandMetadata)
  => boolean

Root help checks immediate entries once each in sibling registration order. A group selection checks ancestors first, then the selected group and its immediate children, at most once per entry. An ancestor or selected group that include rejects returns [] without inspecting later entries. An included empty group remains visible, even if none of its children appear

Excluding an entry from help does not prevent the command from running. Help does not run guards or check cooldowns

A throw fails with ConfigurationError whose cause is the thrown value. A non-boolean or asynchronous return fails with ConfigurationError. Promises are not supported or awaited. Keep this synchronous callback short because it cannot be interrupted while running

maxLength#

Required maximum page length, measured in UTF-16 code units

Use a positive safe integer. There is no default or provider-limit lookup

number
prefix#

Nonempty well-formed prefix to display, such as !

This can differ from dispatch prefixes and does not call the prefix resolver

string

Commands#

Recognize message commands such as !repeat hello with the default API's optional commands tools

Create a router, keep the new router returned by each registration and attach the final router to a client, or pass the commands to runBot, which does all of that. Construction is local. No connection or subscription starts until attachment

interface Commands

create#

Create an empty router, for example commands.create({ prefix: "!", parse: commands.parseQuoted })

Defaults to full Message typing. Supply the client's MessageCore or SelectedMessage type as M when it selects fewer fields. Prefix resolvers, parsers, command callbacks and their context client keep that same message type. Invalid options throw ConfigurationError, without including rejected prefix or parser values. A throwing getter throws SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect. No client connects and no feedback callback runs during creation

create<
    M extends MessageCore = Message
>(options: PrefixCommandsOptions<M>): PrefixCommandRouter<M>create<
  M extends MessageCore = Message
>(
  options: PrefixCommandsOptions<M>
): PrefixCommandRouter<M>

Parameters

memoryCooldowns#

Create an empty in-memory cooldown store with a default limit of 10,000 keys, to share one store between routers

An optional positive safe integer maxEntries changes that limit. A full store makes room rather than refusing a new key, removing expired keys first and then the reservation that expires soonest. Invalid options throw ConfigurationError. A throwing getter throws SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect. The store has no timer or persistence. Reuse it in command cooldown definitions and clear it explicitly to forget claims

memoryCooldowns(
    options?: MemoryCooldownOptions
): MemoryCooldownStorememoryCooldowns(
  options?: MemoryCooldownOptions
): MemoryCooldownStore

Parameters

options optional
MemoryCooldownOptions

parseArgument#

Convert one token with a built-in argument descriptor, such as commands.parseArgument("30m", { type: "duration" }), which returns 1_800_000

The grammar is the router's own, so a token converts here exactly when it converts in a command with that descriptor. Accepts every descriptor type except custom, whose parse function can be called directly. The result has the descriptor's value type, such as a string for id and a MemberReference for member. A rejected token returns undefined, as does a missing token unless the descriptor has a default, which is returned instead. A member descriptor needs options.guildId, the community the token belongs to, and rejects every token without it. Runs synchronously without a cache, client or network work. A bad descriptor, a custom descriptor, a token that is neither a string nor undefined and bad options throw ConfigurationError. A throwing getter or proxy trap in the descriptor or options throws SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect

parseArgument<
    D extends
        | CommandArgumentText
        | CommandArgumentInteger
        | CommandArgumentNumber
        | CommandArgumentBoolean
        | CommandArgumentId
        | CommandArgumentChoice<readonly string[]>
        | CommandArgumentUserSelection<
              readonly CommandArgumentUser[]
          >
        | CommandArgumentChannelSelection<
              readonly CommandArgumentChannel[]
          >
        | CommandArgumentRoleSelection<
              readonly CommandArgumentRole[]
          >
        | CommandArgumentMember
        | CommandArgumentDuration
>(
    token: string | undefined,
    descriptor: D,
    options?: CommandArgumentParseOptions
): CommandArgumentValue<D> | undefinedparseArgument<
  D extends
    | CommandArgumentText
    | CommandArgumentInteger
    | CommandArgumentNumber
    | CommandArgumentBoolean
    | CommandArgumentId
    | CommandArgumentChoice<
        readonly string[]
      >
    | CommandArgumentUserSelection<
        readonly CommandArgumentUser[]
      >
    | CommandArgumentChannelSelection<
        readonly CommandArgumentChannel[]
      >
    | CommandArgumentRoleSelection<
        readonly CommandArgumentRole[]
      >
    | CommandArgumentMember
    | CommandArgumentDuration
>(
  token: string | undefined,
  descriptor: D,
  options?:
    CommandArgumentParseOptions
):
  | CommandArgumentValue<D>
  | undefined

Parameters

token
string | undefined
descriptor
D
Returns
CommandArgumentValue<D> | undefined

parseQuoted#

Split a suffix using single or double quotes and backslash escapes, retaining original argument text in rawArgs

Use as create({ prefix: "!", parse: commands.parseQuoted }) to opt in instead of the whitespace-only default. Returns undefined for empty input or invalid names, and a PrefixCommandParseRejection with a fixed reason and the command name for an unclosed quote or a trailing escape, which onUnmatched receives. For 'a'b, the single-quote reason adds that a quote followed directly by a letter or digit does not close the span. A double quote opens a span anywhere. A single quote opens one only at the start of a word and closes unless a letter or digit follows, so don't stays literal and 'Yes', 'No' is Yes, and No. Empty quotes produce an empty token. Runs synchronously without client or network work

parseQuoted<M extends MessageCore = Message>(
    input: PrefixCommandParseInput<M>
): PrefixCommandParse | PrefixCommandParseRejection | undefinedparseQuoted<
  M extends MessageCore = Message
>(
  input: PrefixCommandParseInput<M>
):
  | PrefixCommandParse
  | PrefixCommandParseRejection
  | undefined

Parameters

suggest#

Name the candidate closest to a misspelled name, such as commands.suggest("pnig", ["ping", "help"]), which returns "ping"

This is the rule behind the suggestion of CommandUnknownName. It agrees with the router's suggestion for its default case-insensitive matching when the candidates are the registered names of one level. The router also considers aliases and reports the command's own name for a near alias, whereas this helper returns the candidate that matched. A router created with caseSensitive: true compares case. Case is ignored. A candidate counts when it is within one edit of a name of up to four characters, or within two edits of a longer name, where an edit inserts, deletes or replaces one character, or swaps two adjacent characters. The closest candidate wins, and the earliest of equally close ones. Returns the winning candidate as written, or undefined when none is close enough or the name is empty. The helper applies no hidden-command filter, so pass only names that may be revealed. Runs synchronously without client or network work. A name that is not a string, candidates that are not an iterable of strings and a lone string as candidates throw ConfigurationError. Candidates whose iterator lookup or iterator throws make it throw SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect

suggest(
    name: string,
    candidates: Iterable<string>
): string | undefinedsuggest(
  name: string,
  candidates: Iterable<string>
): string | undefined

Parameters

name
string
candidates
Iterable<string>
Returns
string | undefined

CooldownStore#

Storage that reserves a command cooldown and reports whether this invocation may execute

A custom store must handle saving claims and sharing them across processes if needed. Checking and reserving a full key must be atomic, so two concurrent calls cannot both claim the same key

interface CooldownStore

claim#

Reserve input.key for input.durationMs and return a claim immediately or through a promise

Only CooldownAcquired permits the handler to run. Other claims give optional rejection feedback, without waiting or retrying. Malformed claims, throws, rejected promises and returned Err results use subscription error reporting

claim(
    input: CommandCooldownRequest
): CommandCooldownClaim | PromiseLike<CommandCooldownClaim>claim(
  input: CommandCooldownRequest
):
  | CommandCooldownClaim
  | PromiseLike<
      CommandCooldownClaim
    >

Parameters

Guards#

Built-in command guards for the most common access rules

Pass one or a list as a command's guard. Each denial carries a short reason that onReject: "reply" sends to the user. Invalid guard settings throw ConfigurationError when the guard is created

interface Guards
import { guards, runBot } from "@neontechspace/fluxerly"

export function moderationBot(token) {
    return runBot({
        token,
        commands: {
            prefix: "!",
            commands: {
                purge: {
                    guard: [guards.guildOnly(), guards.requirePermissions(["ManageMessages"])],
                    execute: ({ reply }) => reply("Purging"),
                },
            },
        },
    })
}
import { guards, runBot } from "@neontechspace/fluxerly"

export function moderationBot(token: string | undefined) {
    return runBot({
        token,
        commands: {
            prefix: "!",
            commands: {
                purge: {
                    guard: [guards.guildOnly(), guards.requirePermissions(["ManageMessages"])],
                    execute: ({ reply }) => reply("Purging"),
                },
            },
        },
    })
}

dmOnly#

Allow the command only in a confirmed one-to-one or group conversation, and deny every other message

A message with a guildId is denied without a request. For a message without one, the guard checks the direct-message cache, then the channel cache, and otherwise reads the channel once with directMessages.fetch, which also fills an enabled direct-message cache. Enable cache.directMessages to avoid a read for each invocation in the same conversation. A private conversation allows the command. A cached community channel, or a read that Fluxer answers with anything other than a private conversation, denies it. Any other read failure, such as a network failure, timeout or rejection, leaves the channel unconfirmed and fails the command, which is reported with the command name

dmOnly<M extends MessageCore = Message>(): PrefixCommandGuard<M>dmOnly<
  M extends MessageCore = Message
>(): PrefixCommandGuard<M>

guildOnly#

Allow the command only for a message that carries its community's guildId, and deny every other message without a request

Fluxer's gateway supplies the guildId with every community message, so a message without one is denied even when its channel belongs to a community

guildOnly<M extends MessageCore = Message>(): PrefixCommandGuard<M>guildOnly<
  M extends MessageCore = Message
>(): PrefixCommandGuard<M>

ownerOnly#

Allow the command only for the owner of the bot's application, as Fluxer reports it

The first invocation reads the owner's user ID with application.fetch, and the guard keeps it for the rest of the client's lifetime, so later invocations decide without a request and an ownership transfer applies to a new client. Invocations that start before the first read completes each read it. A failed read, such as a network failure, timeout or rejection, keeps nothing and fails the command, which is reported with the command name, and the next invocation reads again

ownerOnly<M extends MessageCore = Message>(): PrefixCommandGuard<M>ownerOnly<
  M extends MessageCore = Message
>(): PrefixCommandGuard<M>

Allow the command only for the given decimal user IDs, such as the bot owner's

An undefined argument, such as an unset environment variable, throws ConfigurationError rather than falling back to the application owner

ownerOnly<
    M extends MessageCore = Message
>(ownerIds: string | readonly string[]): PrefixCommandGuard<M>ownerOnly<
  M extends MessageCore = Message
>(
  ownerIds:
    | string
    | readonly string[]
): PrefixCommandGuard<M>

Parameters

ownerIds
string | readonly string[]

requirePermissions#

Allow the command only when the invoking member has every named permission in the message's channel, and deny it in direct messages

The guard decides with permissions.fetch and preferCache: It reads the community, member, roles and channel from enabled caches first and fetches only what is missing, at most once each per invocation, then applies permissions.calculate. The reads share one deadline, rest.defaultTimeoutMs, and only fetches count toward it. Cached observations reflect the last applied gateway events, so a permission change that Fluxer has made but the gateway has not yet delivered is not seen. Where a stale grant matters, check again with permissions.fetch before acting. A cached channel without its overwrite list is read again instead of being treated as having no overwrites. In a thread, the guard also reads the thread's parent channel the same way, cache first, and decides as Fluxer does: From the parent channel's permissions, with SendMessages granted exactly when SendMessagesInThreads is. It does not check whether the member can see a private thread or whether the thread is archived or locked. Fluxer sends a channel's permission overwrite list, empty when there are none. When Fluxer sent the channel without its permission overwrite list, its permissions cannot be confirmed, so the guard denies the command rather than guessing. Every other failed read fails the command, which is reported with the command name. The decision does not guarantee that Fluxer allows a later action. A timed-out member keeps its permission bits, but Fluxer blocks sending, editing and publishing own messages, reacting, uploading, typing, changing the own nickname, writing in threads and joining voice, so check members.timeoutState as well. The denial names each missing permission by Fluxer's label, such as Manage community

requirePermissions<M extends MessageCore = Message>(
    names: readonly (
        | "CreateInstantInvite" | "KickMembers" | "BanMembers"
        | "Administrator" | "ManageChannels" | "ManageGuild"
        | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker"
        | "Stream" | "ViewChannel" | "SendMessages"
        | "SendTtsMessages" | "ManageMessages" | "EmbedLinks"
        | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone"
        | "UseExternalEmojis" | "Connect" | "Speak" | "MuteMembers"
        | "DeafenMembers" | "MoveMembers" | "UseVad"
        | "ChangeNickname" | "ManageNicknames" | "ManageRoles"
        | "ManageWebhooks" | "ManageExpressions" | "ManageThreads"
        | "CreatePublicThreads" | "CreatePrivateThreads"
        | "UseExternalStickers" | "SendMessagesInThreads"
        | "ModerateMembers" | "CreateExpressions" | "PinMessages"
        | "BypassSlowmode" | "UpdateRtcRegion"
        | "ViewChannelMembers")[]
): PrefixCommandGuard<M>requirePermissions<
  M extends MessageCore = Message
>(
  names: readonly (
    | "CreateInstantInvite"
    | "KickMembers" | "BanMembers"
    | "Administrator"
    | "ManageChannels"
    | "ManageGuild"
    | "AddReactions"
    | "ViewAuditLog"
    | "PrioritySpeaker" | "Stream"
    | "ViewChannel"
    | "SendMessages"
    | "SendTtsMessages"
    | "ManageMessages"
    | "EmbedLinks" | "AttachFiles"
    | "ReadMessageHistory"
    | "MentionEveryone"
    | "UseExternalEmojis"
    | "Connect" | "Speak"
    | "MuteMembers"
    | "DeafenMembers"
    | "MoveMembers" | "UseVad"
    | "ChangeNickname"
    | "ManageNicknames"
    | "ManageRoles"
    | "ManageWebhooks"
    | "ManageExpressions"
    | "ManageThreads"
    | "CreatePublicThreads"
    | "CreatePrivateThreads"
    | "UseExternalStickers"
    | "SendMessagesInThreads"
    | "ModerateMembers"
    | "CreateExpressions"
    | "PinMessages"
    | "BypassSlowmode"
    | "UpdateRtcRegion"
    | "ViewChannelMembers")[]
): PrefixCommandGuard<M>

Parameters

names
readonly (
    | "CreateInstantInvite" | "KickMembers" | "BanMembers"
    | "Administrator" | "ManageChannels" | "ManageGuild"
    | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream"
    | "ViewChannel" | "SendMessages" | "SendTtsMessages"
    | "ManageMessages" | "EmbedLinks" | "AttachFiles"
    | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis"
    | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers"
    | "MoveMembers" | "UseVad" | "ChangeNickname"
    | "ManageNicknames" | "ManageRoles" | "ManageWebhooks"
    | "ManageExpressions" | "ManageThreads" | "CreatePublicThreads"
    | "CreatePrivateThreads" | "UseExternalStickers"
    | "SendMessagesInThreads" | "ModerateMembers"
    | "CreateExpressions" | "PinMessages" | "BypassSlowmode"
    | "UpdateRtcRegion" | "ViewChannelMembers")[]readonly (
  | "CreateInstantInvite"
  | "KickMembers" | "BanMembers"
  | "Administrator" | "ManageChannels"
  | "ManageGuild" | "AddReactions"
  | "ViewAuditLog" | "PrioritySpeaker"
  | "Stream" | "ViewChannel"
  | "SendMessages" | "SendTtsMessages"
  | "ManageMessages" | "EmbedLinks"
  | "AttachFiles"
  | "ReadMessageHistory"
  | "MentionEveryone"
  | "UseExternalEmojis" | "Connect"
  | "Speak" | "MuteMembers"
  | "DeafenMembers" | "MoveMembers"
  | "UseVad" | "ChangeNickname"
  | "ManageNicknames" | "ManageRoles"
  | "ManageWebhooks"
  | "ManageExpressions"
  | "ManageThreads"
  | "CreatePublicThreads"
  | "CreatePrivateThreads"
  | "UseExternalStickers"
  | "SendMessagesInThreads"
  | "ModerateMembers"
  | "CreateExpressions"
  | "PinMessages" | "BypassSlowmode"
  | "UpdateRtcRegion"
  | "ViewChannelMembers")[]

MemoryCooldownOptions#

Limit the cooldown keys held by one memory store in one process

Keys are not saved or shared with other processes

interface MemoryCooldownOptions
maxEntries optional#

Positive safe integer key limit, defaulting to 10,000

A claim for a new key never fails for lack of space: A full store removes expired keys first, then the reservation that expires soonest, whose key can then claim again early

number

MemoryCooldownStore#

Cooldown reservations kept in one memory store, up to that store's configured key limit

Share this instance across commands that should use the same store. Uses Date.now() to set expiry. It does not save claims, run a background timer or share claims with other processes

interface MemoryCooldownStore
maxEntries#

Maximum keys this store retains, fixed at creation

number
size#

Current stored-key count

May include expired keys until a full store needs room or an explicit sweep removes them

number

claim#

Atomically reserve the requested key, or report its active cooldown

Acquired expiry is Date.now() + durationMs. A new key is never refused for lack of space: A full store removes expired keys first, then the reservation that expires soonest, whose key can then claim again early. Malformed keys or durations throw ConfigurationError. A throwing claim getter throws SdkDefect for commands with code application.defect and the thrown value as its cause

claim(input: CommandCooldownRequest): CommandCooldownClaimclaim(
  input: CommandCooldownRequest
): CommandCooldownClaim

Parameters

clear#

Forget this store's reservations immediately, allowing new claims

Does not cancel handlers or clear other store instances

clear(): void

sweep#

Remove keys expired according to Date.now() and return the number removed, without changing active reservations

sweep(): number
Returns
number

PrefixCommand#

A command name and the application callback that handles it

Dispatch runs router middleware and the command's guards, converts arguments, claims any cooldown and then calls execute. Registration copies metadata and arguments but retains callback and cooldown-store references. No automatic message or retry is provided, and successful return values are ignored

interface PrefixCommand<
    S extends CommandArgumentSchema = { … },
    M extends MessageCore = Message
>
    extends PrefixCommandDefinitioninterface PrefixCommand<
  S extends CommandArgumentSchema
    = { … },
  M extends MessageCore = Message
>
  extends PrefixCommandDefinition
arguments optional#

Named positional conversions, read in property order

Omitted leaves args unrestricted and values empty

S
cooldown optional#

Optional reservation required after the guards and arguments succeed

A denied claim skips execution

PrefixCommandCooldown<S, M>
execute#

Perform application work after the command is allowed and all arguments convert

Return normally or a promise completing that work. A returned or resolved Err result is reported like a throw, so ({ reply }) => reply("Pong") reports a failed reply. Other successful values are ignored. Throws, rejected promises and Err results use attachment onError reporting, with no automatic retry or cooldown rollback. Pass context.signal to cancellable operations when application work should stop with the subscription

(context: PrefixCommandExecutionContext<S, M>) => unknown(
  context: PrefixCommandExecutionContext<
    S, M
  >
) => unknown
guard optional#

One guard or a list of guards, run in order before argument conversion

The first that denies stops the command. Each command owns its policy, with no inherited group guard or permission from help visibility

PrefixCommandGuard<M> | readonly PrefixCommandGuard<M>[]| PrefixCommandGuard<M>
| readonly PrefixCommandGuard<M>[]
onReject optional#

Feedback after a denied guard, rejected arguments or a denied cooldown claim, overriding the router's onReject

A function receives raw context and a safe rejection classification, not partially converted values. Throws, rejected promises and returned Err results use attachment error reporting, without retry

PrefixCommandRejectionFeedback<M>

Inherited from PrefixCommandDefinition: aliases, description, hidden, name, usage

PrefixCommandContext#

Frozen command information copied for a matched message and passed to its guards, middleware and rejection callback

Includes the incoming message and parsed strings, but not converted argument values. Execution and cooldown-key callbacks receive the extended execution context

interface PrefixCommandContext<M extends MessageCore = Message>interface PrefixCommandContext<
  M extends MessageCore = Message
>
args#

Parsed string tokens copied to a frozen array, unchanged by subsequent argument conversion

readonly string[]
client#

Client supplied to attach, available for explicit operations

The application remains responsible for connecting and shutting it down

Client<M>
help#

Build help pages from the router that matched this command, for example ({ help, reply }) => reply(help()[0] ?? "No commands")

Takes the same settings as router.help, all optional: The prefix defaults to the one this message used and the page length to 2,000 UTF-16 code units. Nothing is sent. Invalid settings throw ConfigurationError as router.help does

(options?: CommandContextHelpOptions) => readonly string[](options?: CommandContextHelpOptions)
  => readonly string[]
message#

Incoming frozen message with this client's selected fields

The router does not fetch excluded fields

M
name#

Registered command name, even when the message used an alias or different letter case

string
paginate#

Show several pages in this message's channel that the command's author flips with ◀ and ▶ reactions, such as ({ paginate }) => paginate(["First", "Second"])

Delegates to client.messages.paginate with users defaulting to this message's author and this handler's cancellation signal applied, combined with any supplied signal. Pass users to let other people flip, such as () => true for anyone. The other options, defaults, permissions, cleanup and failures are those of client.messages.paginate. The result settles when listening ends, so the handler stays active until then. Returning it from a command callback reports an Err like a thrown error

(pages: readonly PageInput[], options?: PaginateOptions)
    => ResultAsync<
        PaginateResult<M>,
        CancelledError | PaginateFailure
    >(
  pages: readonly PageInput[],
  options?: PaginateOptions
) => ResultAsync<
  PaginateResult<M>,
  CancelledError | PaginateFailure
>
path optional#

Frozen registered names through this command, such as ["admin", "inspect"]

Absent for root commands and does not imply permission

readonly string[]
prefix#

Prefix that matched this message, such as !, !! or the bot mention when mentionPrefix is enabled

string
rawArgs#

Argument text from the parser

The default retains everything after command-name separator whitespace, including trailing whitespace

string
reply#

Reply to the incoming message through the attached client, with this handler's cancellation signal already applied

A string is sent as the reply's content, such as reply("Pong"). Delegates to client.messages.reply, including its validation, deadline, nonce, retry, cache and defect behavior. Cancellation or a lost response can leave the reply posted. An Err does not always mean nothing was sent, and uncertain sends are not replayed. Returning the result from a command callback reports an Err like a thrown error, so ({ reply }) => reply("Pong") needs no further checks

(
    input: string | ReplyInput,
    options?: Omit<RequestOptions, "signal">
) => ResultAsync<M, ConfigurationError | SendError | CancelledError>(
  input: string | ReplyInput,
  options?:
    Omit<RequestOptions, "signal">
) => ResultAsync<
  M,
  | ConfigurationError | SendError
  | CancelledError
>
sendHelp#

Send this router's help in this message's channel, such as ({ sendHelp }) => sendHelp()

Takes the same optional settings as help. One page is sent as a plain message, and several pages use paginate with its defaults, so only the command's author can flip them. Invalid settings, and help with no visible entries, return ConfigurationError without sending anything

(options?: CommandContextHelpOptions)
    => ResultAsync<
        PaginateResult<M>,
        CancelledError | PaginateFailure
    >(options?: CommandContextHelpOptions)
  => ResultAsync<
    PaginateResult<M>,
    CancelledError | PaginateFailure
  >
signal#

Subscription cancellation signal for operations that support it

Cancellation prevents later router callbacks but cannot forcibly stop pending application promises

AbortSignal

PrefixCommandCooldown#

Limit how often a known command executes, per user by default

Claimed after its guards and argument conversion succeed, immediately before execution. The router does not release an acquired claim after handler failure or cancellation

interface PrefixCommandCooldown<
    S extends CommandArgumentSchema = { … },
    M extends MessageCore = Message
>interface PrefixCommandCooldown<
  S extends CommandArgumentSchema
    = { … },
  M extends MessageCore = Message
>
durationMs#

Whole milliseconds per reservation, from 1 through 2,147,483,647

number
key optional#

Return a nonempty key suffix from the converted execution context, replacing the one selected by per

The router adds the command's canonical name or full group path, so aliases share a reservation and different groups remain separate

(context: PrefixCommandExecutionContext<S, M>) => string(
  context: PrefixCommandExecutionContext<
    S, M
  >
) => string
per optional#

Whose invocations share the cooldown: user (default), channel or guild

CommandCooldownPer
store optional#

Store whose claim performs the reservation

Omit it to use the router's own memory store in this process, sized by the router's cooldowns option and shared by every router derived from the same create

CooldownStore

PrefixCommandDefinition#

Names, help text and positional arguments for a command such as !repeat hello 3

Each API's command type adds the callback that runs when this name or an alias matches. Registration copies this data, so later edits do not change an existing router. Individual and batch command registration read recognized fields once, including inherited and non-enumerable fields

interface PrefixCommandDefinition
aliases optional#

Other names that run the same command, such as r

Names and aliases must not collide with a command or group in the same parent group

readonly string[]
arguments optional#

Named argument descriptors read in property order after the guard allows execution

Omit this to leave args unconverted and expose empty values

Readonly<Record<string, CommandArgumentDescriptor>>Readonly<
  Record<
    string,
    CommandArgumentDescriptor
  >
>
description optional#

Text placed below the invocation in generated help

The router neither escapes markup nor sends the text

string
hidden optional#

Leave this command out of generated help and unknown-name suggestions, for example for owner-only tools

Defaults to false. A hidden command still runs when invoked by name or alias, so pair it with a guard to restrict who can use it

boolean
name#

Name without the prefix, such as repeat, beginning with an ASCII letter or digit and using only ASCII letters, digits, _ or -

string
usage optional#

Help text after the command name, such as <text> <count>

Overrides argument-derived syntax, including when set to an empty string

string

PrefixCommandExecutionContext#

Matched-command context plus fully converted argument values, supplied to execution and cooldown-key callbacks

interface PrefixCommandExecutionContext<
    S extends CommandArgumentSchema = { … },
    M extends MessageCore = Message
>
    extends PrefixCommandContext<M>interface PrefixCommandExecutionContext<
  S extends CommandArgumentSchema
    = { … },
  M extends MessageCore = Message
>
  extends PrefixCommandContext<M>
values#

Frozen values keyed by schema names, such as values.count

Empty when no schema is supplied. Guards and rejection callbacks never receive partial values

CommandArgumentValues<S>

Inherited from PrefixCommandContext: args, client, help, message, name, paginate, path, prefix, rawArgs, reply, sendHelp, signal

PrefixCommandGroupDefinition#

A named parent for commands, such as admin in !admin inspect

Register the group before adding children to its canonical path. Groups organize lookup and help only, with no handler, argument conversion, guard or cooldown. Each protected command needs its own guard

interface PrefixCommandGroupDefinition
aliases optional#

Alternative names for entering this group, matched with the router's caseSensitive setting

readonly string[]
description optional#

Description shown below this group's help entry, without markup escaping or automatic delivery

string
hidden optional#

Leave this group and everything inside it out of generated help and unknown-name suggestions

Defaults to false. Help treats a hidden group like a missing one. Its commands still run when invoked by name, so guard each one that needs it

boolean
name#

Group name using command-name syntax

It must not collide with sibling command or group names and aliases

string

PrefixCommandGroupMetadata#

Frozen group information copied at registration, including its registered-name path for adding children or selecting help

interface PrefixCommandGroupMetadata
    extends PrefixCommandGroupDefinitioninterface PrefixCommandGroupMetadata
  extends PrefixCommandGroupDefinition
kind#

Distinguishes this non-executable group from a command in help visibility callbacks

"group"
path#

Registered names from the outermost group to this one, such as ["admin", "users"], even if the message used aliases

readonly string[]

Inherited from PrefixCommandGroupDefinition: aliases, description, hidden, name

PrefixCommandMetadata#

Frozen command information for inspection and help generation

Contains no callbacks, cooldown store or resource candidates. Help visibility callbacks also receive group entries, identified by kind: "group"

interface PrefixCommandMetadata
    extends Omit<PrefixCommandDefinition, "arguments">interface PrefixCommandMetadata
  extends Omit<
    PrefixCommandDefinition,
    "arguments"
  >
arguments optional#

Frozen argument descriptions used to generate usage text, without candidate objects

readonly CommandArgumentMetadata[]
kind optional#

Present for groups supplied to help visibility callbacks

Executable commands omit this property

"group"
path optional#

Registered names from the outer group through this entry, such as ["admin", "inspect"]

Root commands omit this property

readonly string[]

Inherited from PrefixCommandDefinition: aliases, description, hidden, name, usage

PrefixCommandParse#

Parser result copied and frozen by the router before command callbacks run

interface PrefixCommandParse
args#

Positional string tokens in input order, without array holes

A custom parser defines how these tokens are split

readonly string[]
name#

Command name or alias using command-name syntax in the selected parent

A returned group name does not enter that group

string
rawArgs#

Argument text retained for callbacks

The default parser removes whitespace between name and arguments but preserves the remaining text

string

PrefixCommandParseInput#

Frozen input given to a custom parser after prefix matching and any group lookup

interface PrefixCommandParseInput<M extends MessageCore = Message>interface PrefixCommandParseInput<
  M extends MessageCore = Message
>
message#

Incoming message with the attached client's selected fields

The router does not fetch omitted fields

M
path optional#

Registered parent group names already selected

Absent at root. Returning another name cannot change this parent

readonly string[]
prefix#

Matched prefix as written, such as !! when both ! and !! match

string
source#

Text after the prefix at root, or after consumed group names and their separator whitespace

Remaining text is not rewritten

string

PrefixCommandParseRejection#

A parser result that reports a syntax mistake in text that was meant as a command, such as a quote that never closes

Return it instead of undefined so onUnmatched receives the explanation as reason of CommandParserRejected. The router sends nothing by itself, so the application decides whether to turn the reason into a reply. The router passes the reason on unchanged and logs it at Debug, so keep it fixed text that is safe to show in a channel and leaves the sender's input out. Return undefined instead when the text is probably not a command, such as ordinary chat that begins with the prefix

interface PrefixCommandParseRejection
name optional#

The command name the parser read before the mistake, in command-name syntax, which onUnmatched receives as name so a reply can say which command the mistake was for

Omit it when no name was read. Any other value is reported as a parser failure

string
reason#

Nonempty explanation, such as A double quote was opened but never closed

Any other value is reported as a parser failure

string

PrefixCommandParsing#

Parsing and cooldown settings shared by both API styles, apart from the prefix, which each API resolves in its own way

interface PrefixCommandParsing<M extends MessageCore = Message>interface PrefixCommandParsing<
  M extends MessageCore = Message
>
caseSensitive optional#

Require matching letter case for command and group names and aliases

Defaults to false. This does not change prefix or argument matching

boolean
cooldowns optional#

Size the router's own memory cooldown store, used by commands whose cooldown supplies no store, such as { maxEntries: 50_000 }

Defaults to 10,000 keys, shared by every router derived from the same create. A full store never denies a new key: It removes expired keys first, then the reservation that expires soonest, which lets that key run again early. The same limit bounds the rejections onReject: "reply" remembers so that it does not repeat itself. Invalid options throw ConfigurationError when the router is created

MemoryCooldownOptions
ignoreBots optional#

Skip messages whose author has isBot set, before prefix resolution

Defaults to true

boolean
mentionPrefix optional#

Also accept a mention of the bot, such as @bot ping, as a prefix

Defaults to false. Both mention forms, <@id> and <@!id>, are accepted. The bot's user ID comes from the user in the gateway's READY, so normally no request is needed. When READY did not supply a valid ID, the first message that starts with a mention triggers one users.fetchSelf read, which concurrent messages share. A failed read is logged at Warn in the commands category, and mentions are then treated as ordinary text without a new read for a backoff that starts at 5 seconds and doubles per consecutive failure up to 5 minutes, unless READY supplies the ID first

boolean
parse optional#

Split the command name and arguments, optionally using commands.parseQuoted for quoted words

By default, leading whitespace is trimmed, the name must use command-name syntax and trimmed arguments are split on whitespace. The default preserves rawArgs after removing the command-name separator whitespace. Groups are consumed first using fixed whitespace separators, then this parser runs once on the remaining command text. A custom parser cannot enter another group by returning its name. Return undefined to decline parsing and call optional onUnmatched feedback. Return a PrefixCommandParseRejection instead to decline with a reason that onUnmatched receives, as commands.parseQuoted does for an unclosed quote. A group with no following name reports CommandMissingSubcommand without calling the parser. Throws and malformed results use the attached subscription's error reporting, with no automatic response or retry

(input: PrefixCommandParseInput<M>)
    => PrefixCommandParse | PrefixCommandParseRejection | undefined(input: PrefixCommandParseInput<M>)
  =>
    | PrefixCommandParse
    | PrefixCommandParseRejection
    | undefined

PrefixCommandRegistrationOptions#

Choose the existing parent group for a new command or child group

interface PrefixCommandRegistrationOptions
group optional#

Registered parent names, not aliases, such as ["admin", "users"]

Omit or use [] for root. Missing parents are not created

readonly string[]

PrefixCommandRouter#

Immutable registered commands and groups that can be attached to a client

The register and registerGroup methods return new routers, so use the returned value for subsequent registrations and attachment. Older routers and their active attachments keep the definitions they already had

interface PrefixCommandRouter<M extends MessageCore = Message>interface PrefixCommandRouter<
  M extends MessageCore = Message
>
commands#

Frozen command descriptions in registration order across groups, without callbacks or resource candidates

Grouped entries include paths of registered names

readonly PrefixCommandMetadata[]
groups#

Frozen groups in registration order, including empty groups

These names organize lookup and help, not permission checks

readonly PrefixCommandGroupMetadata[]

attach#

Subscribe this router to messageCreate through the client's existing bounded event system

Returns a Subscription immediately, without connecting the client or creating another queue. Invalid options throw ConfigurationError, and a throwing onError getter throws SdkDefect for commands with code application.defect. On a closing or closed client, attach returns an already-closed Subscription whose waitForClose succeeds, and writes a Warn log record with code events.registeredAfterShutdown. Subscription options control scheduling, overflow and onError reports for callback failures. With partition, the option partitionReady holds a key's messages until that key's own setup is done, as for client.on. The held messages wait in the queue without using a concurrent command slot, and a hook that throws, rejects or returns an Err result reports each waiting message to the router's onError, without a command name, and runs no command for it. A throwing partition function and an overflow that stops the router are reported to onError the same way, in the order the reports arrived. Without an onError they go to the client-level onError or the log. The option without partition throws ConfigurationError. Omitted or undefined settings default to eight concurrent commands and overflow dropOldest, so a burst never stops the router. A failed command is reported with its command name, original error and message IDs to onError, or logged at Error without one. A failed onUnmatched callback, prefix resolver or custom parser is reported the same way without a command name. The router owns one bounded queue for its onError hook, which receives reports one at a time in order, as for client.on. Rejected and unmatched commands are counted and logged at Debug in the commands category. The observe option receives one handler observation per router invocation, including event and command middleware. A matched command supplies its canonical command name, even when a guard denies it or middleware stops it. Reported failures produce outcome failure, cancellation produces cancelled, and other completions produce success. A message with no command match still produces an observation without a command name. Only messages a person typed can run commands. Messages from bots are skipped unless ignoreBots is false, and so is every message whose type is neither Default nor Reply, such as the notice Fluxer posts with a new thread's name as its content. A message without a type, which Fluxer never sends, counts as typed. Each attachment dispatches independently, so attaching twice can run the same command twice. Call subscription.close() to detach it and signal cancellation, without shutting down the client. The returned subscription.waitForClose() observes SDK cleanup, not completion of arbitrary application promises. Cancellation prevents later dispatch stages but cannot forcibly stop application promises already running. The client must have this router's message type M. A full-message router cannot attach to a client with omitted fields

attach(
    client: Client<M>,
    options?: EventHandlerOptions<unknown>
): Subscriptionattach(
  client: Client<M>,
  options?:
    EventHandlerOptions<unknown>
): Subscription

Parameters

client
Client<M>
options optional
EventHandlerOptions<unknown>

help#

Build frozen help-text pages locally, for example router.help({ prefix: "!", maxLength: 2000 })

help(options: CommandHelpOptions): readonly string[]help(
  options: CommandHelpOptions
): readonly string[]

Root help lists immediate commands and groups in sibling registration order. Selecting a group by its registered names shows that group and its immediate children, including empty groups, unless include rejects an ancestor. Entries show full registered-name paths, aliases and descriptions, with (Group) marking groups. Commands and groups registered with hidden: true never appear, nor does anything inside a hidden group, and selecting a hidden group throws ConfigurationError as for a missing one

Arguments generate <required>, [optional] and <rest...> or [rest...] syntax unless explicit usage overrides it. No schema means no inferred argument syntax, and an explicit empty usage suppresses it. A selection that is empty, or whose group or ancestor include rejects, returns []. Nothing is sent, and no prefix resolver, guard, cooldown or handler runs

Pages respect the UTF-16 length limit and end between visible characters, so a character represented by two code units, a flag or a base character with combining marks is not split, but a page can be shorter than the limit and Markdown may still be split. Only a single character longer than the limit is cut, between code points. Page-edge whitespace is trimmed and empty pages are removed, so joining pages is not a lossless reconstruction. The caller selects pages to send and handles mentions

Malformed options, ill-formed text, too-small limits and invalid visibility callbacks throw ConfigurationError. A throwing option getter throws SdkDefect for commands with code application.defect and the thrown value as its cause, while another unexpected fault uses sdk.defect

Parameters

Returns
readonly string[]

register#

Validate and add one executable command to a new router, at root unless options.group selects an existing parent by registered names

Copies metadata and arguments while retaining callbacks and the cooldown store. Names and aliases must not collide with sibling commands or groups under the router's case policy. Invalid definitions, collisions and missing or alias-only parent paths throw ConfigurationError. A throwing getter throws SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect. The original router and its attachments are unchanged

register<
    S extends Readonly<Record<string, CommandArgumentDescriptor>>
        = { … }
>(
    command: PrefixCommand<S, M>,
    options?: PrefixCommandRegistrationOptions
): PrefixCommandRouter<M>register<
  S extends Readonly<
    Record<
      string,
      CommandArgumentDescriptor
    >
  >
    = { … }
>(
  command: PrefixCommand<S, M>,
  options?: PrefixCommandRegistrationOptions
): PrefixCommandRouter<M>

Parameters

command
PrefixCommand<S, M>

registerGroup#

Add a named group to a new router, at root or beneath an existing options.group path of registered names

Register parents before children. Groups accept identity and description only, with no handler, schema, guard or cooldown. At dispatch, group names and aliases are consumed using whitespace separators before the command parser runs. Invalid metadata, missing parents or sibling name collisions throw ConfigurationError. A throwing getter throws SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect. No callback runs and older routers remain unchanged

registerGroup(
    group: PrefixCommandGroupDefinition,
    options?: PrefixCommandRegistrationOptions
): PrefixCommandRouter<M>registerGroup(
  group:
    PrefixCommandGroupDefinition,
  options?: PrefixCommandRegistrationOptions
): PrefixCommandRouter<M>

Parameters

registerMany#

Validate and add a nonempty object of commands to one new router, in the order of its own enumerable string keys and under the same optional parent group

Each object key supplies its command name. Omit arguments to leave raw args unrestricted, or set arguments: {} to reject positional arguments. The router copies every definition before registration. Keys inherited by the batch object are ignored. Recognized fields inside each definition are read once, including inherited and non-enumerable fields. If any definition is invalid or any name collides, the whole call throws ConfigurationError, without a partially registered router or changes to this router and its attachments. The optional parent is validated and snapshotted once for the whole batch. A throwing getter throws SdkDefect for commands with code application.defect, while another unexpected fault uses sdk.defect. Each entry retains its own inferred argument-value type

registerMany<S extends Readonly<Record<string, unknown>>>(
    commands: PrefixCommandBatch<M, S>,
    options?: PrefixCommandRegistrationOptions
): PrefixCommandRouter<M>registerMany<
  S extends Readonly<
    Record<string, unknown>
  >
>(
  commands:
    PrefixCommandBatch<M, S>,
  options?: PrefixCommandRegistrationOptions
): PrefixCommandRouter<M>

Parameters

commands
PrefixCommandBatch<M, S>

PrefixCommandUnmatchedContext#

Frozen message and matched prefix for onUnmatched, when parsing or lookup did not select an executable command

interface PrefixCommandUnmatchedContext<
    M extends MessageCore = Message
>interface PrefixCommandUnmatchedContext<
  M extends MessageCore = Message
>
client#

Attached client available for explicit feedback

The router does not take ownership of its lifetime

Client<M>
message#

Incoming frozen message with the attached client's selected fields only

M
prefix#

Exact prefix selected before parsing declined, name lookup missed or a group needed a subcommand

string
reply#

Reply to the unmatched incoming message through the attached client, with this callback's cancellation signal already applied

A string is sent as the reply's content. Delegates to client.messages.reply, including its validation, deadline, nonce, retry, cache and defect behavior. Cancellation or a lost response can leave the reply posted. An Err does not always mean nothing was sent, and uncertain sends are not replayed. Returning the result from the callback reports an Err like a thrown error

(
    input: string | ReplyInput,
    options?: Omit<RequestOptions, "signal">
) => ResultAsync<M, ConfigurationError | SendError | CancelledError>(
  input: string | ReplyInput,
  options?:
    Omit<RequestOptions, "signal">
) => ResultAsync<
  M,
  | ConfigurationError | SendError
  | CancelledError
>
signal#

Cancellation signal for cooperative feedback work

A promise that ignores it may keep running after the subscription closes

AbortSignal

PrefixCommandsOptions#

Configure how an attached router recognizes command messages, without connecting or subscribing during construction

Prefix and parsing options, plus router-wide middleware, rejection feedback and optional feedback when a command-like message has no executable match

interface PrefixCommandsOptions<M extends MessageCore = Message>
    extends PrefixCommandParsing<M>interface PrefixCommandsOptions<
  M extends MessageCore = Message
>
  extends PrefixCommandParsing<M>
onReject optional#

Rejection feedback for commands that do not set their own onReject

Omit it to give no feedback, except in runBot, which replies by default. Guards returning false never send an automatic reply

PrefixCommandRejectionFeedback<M>
onUnmatched optional#

Handle parser declines, unknown names and groups without a subcommand, optionally sending feedback

An unknown name carries a suggestion when a registered name is close, and a parser decline carries a reason and the command name when the parser explained a syntax mistake, such as an unclosed quote from commands.parseQuoted. Return normally or a promise that completes the work. The router does not interpret a successful return value or send a response. The callback runs for every such message and anyone can cause that, so a reply sent from it needs the application's own limit, and in a channel shared with other bots the prefix may belong to a command of another bot. Throws, rejected promises and returned Err results use the attachment's onError reporting, without retry. Not called for ignored bots or messages without a matching prefix

(
    context: PrefixCommandUnmatchedContext<M>,
    unmatched: PrefixCommandUnmatched
) => unknown(
  context:
    PrefixCommandUnmatchedContext<M>,
  unmatched: PrefixCommandUnmatched
) => unknown
prefix#

Prefix text, accepted-prefix list or per-message resolver, which may return a promise

If several prefixes match, the longest wins

PrefixCommandPrefix<M>
use optional#

Middleware run around every matched command, in order

See PrefixCommandMiddleware

readonly PrefixCommandMiddleware<M>[]

Inherited from PrefixCommandParsing: caseSensitive, cooldowns, ignoreBots, mentionPrefix, parse

Type Aliases#

CommandArgumentDescriptor#

One argument's conversion settings, selected by its type field and validated when the command is registered

type CommandArgumentDescriptor =
    | CommandArgumentText | CommandArgumentInteger
    | CommandArgumentNumber | CommandArgumentBoolean
    | CommandArgumentId | CommandArgumentChoice
    | CommandArgumentUserSelection | CommandArgumentChannelSelection
    | CommandArgumentRoleSelection | CommandArgumentMember
    | CommandArgumentDuration | CommandArgumentCustomtype CommandArgumentDescriptor =
    | CommandArgumentText
    | CommandArgumentInteger
    | CommandArgumentNumber
    | CommandArgumentBoolean
    | CommandArgumentId
    | CommandArgumentChoice
    | CommandArgumentUserSelection
    | CommandArgumentChannelSelection
    | CommandArgumentRoleSelection
    | CommandArgumentMember
    | CommandArgumentDuration
    | CommandArgumentCustom

CommandArgumentMention#

Additional complete-token mention form accepted by an id descriptor

User mentions are <@123> or <@!123>, channel mentions are <#123> and role mentions are <@&123>. Mention IDs follow the same rules as plain id tokens: Nonzero, without leading zeroes and no larger than 9223372036854775807. Surrounding text is not accepted

type CommandArgumentMention = "user" | "channel" | "role"type CommandArgumentMention =
    "user" | "channel" | "role"

CommandArgumentOptionalValue#

The converted value type V, with undefined added when descriptor D sets optional: true

type CommandArgumentOptionalValue<
    D extends CommandArgumentDescriptor,
    V
> = D extends { readonly default: number }
    ? V
    : D extends { readonly optional: true } ? V | undefined : Vtype CommandArgumentOptionalValue<
  D extends CommandArgumentDescriptor,
  V
> = D extends {
  readonly default: number
}
  ? V
  : D extends {
  readonly optional: true
}
  ? V | undefined
  : V

CommandArgumentRejectionReason#

Argument rejection classification without the rejected token

Missing means a required token was absent, Invalid means conversion failed and Ambiguous means multiple candidates matched. Unexpected means positional tokens remained after the schema was consumed

type CommandArgumentRejectionReason =
    "Missing" | "Invalid" | "Ambiguous" | "Unexpected"type CommandArgumentRejectionReason =
    | "Missing" | "Invalid"
    | "Ambiguous" | "Unexpected"

CommandArgumentSchema#

Describe positional arguments by name, such as { text: { type: "text" }, count: { type: "integer" } }

The router reads the schema's own enumerable properties in order, ignoring inherited and non-enumerable properties. Converted values keep those property names. Names must begin with an ASCII letter and use only ASCII letters, digits or _. Required arguments must precede optional arguments, including arguments with a default, and only the final text or custom argument may use rest. Registration validates and freezes a copy, including choices and resource candidates. Omit the schema to accept any unconverted arguments. Use an empty schema to reject all supplied arguments

type CommandArgumentSchema =
    Readonly<Record<string, CommandArgumentDescriptor>>type CommandArgumentSchema =
    Readonly<
      Record<
        string,
        CommandArgumentDescriptor
      >
    >

CommandArgumentType#

Supported positional conversions, performed locally after the guard allows the command

The text, id and choice types produce strings, numeric types and duration produce numbers and boolean produces a boolean. The member type produces a member reference in the message's community, and custom produces whatever its synchronous parse function returns. The userChoice, channelChoice and roleChoice types select a frozen object from a fixed list of candidates supplied at registration, without a lookup request. For any user, channel or role, use id with a mention form, or member for a member of the message's community

type CommandArgumentType =
    | "text" | "integer" | "number" | "boolean" | "id"
    | "userChoice" | "channelChoice" | "roleChoice" | "choice"
    | "member" | "duration" | "custom"type CommandArgumentType =
    | "text" | "integer" | "number"
    | "boolean" | "id"
    | "userChoice"
    | "channelChoice"
    | "roleChoice" | "choice"
    | "member" | "duration"
    | "custom"

CommandArgumentValue#

TypeScript result type for one descriptor, including undefined when optional: true is selected without a default

Text, IDs and choices return strings, numeric and duration descriptors return numbers and boolean descriptors return booleans. Member descriptors return a MemberReference and custom descriptors return their parse function's value type. Resource descriptors return only the candidate's documented fields, not extra fields from the original object

type CommandArgumentValue<D extends CommandArgumentDescriptor> =
    D extends CommandArgumentText
        ? CommandArgumentOptionalValue<D, string>
        : D extends CommandArgumentInteger | CommandArgumentNumber
        ? CommandArgumentOptionalValue<D, number>
        : D extends CommandArgumentBoolean
        ? CommandArgumentOptionalValue<D, boolean>
        : D extends CommandArgumentId
        ? CommandArgumentOptionalValue<D, string>
        : D extends CommandArgumentChoice<infer C>
        ? CommandArgumentOptionalValue<D, C[number]>
        : D extends CommandArgumentUserSelection
        ? CommandArgumentOptionalValue<D, CommandArgumentUser>
        : D extends CommandArgumentChannelSelection
        ? CommandArgumentOptionalValue<D, CommandArgumentChannel>
        : D extends CommandArgumentRoleSelection
        ? CommandArgumentOptionalValue<D, CommandArgumentRole>
        : D extends CommandArgumentMember
        ? CommandArgumentOptionalValue<D, MemberReference>
        : D extends CommandArgumentDuration
        ? CommandArgumentOptionalValue<..., ...>
        : ... extends ... ? ... : ...type CommandArgumentValue<
  D extends CommandArgumentDescriptor
> = D extends CommandArgumentText
  ? CommandArgumentOptionalValue<
      D, string
    >
  : D extends
  | CommandArgumentInteger
  | CommandArgumentNumber
  ? CommandArgumentOptionalValue<
      D, number
    >
  : D extends CommandArgumentBoolean
  ? CommandArgumentOptionalValue<
      D, boolean
    >
  : D extends CommandArgumentId
  ? CommandArgumentOptionalValue<
      D, string
    >
  : D extends CommandArgumentChoice<
  infer C
>
  ? CommandArgumentOptionalValue<
      D, C[number]
    >
  : D extends CommandArgumentUserSelection
  ? CommandArgumentOptionalValue<
      D, CommandArgumentUser
    >
  : D extends CommandArgumentChannelSelection
  ? CommandArgumentOptionalValue<
      D,
      CommandArgumentChannel
    >
  : D extends CommandArgumentRoleSelection
  ? CommandArgumentOptionalValue<
      D, CommandArgumentRole
    >
  : D extends CommandArgumentMember
  ? CommandArgumentOptionalValue<
      D, MemberReference
    >
  : D extends CommandArgumentDuration
  ? CommandArgumentOptionalValue<
      ..., ...
    >
  : ... extends ... ? ... : ...

CommandArgumentValues#

Frozen converted values keyed by the command's argument names, such as values.count

Available to execution and cooldown-key callbacks only after the entire schema converts successfully. Optional descriptors add undefined to their value type. Guards and rejection callbacks receive raw context instead

type CommandArgumentValues<
    S extends CommandArgumentSchema
> = { readonly [K in keyof S]: CommandArgumentValue<S[K]> }type CommandArgumentValues<
  S extends CommandArgumentSchema
> = {
  readonly [K in keyof S]:
    CommandArgumentValue<S[K]>
}

CommandCooldownClaim#

Outcome of reserving a cooldown, selected by _tag

Only CooldownAcquired permits execution. A denied claim may call onReject, but the router does not wait or retry. Retry times are millisecond timestamps since 1970-01-01 UTC, not wait durations

type CommandCooldownClaim =
    | {
          readonly _tag: "CooldownAcquired"
          readonly retryAtMs: number
      }
    | {
          readonly _tag: "CooldownActive"
          readonly retryAtMs: number
      }type CommandCooldownClaim =
    | {
        readonly _tag:
          "CooldownAcquired"
        readonly retryAtMs: number
      }
    | {
        readonly _tag:
          "CooldownActive"
        readonly retryAtMs: number
      }

CommandCooldownPer#

Whose invocations share one cooldown: The invoking user, the channel, or the guild

A guild cooldown in a direct message applies to that conversation

type CommandCooldownPer = "user" | "channel" | "guild"type CommandCooldownPer =
    "user" | "channel" | "guild"

PrefixCommandBatch#

Commands keyed by name, as registerMany and the runBot commands option accept them

Each value defines one command without its name. Omit arguments to leave raw args unrestricted, or set arguments: {} to reject positional arguments

type PrefixCommandBatch<
    M extends MessageCore,
    S extends Readonly<Record<string, unknown>>
> = {
    readonly [K in keyof S]:
        & Omit<
              PrefixCommand<
                  S[K] extends CommandArgumentSchema ? S[K] : { … },
                  M
              >,
              "name" | "arguments"
          >
        & {
              readonly arguments?: {
                  readonly [A in keyof S[K]]:
                      S[K][A] extends CommandArgumentDescriptor
                          ? S[K][A]
                          : CommandArgumentDescriptor
              }
          }
}type PrefixCommandBatch<
  M extends MessageCore,
  S extends Readonly<
    Record<string, unknown>
  >
> = {
  readonly [K in keyof S]:
    & Omit<
        PrefixCommand<
          S[
            K
          ] extends CommandArgumentSchema
            ? S[K]
            : { … },
          M
        >,
        "name" | "arguments"
      >
    & {
        readonly arguments?: {
          readonly [A in keyof S[K]]:
            S[K][
              A
            ] extends CommandArgumentDescriptor
              ? S[K][A]
              : CommandArgumentDescriptor
        }
      }
}

PrefixCommandGuard#

Decide whether a matched command may run, from raw context before argument conversion

Return true to allow it, false to deny it silently, or { deny: "reason" } to deny it with text that onReject: "reply" sends. A promise of those values is awaited. A false verdict is still counted, logged at Debug and passed to a custom onReject callback, but never sends an automatic reply. The built-in guards cover common checks. Throws, rejected promises, Err results and other values use subscription error reporting

type PrefixCommandGuard<M extends MessageCore = Message> =
    (context: PrefixCommandContext<M>)
        =>
            | PrefixCommandGuardResult
            | PromiseLike<PrefixCommandGuardResult>type PrefixCommandGuard<
  M extends MessageCore = Message
> = (
  context: PrefixCommandContext<M>
) =>
  | PrefixCommandGuardResult
  | PromiseLike<
      PrefixCommandGuardResult
    >

PrefixCommandGuardResult#

A guard's decision: true allows the command, false denies it without an automatic reply, and { deny } denies it with a reason that onReject: "reply" sends to the user

Both denials are counted and logged at Debug. A custom onReject callback receives either denial

type PrefixCommandGuardResult = boolean | { readonly deny: string }type PrefixCommandGuardResult =
    | boolean
    | { readonly deny: string }

PrefixCommandMiddleware#

Run around every matched command of a router, in registration order, before its guards

Call next() to continue and await it to observe completion. Not calling it stops the command, which is logged at Debug. The rest of the chain runs at most once: Calling next() again, even concurrently, returns the first call's promise. When the command fails, next() rejects with the command's own error, the same value its execute callback threw, rejected with or returned in an Err

type PrefixCommandMiddleware<M extends MessageCore = Message> =
    (context: PrefixCommandContext<M>, next: () => Promise<void>)
        => unknowntype PrefixCommandMiddleware<
  M extends MessageCore = Message
> = (
  context: PrefixCommandContext<M>,
  next: () => Promise<void>
) => unknown

A failure of the command is reported once with its command name, whatever the middleware does with the rejection. Rethrowing that same error adds no second report. A different throw, rejection or returned Err from the middleware is reported as a separate failure after the command's, also with the command name. Without a command failure, a middleware failure is reported like a failed command and stops the command if next() was not called yet. The command's failure is still reported when the subscription closes while the middleware is running

PrefixCommandPrefix#

Text that starts a command invocation, such as !, or a list of accepted prefixes

A resolver can choose prefixes from each message, for example from per-community settings, and may return a promise. Return undefined to ignore the message. Prefixes must be nonempty strings, and arrays must be nonempty and contain no holes. Matching is exact at the start of message content, with the longest matching prefix selected

type PrefixCommandPrefix<M extends MessageCore = Message> =
    | string
    | readonly string[]
    | ((message: M)
          =>
              | PrefixCommandPrefixValue
              | PromiseLike<PrefixCommandPrefixValue>)type PrefixCommandPrefix<
  M extends MessageCore = Message
> =
  | string
  | readonly string[]
  | ((message: M)
      =>
        | PrefixCommandPrefixValue
        | PromiseLike<
            PrefixCommandPrefixValue
          >)

PrefixCommandPrefixValue#

Prefix text selected for one message: One prefix, a list of accepted prefixes, or undefined to ignore the message

type PrefixCommandPrefixValue =
    string | readonly string[] | undefinedtype PrefixCommandPrefixValue =
    | string
    | readonly string[]
    | undefined

PrefixCommandRejection#

Why a known command did not execute, passed as a frozen value to its optional onReject callback

Inspect _tag to distinguish a denied guard, cooldown or invalid arguments. The router replies only when onReject: "reply" is selected, and it schedules no retry. That reply is limited: An active cooldown key is answered once until its retryAtMs, and a guard denial once per user and command every 5 seconds. Argument rejections are always answered

type PrefixCommandRejection =
    | {
          readonly _tag: "CommandGuardRejected"
          readonly reason?: string
      }
    | {
          readonly _tag: "CommandCooldownActive"
          readonly retryAtMs: number
      }
    | {
          readonly _tag: "CommandArgumentRejected"
          readonly argument: string
          readonly reason: CommandArgumentRejectionReason
          readonly retryAtMs?: never
      }type PrefixCommandRejection =
    | {
        readonly _tag:
          "CommandGuardRejected"
        readonly reason?: string
      }
    | {
        readonly _tag:
          "CommandCooldownActive"
        readonly retryAtMs: number
      }
    | {
        readonly _tag:
          "CommandArgumentRejected"
        readonly argument: string
        readonly reason: CommandArgumentRejectionReason
        readonly retryAtMs?: never
      }

PrefixCommandRejectionFeedback#

Rejection feedback for a command: "reply" sends a short explanation, such as the missing argument and the command's usage, the guard's deny reason or the cooldown's remaining time

"silent" sends nothing, and the rejection is still logged at Debug. A guard returning false never sends an automatic reply, even with "reply" selected. A function receives the context and rejection instead, including false guard denials

type PrefixCommandRejectionFeedback<
    M extends MessageCore = Message
> =
    | "reply"
    | "silent"
    | ((
          context: PrefixCommandContext<M>,
          rejection: PrefixCommandRejection
      ) => unknown)type PrefixCommandRejectionFeedback<
  M extends MessageCore = Message
> =
  | "reply"
  | "silent"
  | ((
      context:
        PrefixCommandContext<M>,
      rejection:
        PrefixCommandRejection
    ) => unknown)

So that repeated attempts do not make the bot repeat itself, "reply" answers an active cooldown key once until its retry time, and a guard denial once per user and command every 5 seconds. Argument rejections are always answered. A skipped reply is counted with the rejection and logged at Debug in the commands category. A function is called for every rejection, so it can count attempts and apply its own limit, for example with retryAtMs

PrefixCommandUnmatched#

A prefix matched but no executable command was selected

Passed as a frozen value to the router's optional onUnmatched callback, without an automatic response. Ignored bot messages and messages without a matching prefix do not produce this callback

type PrefixCommandUnmatched =
    | {
          readonly _tag: "CommandUnknownName"
          readonly name: string
          readonly path?: readonly string[]
          readonly suggestion?: string
      }
    | {
          readonly _tag: "CommandParserRejected"
          readonly name?: string
          readonly path?: readonly string[]
          readonly reason?: string
      }
    | {
          readonly _tag: "CommandMissingSubcommand"
          readonly path: readonly string[]
      }type PrefixCommandUnmatched =
    | {
        readonly _tag:
          "CommandUnknownName"
        readonly name: string
        readonly path?:
          readonly string[]
        readonly suggestion?:
          string
      }
    | {
        readonly _tag:
          "CommandParserRejected"
        readonly name?: string
        readonly path?:
          readonly string[]
        readonly reason?: string
      }
    | {
        readonly _tag: "CommandMissingSubcommand"
        readonly path:
          readonly string[]
      }