The rationale behind command categories, the subcommand pattern, and how commands are structured.
Commands are grouped by purpose to serve the VAIT community:
Lightweight, stateless commands for community engagement: 8ball, allcap, cowsay, disclaimer, danh-someone, insult, mock-someone, powerball, quote-of-the-day.
These commands have no database dependency, making them simple to implement and test. They exist to keep the community chat lively.
Database-backed features that provide ongoing value: reputation, referral, reminder, weather, aoc-leaderboard.
These commands persist state across sessions. The reputation system encourages positive interactions, referrals share value within the community, and reminders provide personal utility.
Admin-only commands for server management: moderate-users (removeuserbyrole), server-settings (reminder channel, honeypot channel, AoC settings), autobump-threads.
These require permission checks before execution and affect server-wide configuration.
Commands with multiple related operations use discord.js subcommands rather than separate top-level commands. This keeps the command namespace clean and groups related functionality.
When to use subcommands:
- The operations share a domain (e.g. reputation: check, give, take, set, leaderboard)
- The operations share utility functions or database models
- Grouping improves discoverability for users
When to keep commands separate:
- The command does one thing only (e.g.
8ball,cowsay) - The operations are unrelated
Current commands using subcommands: reputation (5 subcommands), referral (2 subcommands), reminder (5 subcommands).
Every command implements the SlashCommand interface:
interface SlashCommand {
data: SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandsOnlyBuilder;
execute: SlashCommandHandler;
autocomplete?: AutocompleteHandler;
}The data field uses discord.js builders to define the command name, description, options, and permissions. The execute field is the async handler that processes the interaction.
Subcommands implement a separate Subcommand interface with a SlashCommandSubcommandBuilder for their data field.
The bot also supports context menu commands (right-click actions on messages or users) via the ContextMenuCommand interface. These are registered separately in src/context-menu-commands/index.ts. Currently no context menu commands are defined, but the infrastructure is in place.
- One directory per command — keeps commands self-contained and easy to find
- Co-located tests — tests live next to the code they test, not in a separate tree
- Default exports for commands — the main
SlashCommandobject is the default export, utility functions use named exports - Result types over exceptions — explicit error handling using oxide.ts
Ok/Errmakes error paths visible in the type system - Lazy user creation — database-backed commands create user records on first interaction rather than requiring pre-registration