Shiver Framework

High-performance Discord bot framework built on discord.js v14. The same foundation that powers Shiver - built so you can create your own bots with command loading, slash and prefix, middleware, storage, and production-ready defaults.

View on GitHub · Source and README are kept in sync with this page.

v2 - 30+ new modules including AI features, ComponentRouter, Scheduler, Systems, Guards, and more

Overview

Shiver Framework provides the infrastructure for Discord bots: command loading and dispatch, slash and prefix handling, middleware pipelines, storage, settings, health endpoints, and safe response handling.

It does not ship ready-made commands; you implement your bot logic on top of the framework.

  • Minimal abstraction - You work with discord.js types and thin wrappers.
  • Explicit over implicit - Commands are plain modules with a well-defined shape.
  • Performance-first - Event deduplication, optional Redis coordination, efficient caching.
  • Pluggable storage - JSON, Supabase, SQLite, or other backends.

Supported runtimes: Node.js 18+. Primary dependency: discord.js ^14.

Why Shiver Framework

Compared to other Discord frameworks, Shiver keeps a thin layer over discord.js so you keep full control. One file per command with name, data, executeSlash, executePrefix - no class boilerplate.

Slash and prefix are first-class with the same middleware and preconditions. Built-in event and prefix deduplication plus optional Redis lock prevent duplicate responses when multiple processes run.

Multi-instance detection warns you with a suggested pkill so you can stop all and restart once. Central safeRespond and defer handling avoid "interaction already replied" and timeout errors. Pluggable storage (JSON, Supabase, etc.) and a plugin system, event bus, and DI container round out extensibility.

AspectShiver Framework
AbstractionThin layer; you keep full control of interactions and messages.
Command formatSingle file; name, data, executeSlash, executePrefix. No class boilerplate.
DeduplicationBuilt-in; optional Redis lock so only one process handles each message.
Response safetyCentral safeRespond, defer strategies, safe edit/delete.
StoragePluggable adapter; settings and migrations on top.

Download & install

The framework is open source and available on GitHub.

Use the link below and follow the commands for your operating system.

Repository

https://github.com/yuwxd/shiver-framework

Clone or download the repository, then install dependencies and use it in your bot project as shown below.

Linux / macOS (terminal)

Bash
$ git clone https://github.com/yuwxd/shiver-framework.git
$ cd shiver-framework
$ npm install

To use the framework in your bot project (from your bot folder):

Bash (from your bot directory)
$ npm install ./path/to/shiver-framework

Windows (PowerShell)

PowerShell
PS> git clone https://github.com/yuwxd/shiver-framework.git
PS> cd shiver-framework
PS> npm install

From your bot project folder:

PowerShell
PS> npm install .\path\to\shiver-framework

Windows (Command Prompt)

cmd
C:\> git clone https://github.com/yuwxd/shiver-framework.git
C:\> cd shiver-framework
C:\> npm install

Requirements

RequirementVersion
Node.js≥ 18
discord.js^14.25.1

Optional (only if you use the feature): redis (multi-instance, prefix dedup), canvas (images), @discordjs/voice + prism-media (voice), @supabase/supabase-js (Supabase storage), better-sqlite3 (SQLite).

Quick start

Create a client, configure the framework, call init(client), then client.login(process.env.DISCORD_TOKEN).

Commands are loaded from commandsPath; slash and prefix listeners are registered automatically.

JavaScript
const { ShiverFramework, ShiverClient } = require('shiver-framework');
const { GatewayIntentBits } = require('discord.js');

const framework = new ShiverFramework({
    commandsPath: './src/commands',
    prefix: ',',
    ownerIds: ['YOUR_DISCORD_USER_ID']
});

const client = framework.createClient({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent
    ]
});

async function main() {
    await framework.init(client);
    await client.login(process.env.DISCORD_TOKEN);
}

main().catch(console.error);

Creating your first command (step by step)

Step 1: Create the commands directory

Ensure your project has a folder for command files, e.g. src/commands. The framework loads all .js files from commandsPath (recursive; files whose name starts with _ are ignored).

Step 2: Create a command file

Create a new file src/commands/ping.js. Each command file must export a single object with at least name, data (for slash), and one or more of executeSlash, executePrefix.

Step 3: Define slash command data

Use SlashCommandBuilder from discord.js. Set name and description (required by Discord for slash commands).

Step 4: Implement executeSlash

Slash commands are deferred by default, so you must use interaction.editReply(...) to send the response. Do not call interaction.reply() unless you did not defer.

Step 5: Implement executePrefix (optional)

For prefix commands, use message.reply(...). The framework passes args (array of arguments after the command name) and commandName (the alias used).

JavaScript
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
    name: 'ping',
    aliases: ['p'],
    data: new SlashCommandBuilder()
        .setName('ping')
        .setDescription('Check bot latency'),
    async executeSlash(interaction, client) {
        await interaction.editReply({ content: `Pong - ${client.ws.ping}ms` });
    },
    async executePrefix(message, args, client, commandName) {
        await message.reply(`Pong - ${client.ws.ping}ms`);
    }
};

Save the file. When you start the bot, the framework loads it automatically.

Sync slash commands to Discord (on first run or when you change slash definitions) with npx shiver-framework sync or by enabling slash sync in options.

Core concepts

After framework.init(client) you have:

PropertyDescription
framework.commandsCommand registry: load, get slash/prefix, sync to Discord.
framework.containerDI container (e.g. container.set('logger', logger)).
framework.eventsEvent bus: CommandRun, CommandError, CommandBlocked, afterReady, etc.
framework.statsCounters and metrics.
framework.healthLifecycle state and optional HTTP health server.
framework.reloadReload commands from disk without restart.
framework.modalHelper for modals and parsing submissions.
framework.assetsLoad fonts and images from base dir.
framework.settingsGuild/user settings backed by storage.
framework.storagePluggable key-value storage adapter.
framework.pingGateway and REST latency helper.
framework.httpPushSend JSON to external APIs or your website.

Client and init flow

ShiverClient extends discord.js Client with sensible defaults: cache limits and sweepers, REST timeouts and retries, gateway compression. Use framework.createClient(overrides) or framework.getClientOptions(overrides).

Init flow: (1) init(client) binds the client and loads commands from commandsPath; (2) Registers interactionCreate and messageCreate listeners (deduplication prevents double-handling); (3) Optional multi-instance detector starts if enabled; (4) On ready, slash sync (if configured), afterReady, and health.markReady() run.

Command system

framework.commands is the command registry.

  • loadFromDirectory(dirPath) - Load all .js commands from a directory. Returns { loaded, errors }.
  • getSlash(name) - Get command by slash name.
  • getPrefix(name) - Get command by prefix name or alias.
  • getAllSlash() / getAllPrefix() - List all registered commands.
  • syncToDiscord(client, options) - Register slash commands with Discord. Use options.guildIds for guild-specific sync or null for global.
  • getSourcePath(name), removeBySourcePath(path) - For reload.

Slash and prefix flow

Slash: Normalize options → middleware chain (defer, lockdown, blacklist, TOS, premium, rate limit, cooldown, disabled, permissions, preconditions) → executeSlash. Emit CommandRun or CommandError.

Prefix: Skip bot messages; deduplicate by message.id (optional Redis tryAcquirePrefixMessage); resolve prefix via getPrefix(message, framework); parse args (quoted strings supported); same middleware chain → executePrefix.

Context menu and autocomplete

Use ContextMenuCommandBuilder and executeContextMenu for user/message context menus. For autocomplete, set options with setAutocomplete(true) and implement handleAutocomplete; call interaction.respond(choices).

Component handlers

Commands can expose handleButton, handleSelect, handleModalSubmit, etc. InteractionHandler matches customId prefixes to commands. Use framework.buildCustomId(prefix, command, action, userId) for consistent IDs.

Command file shape (reference)

FieldRequiredDescription
nameYes (prefix)Primary name for prefix dispatch.
aliasesNoArray of prefix aliases, e.g. ['p'].
dataYes (slash)SlashCommandBuilder or ContextMenuCommandBuilder.
executeSlash(interaction, client)NoCalled for slash commands. Use editReply if deferred.
executePrefix(message, args, client, commandName)NoCalled for prefix commands.
executeContextMenu(interaction, client)NoFor user/message context menu commands.
handleAutocomplete(interaction, client)NoCall interaction.respond(choices).
handleButton, handleSelect, handleModalSubmitNoComponent handlers; match by customId prefix.
preconditionsNoArray of precondition names or configs.
cooldownNoNumber or object for cooldown middleware.
adminOnlyNoIf true, command is not registered for prefix.
deferStrategy, ephemeralNoOverride framework defaults for this command.

Subcommands: use data.addSubcommand(sub => sub.setName('view').setDescription('...')). In executeSlash, use interaction.options.getSubcommand() and getSubcommandGroup(false) to branch.

Configuration reference

Options are deep-merged with defaults.

Guards and moderation

moderation.checkRoleHierarchy, checkTOS, buildTosReply, isBlacklisted, checkServerBlacklisted, isUserAllowed.

Storage and settings

storage.backend (json | memory | supabase | sqlite | mongodb), storage.path, settings.defaults.guild, settings.defaults.user.

migrationsPath - Directory with migration files (e.g. 001_add_users.js). When set, framework.migrations.run() runs them to evolve storage schema or data safely. If null or path missing, no migrations run.

Health and lifecycle

health.enabled, health.port, health.host, multiInstance (boolean or { groupId?, processMatchPattern? }).

Slash sync and hooks

slashSync.guildIds (null = global), registration.retryOnRateLimit, registration.maxRetries. Hooks: afterReady, afterSlashSync, onCommandRun, onCommandError, onCommandBlocked.

tryAcquirePrefixMessage - Optional async function (messageId) => boolean. Called before running a prefix command; return true to allow execution, false to skip. Use e.g. Redis SET NX so only one process handles each message when multiple bot instances run.

Middleware

Both slash and prefix flows run through a middleware chain. Order (slash): Defer → Lockdown → ServerBlacklist → Blacklist → TOS → RateLimit → Cooldown → Disabled → Permissions → preconditions → command. Prefix skips Defer.

Add custom middleware for every command:

JavaScript
framework.use(async (context, next) => {
    const { interaction, message, command, client } = context;
    await next();
});

Custom middleware

Use framework.use(fn) so the middleware runs for every slash and prefix command. Read or mutate context; call await next() to continue or omit it to stop the chain (and optionally send a reply).

Handlers and response safety

safeRespond(interaction, payload, options) - Central helper for replying. Uses followUp if already replied, editReply if deferred, otherwise reply. Use framework.followUp(interaction, payload) in your code.

Defer strategies: always (defer immediately), whenSlow (defer after threshold if no reply yet), never. Default is whenSlow with 1500 ms threshold.

Event deduplication: the framework ensures each interaction/message is handled only once; optional Redis lock prevents duplicate handling across multiple processes.

Storage and settings

framework.storage - Key-value adapter. Methods: get(namespace, key), set(namespace, key, value, ttl?), delete, has, keys, entries, etc. Backends: json, memory, supabase, sqlite, mongodb.

framework.settings - Guild and user settings. Methods: getGuild(guildId), setGuild, patchGuild, getUser, setUser, patchUser, getGuildPrefix(guildId, fallback?), setGuildPrefix, resetGuildPrefix.

Dynamic prefix example:

JavaScript
const framework = new ShiverFramework({
    commandsPath: './src/commands',
    prefix: ',',
    async getPrefix(message, fw) {
        if (!message.guildId) return ',';
        return await fw.settings.getGuildPrefix(message.guildId, null) ?? ',';
    }
});

Migrations

framework.migrations runs migration scripts from options.migrationsPath against the storage backend and tracks which have run so you can evolve schema or data safely.

Embed colors and user themes

framework.embedHelper lets you store per-user and per-command embed colors in storage so your bot can offer a /settings-style color picker. Colors are stored under the embed_colors namespace using setCommandEmbedColor(userId, color, commandName?) and read with getCommandEmbedColor(userId, commandName?). Use them when building embeds or Components v2 containers.

To parse user input such as hex, RGB, or named colors, use embedHelper.parseUserColor(input). Supported formats: #fff, #ffffff, 255, 0, 128, rgb(255, 0, 128), and names like default, success, error, warning, info, blurple, red, green, blue, yellow, orange, purple, cyan, magenta, white, black, gray/grey. Combine this with a settings command to let users choose default and per-command colors.

Health and custom API routes

framework.health tracks lifecycle and can run an HTTP server when health.enabled is true. Built-in routes: /health, /ready, /live, /metrics, /status.

Custom routes: Call health.addRoute(method, path, handler) before init.

The handler receives (req, res, url) and can return a value to send as JSON.

JavaScript
framework.health.addRoute('GET', '/api/bot', async (req, res, url) => {
    return {
        name: 'MyBot',
        version: '1.0.0',
        commands: framework.commands.getAllSlash().length
    };
});

Enable the server with health: { enabled: true, port: 8080 } in framework options.

Assets

framework.assets (AssetLoader) uses options.assets.baseDir (default process.cwd()). Use setBaseDir(path), preload(directory, opts), registerAllFonts(), and getPath(name) for fonts and images in commands or image generation.

Components v2, modals, pagination

Builders (from components/v2/builders.js): textDisplay, separator, mediaGallery, container, buildMessageContainerV2, buildEmbedLikeV2, buildConfirmContainerV2, buildPaginatedContainerV2. Send with flags: MessageFlags.IsComponentsV2. framework.modal helps build and show modals; framework.paginate(interaction, pages, opts) and framework.confirm(interaction, question, opts) for pagination and yes/no flows.

Reload and debug

framework.reload clears require.cache for command files and calls loadFromDirectory(commandsPath) so you can reload without restart. When options.debug === true, framework.debugPanel (LiveDebugPanel) can attach to the client and log command runs and errors. Use syncToDiscord again after reload if slash definitions changed.

Stats and health

framework.stats records metrics (commands run, errors, messages, interactions); use recordCommandRun, recordCommandError, etc. framework.health tracks lifecycle (markStarting, markReady, markShuttingDown) and runs the HTTP server when enabled. Built-in routes: /health, /ready, /live, /metrics, /status.

Lifecycle and multi-instance

Register cleanup with framework.onShutdown(fn). On SIGINT/SIGTERM the framework runs shutdown handlers.

When multiInstance is enabled, a Redis heartbeat runs; if more than one process is detected, the framework logs once with a suggested pkill so you can stop all and restart. Uses REDIS_URL or REDIS_URI.

Voice and code execution

Voice: framework.voice (VoiceManager) for joining channels and playing audio; options: voice.maxBitrateKbps, maxDurationSeconds. Execute: ExecuteRunner for sandboxed code (e.g. Piston or local); options.execute.backend, timeoutMs, maxCodeLength.

Moderation, anti-crash, sharding

framework.moderation provides role hierarchy checks and helpers for kick, ban, timeout. framework.antiCrash listens for uncaughtException and unhandledRejection. When options.sharding.scriptPath is set, framework.sharding (ShardManager) can spawn a child process for the sharding launcher.

Plugins

framework.plugins (PluginManager): plugins.register(name, plugin), plugins.load(nameOrPlugin, options). A plugin has init(framework, options). Built-in plugins include slash-sync, backup-restore, feature-flags, error-reporter, stats-server, scheduled-tasks, i18n, webhook-logger, logger.

Events and container

framework.events emits CommandRun, CommandError, CommandBlocked, afterReady, afterSlashSync, commandReloaded, commandsReloaded. Subscribe with framework.events.on('CommandRun', ...). framework.container is a key-value DI container; the framework sets client, assets, etc.; your bot can container.set('logger', logger) and middleware/commands use container.get('logger').

Security

  • Token: Load from process.env.DISCORD_TOKEN. Never commit or log it. The framework uses log redaction (safeError, redactSecrets) so tokens never appear in console output.
  • User-facing errors: Show only generic messages (e.g. "This command is currently unavailable"). Log details server-side with safeError.
  • HTTP push: Use framework.httpPush(url, payload, opts) when sending data to external URLs; its error logging is redaction-safe.

Message edit and delete

safeEdit(message, payload, opts?) - Edits a message. Returns the edited message or null. Benign Discord errors (Unknown Message, Cannot edit) are not logged. opts.retryOnce: true retries once on 5xx/429.

safeDelete(message, opts?) - Deletes a message if message.deletable. Returns true/false. opts.reason for audit log.

MessageEditDeleteHelper - For high-frequency updates on the same message, use createMessageEditDeleteHelper({ debounceMs: 120 }). Call helper.edit(message, payload); multiple edits within the window are coalesced into one API call. Use helper.delete(message) to cancel pending edit, helper.flush(key) to send immediately.

CLI

Run from a project that depends on the framework (or from the framework directory):

CommandDescription
npx shiver-framework validate [commandsDir]Validate command files (default: src/commands).
npx shiver-framework sync [token] [commandsDir]Register slash commands with Discord. Token from args or DISCORD_TOKEN.
npx shiver-framework generate <type> <name> [dir]Scaffold command, listener, precondition, or system. type: command, listener, precondition, system.

Testing

CommandTester (testing/CommandTester.js): run commands in isolation with tester.runSlash(commandName, interactionOverrides) or tester.runPrefix(commandName, messageOverrides, args). Mocks (testing/mocks.js): createMockInteraction, createMockMessage, createMockClient, createMockGuild, etc., plus MockStorage and MockEventBus.

Custom IDs and validation

Use framework.buildCustomId(prefix, command, action, userId) for consistent component customIds; framework.parseCustomId(customId) to parse.

Validation helpers: validateFrameworkConfig, validateCommandDefinition, validateCustomId, validatePayload. Helpers: createGenericErrorPayload, createWarningPayload, createSuccessPayload, createCooldownPayload, and others for consistent user-facing messages.

AI rules and development guidelines

Use async/await everywhere; defer early for slow slash commands. Prefer self-explanatory code; minimal code; single response path per flow.

Generic errors only for users; log details server-side with safeError. Ephemeral for sensitive content; consistent customIds; sensible collector timeouts. Respect Discord limits (25 MB per file, 10 files). Finish work fully; verify before finishing (run the bot and fix startup/runtime errors).

Examples

Ping (gateway and REST latency)

JavaScript
await framework.init(client);
const gatewayMs = framework.ping.getGatewayMs();
const restMs = await framework.ping.getRestMs();
const full = await framework.ping.getFullPing();

HTTP push (send JSON to your site or API)

JavaScript
const result = await framework.httpPush('https://your-site.com/api/stats', {
    guilds: client.guilds.cache.size,
    commands: framework.commands.getAllSlash().length
}, { method: 'POST', timeoutMs: 5000 });

Safe response (followUp)

JavaScript
const payload = { content: 'Done.', ephemeral: true };
await framework.followUp(interaction, payload);

FunctionRegistry NEW

Export all registered commands as OpenAI or Anthropic function-calling schemas. Lets AI agents know exactly what your bot can do and call it directly.

JavaScript
const { FunctionRegistry } = require('shiver-framework');

const registry = new FunctionRegistry();
registry.registerAllFromCommands(framework.commands.getAllSlash());

const tools = registry.exportOpenAITools();
const response = await openai.chat.completions.create({ model: 'gpt-4o', tools, messages });

const anthropicTools = registry.exportAnthropicTools();

registry.register('ban-user', 'Ban a user from the server', {
    type: 'object',
    properties: { userId: { type: 'string', description: 'Discord user ID' }, reason: { type: 'string' } },
    required: ['userId']
}, async ({ userId, reason }) => banUser(userId, reason));

await registry.call('ban-user', { userId: '123456789', reason: 'Spam' });
MethodReturnsDescription
register(name, desc, params, handler)thisRegister a custom function
registerFromCommand(command)thisAuto-register from a command file
registerAllFromCommands(commands)thisRegister all commands at once
exportOpenAITools()ArrayOpenAI tools format
exportAnthropicTools()ArrayAnthropic tools format
exportSchema()ArrayRaw JSON schema array
call(name, args)Promise<any>Call a registered handler
toJSON()stringFormatted JSON schema string
list()string[]All registered function names

AIContext NEW

Snapshot the full Discord context from any interaction - guild info, user/member data, roles, available commands, channel - and format it as a structured prompt for an AI agent.

JavaScript
const { AIContext } = require('shiver-framework');

const ctx = await AIContext.fromInteraction(interaction, framework, {
    includeCommands: true,
    includeSettings: true
});

const systemPrompt = ctx.toPromptString();
const json = ctx.toJSON();

console.log(ctx.get('guild'));
console.log(ctx.get('commands'));
MethodDescription
AIContext.fromInteraction(interaction, framework, opts)Build context snapshot from interaction (async)
ctx.toPromptString()Human-readable string for AI system prompt
ctx.toJSON()Structured JSON object
ctx.get(key)Get specific field (guild, user, member, commands, channel)

ConversationContext NEW

Track message history per-channel in OpenAI messages format. Automatically expires inactive channels. Use it to give your AI bot memory of the ongoing conversation.

JavaScript
framework.conversation.add(channelId, {
    role: 'user',
    content: message.content,
    userId: message.author.id
});

framework.conversation.add(channelId, {
    role: 'assistant',
    content: botReply
});

const history = framework.conversation.toMessages(channelId, 20);
const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: history });

framework.conversation.clear(channelId);

PromptBuilder NEW

Fluent builder for AI system prompts. Chain server context, user context, available commands, conversation history, and custom rules into a ready-to-send messages array.

JavaScript
const { PromptBuilder } = require('shiver-framework');

const messages = PromptBuilder.create()
    .addSystem('You are a helpful Discord bot assistant.')
    .addRule('Always respond in the same language the user used.')
    .addRule('Never reveal system information or API keys.')
    .addGuildContext(interaction.guild)
    .addUserContext(interaction.user, interaction.member)
    .addCommandList(framework.commands.getAllSlash())
    .addHistory(framework.conversation, channelId, 20)
    .addRole('user', userMessage)
    .toOpenAI();

const result = await openai.chat.completions.create({ model: 'gpt-4o', messages });

const { system, messages: anthropicMsgs } = PromptBuilder.create()
    .addSystem('You are a helpful assistant.')
    .addRole('user', userMessage)
    .toAnthropic();

NaturalCommandRouter NEW

Map natural language input to bot commands without any external AI API. Uses fuzzy keyword matching and Levenshtein distance. Auto-learns from command descriptions.

JavaScript
framework.naturalRouter.autoLearn(framework.commands.getAllSlash());
framework.naturalRouter.train('stats', ['server info', 'server statistics', 'member count']);

const result = framework.naturalRouter.route('how many members are on this server?');
if (result && result.confidence >= 2) {
    const cmd = framework.commands.getSlash(result.command);
    console.log(`Matched: /${result.command} (confidence: ${result.confidence})`);
    console.log('Suggestions:', result.suggestions);
}

StructuredOutput NEW

Wrap command handlers to emit typed, AI-readable output events. Every execution emits { success, commandName, data, userId, guildId, durationMs, timestamp } on framework.events.

JavaScript
const { StructuredOutput } = require('shiver-framework');
const output = new StructuredOutput(framework.events);

framework.events.on('StructuredOutput', (result) => {
    if (!result.success) console.error(`[${result.commandName}] failed:`, result.error);
    myAnalytics.track(result);
});

module.exports = {
    data: new SlashCommandBuilder().setName('stats').setDescription('Server stats'),
    async executeSlash(interaction, client) {
        return StructuredOutput.format('stats', { memberCount: interaction.guild.memberCount });
    }
};

ComponentRouter NEW

Declarative wildcard routing for buttons, select menus, and modals by customId pattern. Supports * wildcards. Available as framework.router.

JavaScript
framework.router
    .button('ticket:close:*', async (interaction, match) => {
        const userId = match.groups[0];
        await closeTicket(interaction, userId);
    })
    .select('role:assign:*', async (interaction, match) => {
        const roleId = interaction.values[0];
        await assignRole(interaction.member, roleId);
    })
    .modal('report:modal:*', async (interaction, match) => {
        const reason = interaction.fields.getTextInputValue('reason');
        await handleReport(interaction, reason);
    })
    .any('confirm:*', async (interaction, match) => {
        await interaction.deferUpdate();
    });

client.on('interactionCreate', async interaction => {
    if (interaction.isButton()) await framework.router.routeButton(interaction);
    if (interaction.isStringSelectMenu()) await framework.router.routeSelect(interaction);
    if (interaction.isModalSubmit()) await framework.router.routeModal(interaction);
});

WizardSession NEW

Multi-step guided interaction wizard with built-in state management, step navigation, and automatic timeout cleanup.

JavaScript
const { WizardSession } = require('shiver-framework');

const wizard = new WizardSession(interaction, [
    {
        name: 'ticket-reason',
        async run(wizard, i) {
            await i.reply({ content: 'Briefly describe your issue:', ephemeral: true });
            const collected = await i.channel.awaitMessages({
                filter: m => m.author.id === i.user.id, max: 1, time: 60000
            });
            wizard.setData('reason', collected.first()?.content ?? '');
        }
    },
    {
        name: 'ticket-priority',
        async run(wizard, i) {
            wizard.setData('priority', 'normal');
        }
    }
], {
    timeoutMs: 120000,
    onTimeout: async (wiz) => { /* clean up */ },
    onCancel: async (wiz) => { /* handle cancel */ }
});

const data = await wizard.run();
console.log(data.reason, data.priority);

FormBuilder NEW

Fluent builder for Discord modals with built-in validation and field parsing. No manual TextInputBuilder boilerplate.

JavaScript
const { FormBuilder } = require('shiver-framework');
const { TextInputStyle } = require('discord.js');

const form = new FormBuilder('Submit Report', 'report:modal')
    .addField('title', 'Report Title', { required: true, maxLength: 100 })
    .addField('details', 'What happened?', {
        style: TextInputStyle.Paragraph,
        required: true,
        minLength: 20
    })
    .addField('link', 'Message link', {
        required: false,
        regex: /^https:\/\/discord\.com\/channels\//
    });

await interaction.showModal(form.build());

const submitted = await interaction.awaitModalSubmit({ time: 120000 });
const { values, errors, ok } = form.parse(submitted);

if (!ok) {
    return submitted.reply({ content: errors[0].message, ephemeral: true });
}
await handleReport(values.title, values.details);

VoteManager NEW

Full in-memory poll/voting system. Create polls, accept votes, retrieve results with percentages and automatic winner calculation.

JavaScript
const { VoteManager } = require('shiver-framework');
const votes = new VoteManager();

const pollId = votes.create(channelId, ['Yes', 'No', 'Maybe'], {
    title: 'Should we add a music bot?',
    timeoutMs: 300000,
    anonymous: true
});

votes.cast(pollId, userId, 0);
votes.retract(pollId, userId);
votes.cast(pollId, userId, 1);

const results = votes.getResults(pollId);
console.log(results.options);
console.log(`Winner: ${results.winner.label} with ${results.winner.votes} votes`);

const final = votes.end(pollId);

MessageCollector NEW

High-level helper for prompting a user and collecting their response in one call. Supports validation, multi-question sequences, and auto-cleanup.

JavaScript
const { MessageCollector } = require('shiver-framework');

const { content } = await MessageCollector.prompt(
    channel, userId, 'What is your preferred username?',
    {
        timeoutMs: 30000,
        validator: (value) => value.length < 3 ? 'Username must be at least 3 characters.' : true
    }
);

const answers = await MessageCollector.sequence(channel, userId, [
    { key: 'name', question: 'What is your name?' },
    { key: 'age', question: 'How old are you?', validator: v => isNaN(v) ? 'Enter a number.' : true }
]);
console.log(answers.name, answers.age);

UserSessionStore NEW

Per-user key-value session storage with TTL and automatic cleanup. Available as framework.sessions.

JavaScript
framework.sessions.set(userId, 'step', 'confirm', 60000);
framework.sessions.set(userId, 'pendingBanTarget', targetUserId);

const step = framework.sessions.get(userId, 'step');
const all = framework.sessions.getAll(userId);

if (framework.sessions.has(userId, 'pendingBanTarget')) {
    framework.sessions.touch(userId);
    const target = framework.sessions.get(userId, 'pendingBanTarget');
    framework.sessions.delete(userId, 'pendingBanTarget');
}

Scheduler NEW

Named interval, timeout, and cron-like tasks. Auto-destroyed on framework.shutdown(). Available as framework.scheduler.

JavaScript
framework.scheduler.every(30000, 'heartbeat', async () => {
    console.log('[heartbeat] ping:', framework.client.ws.ping);
});

framework.scheduler.cron('0 9 * * *', 'daily-report', async () => {
    await reportChannel.send('Good morning! Daily report:');
});

framework.scheduler.once(5000, 'startup-announce', async () => {
    await announceChannel.send('Bot is online!');
});

framework.scheduler.pause('heartbeat');
framework.scheduler.resume('heartbeat');
framework.scheduler.cancel('heartbeat');

console.log(framework.scheduler.list());

BroadcastManager NEW

Rate-safe mass messaging to multiple channels, guilds, or users with configurable delay between sends. Available as framework.broadcast.

JavaScript
const { sent, failed } = await framework.broadcast.send(
    ['channelId1', 'channelId2', 'channelId3'],
    { content: 'Maintenance in 10 minutes.' }
);
console.log(`Sent: ${sent.length}, Failed: ${failed.length}`);

await framework.broadcast.sendToGuilds(
    ['guildId1', 'guildId2'],
    async (guild) => guild.systemChannel,
    { content: 'Thanks for using our bot!' }
);

await framework.broadcast.dm(
    ['userId1', 'userId2'],
    { content: 'Your subscription has been renewed.' }
);

RequestDeduplicator NEW

Deduplicate parallel identical async calls. If two commands run simultaneously and both fetch guild settings, only one DB call is made and both get the same result.

JavaScript
const { RequestDeduplicator } = require('shiver-framework');
const dedup = new RequestDeduplicator({ ttlMs: 500 });

async function getGuildSettings(guildId) {
    return dedup.run(`settings:${guildId}`, () => db.fetchSettings(guildId));
}

dedup.invalidate(`settings:${guildId}`);
dedup.clear();

SafeExecutor NEW

Three composable wrappers for safe async execution: safeRun (full), withRetry (retry only), withTimeout (timeout only).

JavaScript
const { safeRun, withRetry, withTimeout } = require('shiver-framework');

const data = await safeRun(() => fetchExternalAPI(), {
    retries: 3,
    timeoutMs: 5000,
    delayMs: 500,
    onError: (err) => console.error('[API] error:', err.message),
    fallback: null
});

const result = await withRetry(() => db.query(), 3, 300);
const val = await withTimeout(() => heavyOperation(), 10000);

FeatureFlagManager NEW

Per-guild, per-user, per-channel, and global feature flags stored in your storage backend. Hierarchy: user override > guild override > global default. Available as framework.flags.

JavaScript
framework.flags
    .define('new-leveling', { default: false, description: 'New XP algorithm rollout' })
    .define('beta-music', { default: false });

const enabled = await framework.flags.isEnabled('new-leveling', { guildId });
if (enabled) {
    await newLevelingLogic(interaction);
} else {
    await legacyLevelingLogic(interaction);
}

await framework.flags.enable('beta-music', { guildId: '123456789' });
await framework.flags.disable('beta-music', { userId: specificUserId });
const all = await framework.flags.getAll({ guildId });

CommandDisabledManager NEW

Disable or enable specific commands per-guild at runtime through storage. Works together with EnabledPrecondition.

JavaScript
const { CommandDisabledManager } = require('shiver-framework');
const cmdDisabled = new CommandDisabledManager(framework.storage);

await cmdDisabled.disable('music', guildId);
await cmdDisabled.enable('music', guildId);

const isOff = await cmdDisabled.isDisabled('music', guildId);
const list = await cmdDisabled.getDisabledCommands(guildId);

await cmdDisabled.disableAll(guildId, ['music', 'giveaway']);

HelpGenerator NEW

Auto-generate formatted help text from slash command definitions. No manual documentation needed.

JavaScript
const { HelpGenerator } = require('shiver-framework');

const text = HelpGenerator.generate(framework.commands.getSlash('report'));
const list = HelpGenerator.generateList(framework.commands.getAllSlash());
const modCmds = HelpGenerator.generateCategory(framework.commands.getAllSlash(), 'moderation');

CommandSuggester NEW

Fuzzy matching for prefix command typos using Levenshtein distance. Returns top-N closest matches with distance scores.

JavaScript
const { CommandSuggester } = require('shiver-framework');
const suggester = new CommandSuggester();

const suggestions = suggester.suggest('stast', framework.commands.getAllSlash(), 3);
if (suggestions.length) {
    await message.reply(`Did you mean: ${suggestions.map(s => \`\`,\${s.command}\`\`).join(', ')}?`);
}

DiffTracker NEW

Compare two objects and format the diff as Discord-friendly text. Perfect for settings changed confirmations.

JavaScript
const { diff, formatDiff } = require('shiver-framework');

const before = { color: '#5865F2', prefix: ',', language: 'en' };
const after  = { color: '#ED4245', prefix: '!', language: 'en', theme: 'dark' };

const changes = diff(before, after);
const text = formatDiff(changes);
// ~ **color**: `#5865F2` to `#ED4245`
// ~ **prefix**: `,` to `!`
// + **theme**: `dark`

AlertManager NEW

Define metric-based alerts with polling, thresholds, comparison operators, and cooldowns. Integrates with StatsManager. Available as framework.alerts.

JavaScript
framework.alerts
    .define('high-errors', stats => stats.get('errors') ?? 0, 50, async ({ value }) => {
        await alertChannel.send(`High error rate: ${value} errors`);
    }, { cooldownMs: 300000, compare: 'gt' })
    .define('low-guilds', stats => stats.get('guilds') ?? 0, 5, async ({ value }) => {
        console.warn(`Only ${value} guilds left!`);
    }, { compare: 'lt' });

framework.alerts.startPolling(60000);
await framework.alerts.checkAll();
framework.alerts.stopPolling();

LocaleSync NEW

Apply i18n localizations to slash command definitions for Discord's built-in localization. Reads from your I18n instance.

JavaScript
const { localizeCommand, localizeAll } = require('shiver-framework');

const locales = ['pl', 'de', 'fr'];

const localizedData = localizeCommand(command.data, framework.i18n, locales);
const allLocalized = localizeAll(framework.commands.getAllSlash(), framework.i18n, locales);

ProgressBar NEW

Text-based progress bars and multi-bar displays for Components v2 output.

JavaScript
const { buildProgressBar, buildMultiBar } = require('shiver-framework');

buildProgressBar(750, 1000, { width: 20, showFraction: true });
// ███████████████░░░░░ 75% (750/1000)

buildProgressBar(30, 100, { filled: '▓', empty: '░', prefix: 'XP', showPercent: true });
// XP ▓▓▓▓▓▓░░░░░░░░░░░░░░ 30%

const multibar = buildMultiBar([
    { label: 'CPU', current: 72, total: 100 },
    { label: 'Memory', current: 4200, total: 8192 },
    { label: 'Disk', current: 250, total: 500 }
]);

TableBuilder NEW

Build ASCII tables and export as Discord code blocks.

JavaScript
const { TableBuilder } = require('shiver-framework');

const table = new TableBuilder()
    .addColumn('rank', '#', { width: 4, align: 'right' })
    .addColumn('name', 'Player', { width: 18 })
    .addColumn('score', 'Score', { width: 8, align: 'right' })
    .addRow({ rank: '1', name: 'Alice', score: '9800' })
    .addRow({ rank: '2', name: 'Bob', score: '7400' })
    .addSeparator()
    .addRow({ rank: '3', name: 'Charlie', score: '5200' });

await interaction.reply({ content: table.toCodeBlock(), flags: [] });

ListBuilder NEW

Formatted Discord-markdown lists with sections and per-item bold/code styling.

JavaScript
const { ListBuilder } = require('shiver-framework');

const text = new ListBuilder()
    .addSection('Server Info')
    .add('Name', guild.name, { bold: true })
    .add('Members', guild.memberCount)
    .addBlank()
    .addSection('Settings')
    .add('Prefix', framework.options.prefix, { code: true })
    .add('Language', 'en')
    .build();

TicketSystem NEW

Full ticket channel system: open private channels, assign support roles, close with optional transcript generation.

JavaScript
const { TicketSystem } = require('shiver-framework');
const tickets = new TicketSystem(framework.storage, {
    supportRoles: ['supportRoleId'],
    categoryId: 'ticketCategoryId'
});

const { channel, ok } = await tickets.open(guild, userId, {
    welcome: { content: 'Support will be with you shortly.' }
});

const existingChannel = await tickets.isOpen(guildId, userId);

const { transcript } = await tickets.close(channel, { deleteChannel: true });
console.log(transcript.map(m => `[${m.author}] ${m.content}`).join('\n'));

GiveawaySystem NEW

Full giveaway system with persistent entries, automatic draw timer, and reroll support.

JavaScript
const { GiveawaySystem } = require('shiver-framework');
const giveaways = new GiveawaySystem(framework.storage);

const { id } = await giveaways.start(channel, {
    prize: 'Discord Nitro',
    winnersCount: 2,
    durationMs: 3600000
});

await giveaways.enter(id, userId);
await giveaways.leave(id, userId);

const result = await giveaways.draw(id);
console.log(`Winners: ${result.winners.join(', ')}`);

const rerolled = await giveaways.reroll(id, 1);
console.log(`New winner: ${rerolled.winners[0]}`);

TagSystem NEW

Per-guild custom text tags with variable interpolation. Think YAGPDB-style custom commands backed by your storage.

JavaScript
const { TagSystem } = require('shiver-framework');
const tags = new TagSystem(framework.storage);

await tags.create(guildId, 'rules', 'Welcome {user}! Please read <#channelId>.', userId);
await tags.create(guildId, 'ping', 'Pong! The bot is online.', userId);

const content = await tags.use(guildId, 'rules', { user: interaction.user.username });
await interaction.reply(content);

const allTags = await tags.list(guildId);
const results = await tags.search(guildId, 'rule');
await tags.update(guildId, 'rules', 'New rules content here.');
await tags.delete(guildId, 'rules');

StarboardSystem NEW

Auto-posts popular messages to a starboard channel when they reach a configurable reaction threshold.

JavaScript
const { StarboardSystem } = require('shiver-framework');
const starboard = new StarboardSystem(framework.storage, client);

await starboard.configure(guildId, starboardChannelId, 3, '⭐');

client.on('messageReactionAdd', async (reaction, user) => {
    await starboard.handleReaction(reaction, user);
});

const alreadyPosted = await starboard.isPosted(message.id);

Guards NEW

Lightweight, composable guards for quick checks inside command handlers. Each guard has a .check() method and an optional .middleware() adapter.

JavaScript
const { OwnerGuard, GuildGuard, ChannelGuard, RoleGuard, TimeGuard, RateLimitGuard } = require('shiver-framework');

const ownerGuard = new OwnerGuard(['ownerId1', 'ownerId2']);
if (!ownerGuard.check(interaction.user.id)) {
    return interaction.reply({ content: 'Owner only.', ephemeral: true });
}

const guildGuard = new GuildGuard(['allowedGuildId']);
const channelGuard = new ChannelGuard(['allowedChannelId']);

const modRole = new RoleGuard(['modRoleId', 'adminRoleId']);
if (!modRole.check(interaction.member)) {
    return interaction.reply({ content: 'Mod only.', ephemeral: true });
}

const supportHours = new TimeGuard(9, 17, { timezone: 'Europe/Warsaw' });
if (!supportHours.check()) {
    return interaction.reply({ content: 'Support is only available 9-17 Warsaw time.', ephemeral: true });
}

const spamGuard = new RateLimitGuard(3, 10000);
if (!spamGuard.check(interaction.user.id)) {
    return interaction.reply({ content: 'Too many requests. Try again soon.', ephemeral: true });
}

Discord text formatting

Complete reference for Discord's markdown syntax. All of these work in regular messages and in Components v2 TextDisplay fields.

FormatSyntaxResult
Bold**text**text
Italic*text* or _text_text
Underline__text__text
Strikethrough~~text~~text
Spoiler||text||hidden spoiler
Inline code`text`text
Code block```js\ncode\n```highlighted block
Header H1# Titlelarge heading
Header H2## Titlemedium heading
Header H3### Titlesmall heading
Small header-# textsmall muted header
Blockquote> textsingle-line quote
Multi blockquote>>> textmulti-line quote
Bold + italic***text***text
Masked link[label](url)clickable link text
List item- itembullet list
Timestamp<t:1234567890:R>dynamic time (R=relative, F=full, D=date, T=time, d=short date)
JavaScript - TextDisplay example
{ type: ComponentType.TextDisplay, content: [
    '# Server Stats',
    '',
    '> **Members:** 1,234',
    '> **Online:** 456',
    '',
    '-# Updated <t:' + Math.floor(Date.now()/1000) + ':R>'
].join('\n') }

Emoji & mentions

Format custom emoji and user/role/channel mentions in Discord messages.

TypeSyntaxNotes
Custom emoji (static)<:name:id>e.g. <:shiver:1234567890>
Custom emoji (animated)<a:name:id>e.g. <a:loading:987654321>
Mention user<@userId>Pings the user
Mention role<@&roleId>Pings the role
Mention channel<#channelId>Links the channel
@everyone@everyoneRequires permission
@here@hereOnline members only
JavaScript - emoji usage
const status = online ? '<:online:1234567890>' : '<:offline:9876543210>';
const loading = '<a:loading:1122334455>';

await interaction.reply({
    content: `${loading} Fetching data for <@${userId}> in <#${channelId}>...`
});

Components v2 guide

Components v2 replaces embeds with a composable component tree. Messages use MessageFlags.IsComponentsV2 and cannot contain embeds, content, stickers, or poll.

Container

JavaScript
const { ComponentType, MessageFlags } = require('discord.js');

const container = {
    type: ComponentType.Container,
    accentColor: 0x5865f2,
    components: [
        { type: ComponentType.TextDisplay, content: '# Hello World\n> Welcome to the server!' },
        { type: ComponentType.Separator, divider: true, spacing: 2 },
        { type: ComponentType.TextDisplay, content: 'This is the main content.' }
    ]
};

await interaction.reply({ components: [container], flags: MessageFlags.IsComponentsV2 });

Separator

JavaScript
{ type: ComponentType.Separator, divider: true, spacing: 2 }
// divider: true = visible line; false = spacing only
// spacing: 1 = Small, 2 = Large

MediaGallery (images)

JavaScript
{ type: ComponentType.MediaGallery, items: [
    { media: { url: 'attachment://shiver.png' }, description: 'Alt text' }
] }

await interaction.reply({
    components: [container],
    files: [new AttachmentBuilder(buffer, { name: 'shiver.png' })],
    flags: MessageFlags.IsComponentsV2
});

Section (text + thumbnail side-by-side)

JavaScript
{ type: ComponentType.Section,
  components: [{ type: ComponentType.TextDisplay, content: '**Alice** - Level 42\n> XP: 8,400' }],
  accessory: { type: ComponentType.Thumbnail, media: { url: 'https://cdn.discordapp.com/avatars/...' } }
}

Buttons in ActionRow

JavaScript
const { ButtonStyle } = require('discord.js');

{ type: ComponentType.ActionRow, components: [
    { type: ComponentType.Button, label: 'Confirm', style: ButtonStyle.Success, customId: 'confirm:yes:' + userId },
    { type: ComponentType.Button, label: 'Cancel', style: ButtonStyle.Secondary, customId: 'confirm:no:' + userId },
    { type: ComponentType.Button, label: 'GitHub', style: ButtonStyle.Link, url: 'https://github.com' }
] }

StringSelect dropdown

JavaScript
{ type: ComponentType.StringSelect,
  customId: 'settings:theme:' + userId,
  placeholder: 'Choose a theme...',
  minValues: 1, maxValues: 1,
  options: [
      { label: 'Dark', value: 'dark', description: 'Dark mode', emoji: { name: '🌙' }, default: true },
      { label: 'Light', value: 'light', description: 'Light mode', emoji: { name: '☀️' } }
  ]
}

Rules

  • Always set IsComponentsV2 on both deferReply({ flags }) and editReply.
  • Never mix with embeds, content, stickers, or poll in the same message.
  • Filename in AttachmentBuilder must exactly match the attachment:// reference.
  • accentColor accepts a decimal integer, not a hex string.
  • Max 5 ActionRows per message, max 5 buttons per ActionRow.

Embeds (legacy EmbedBuilder)

Use for legacy compatibility only. All new commands should use Components v2 instead.

JavaScript
const { EmbedBuilder } = require('discord.js');

const embed = new EmbedBuilder()
    .setTitle('Server Info')
    .setDescription('> **Members:** 1,234\n> **Online:** 456')
    .addFields(
        { name: 'Owner', value: '<@ownerId>', inline: true },
        { name: 'Created', value: '<t:1609459200:D>', inline: true }
    )
    .setColor(0x5865f2)
    .setThumbnail(guild.iconURL())
    .setImage('https://example.com/banner.png')
    .setFooter({ text: 'Shiver Framework', iconURL: client.user.displayAvatarURL() })
    .setTimestamp();

await interaction.reply({ embeds: [embed] });

AI rules

Universal guidelines for AI assistants working on Discord bot projects built with Shiver Framework. Copy the block below into your AI system prompt, AGENTS.md, .cursorrules, or .windsurfrules.

AGENTS.md / AI Rules
# Shiver Framework - AI Rules

## Language & Communication
- Communicate with the user in the same language they use to talk to you.
- Use English for all code: variable names, function names, file names, and comments.
- If the user does not specify a language for communication, default to English.
- Questions are always welcome. Ask clarifying questions immediately when something is ambiguous.
- Be 100% aligned with the user's intent. Confirm understanding before implementing if anything is unclear.
- Never respond with acknowledgment phrases like "Great!", "Sure!", "Understood!" - go directly to work.
- Always finish the task completely without stopping to ask "should I continue?".

## Code Quality
- All functions must be async/await. Never use .then() or callbacks.
- No code comments. Write self-explanatory code through proper naming and structure.
- Write the absolute minimum code needed. No extra features, no unused variables.
- Always finish work fully. Do not stop midway or leave stubs.
- Before marking a task done, verify the project starts cleanly with no errors.

## Discord Bot Standards
- All new command output must use Components v2 (ContainerBuilder / raw API objects with ComponentType).
  Never use EmbedBuilder for primary command output in new code.
- Never show raw error messages, stack traces, or technical details to Discord users.
  Use generic user-facing messages and log technical details server-side only.
- All sensitive, private, or user-specific replies must be sent with MessageFlags.Ephemeral.
- For slash commands that do I/O or heavy computation, call deferReply at the very start
  (before any await) so Discord receives an acknowledgment within 3 seconds.
- Use a consistent customId scheme: command:action:userId (e.g. ticket:close:123456789).
- Collectors must have sensible timeout and idle durations. Always clean up in the "end" handler.
- Respect Discord limits: 25MB per file, 10 files per message, 5 buttons per ActionRow.

## Components v2 Rules
- Never mix components messages with embeds, content, stickers, or poll in the same reply.
- Always set MessageFlags.IsComponentsV2 on both deferReply AND editReply.
- The filename in AttachmentBuilder must exactly match the attachment:// reference.
- accentColor accepts a decimal integer (0x5865f2), not a hex string.
- Use Separator with divider: true for visual lines, divider: false for spacing only.

## Security
- Never expose API keys, tokens, secrets, or stack traces in user-facing messages.
- Log errors server-side (console.error) only. Redact sensitive data in all logs.
- All attachment filenames must use the format: shiver.(ext) or shiver_1.png for multiples.

## Framework First
- When shiver-framework is missing a generic capability, improve the framework first
  instead of adding a bot-specific workaround.
- Do not duplicate infrastructure in the bot if the concern belongs in the framework.
- Framework changes must remain broadly useful and reusable - no bot-specific hacks.

## Workspace Facts (customize for your project)
- Commands path: src/commands/
- Entry point: src/index.js
- Framework: shiver-framework (linked locally or installed from npm)
- Bot prefix: , (comma)
- Storage backend: json (or supabase / sqlite / mongo)

Module index

AreaPath
Entry, initsrc/index.js
Command registrysrc/core/CommandRegistry.js
Handlerssrc/handlers/SlashHandler.js, PrefixHandler, InteractionHandler, AutocompleteHandler, ContextMenuHandler
safeRespondsrc/handlers/safeRespond.js
safeEdit, safeDelete, MessageEditDeleteHelpersrc/utils/Helpers.js, src/utils/MessageEditDeleteHelper.js
Middlewaresrc/middleware/*.js
Storage, settingssrc/storage/StorageAdapter.js, src/settings/SettingsManager.js
Health (addRoute)src/lifecycle/Health.js
Ping, HTTP pushsrc/utils/PingHelper.js, src/utils/httpPush.js
Security, redactionsrc/security/redact.js
CLIsrc/cli/index.js
AI - FunctionRegistrysrc/ai/FunctionRegistry.js
AI - AIContextsrc/ai/AIContext.js
AI - ConversationContextsrc/ai/ConversationContext.js
AI - PromptBuildersrc/ai/PromptBuilder.js
AI - NaturalCommandRoutersrc/ai/NaturalCommandRouter.js
AI - StructuredOutputsrc/ai/StructuredOutput.js
Interaction - ComponentRoutersrc/routing/ComponentRouter.js
Interaction - WizardSessionsrc/wizard/WizardSession.js
Interaction - FormBuildersrc/forms/FormBuilder.js
Interaction - VoteManagersrc/voting/VoteManager.js
Interaction - MessageCollectorsrc/collectors/MessageCollector.js
Interaction - UserSessionStoresrc/sessions/UserSessionStore.js
Infrastructure - Schedulersrc/scheduler/Scheduler.js
Infrastructure - BroadcastManagersrc/broadcast/BroadcastManager.js
Infrastructure - RequestDeduplicatorsrc/cache/RequestDeduplicator.js
Infrastructure - SafeExecutorsrc/utils/SafeExecutor.js
Infrastructure - CommandDisabledManagersrc/commands/CommandDisabledManager.js
Infrastructure - FeatureFlagManagersrc/flags/FeatureFlagManager.js
DX - HelpGeneratorsrc/help/HelpGenerator.js
DX - CommandSuggestersrc/help/CommandSuggester.js
DX - DiffTrackersrc/utils/DiffTracker.js
DX - AlertManagersrc/alerts/AlertManager.js
DX - LocaleSyncsrc/locale/LocaleSync.js
UI - ProgressBarsrc/ui/ProgressBar.js
UI - TableBuildersrc/ui/TableBuilder.js
UI - ListBuildersrc/ui/ListBuilder.js
Systems - TicketSystemsrc/systems/TicketSystem.js
Systems - GiveawaySystemsrc/systems/GiveawaySystem.js
Systems - TagSystemsrc/systems/TagSystem.js
Systems - StarboardSystemsrc/systems/StarboardSystem.js
Guardssrc/guards/Guards.js

This page is the reference for the framework. For the repository and README, see github.com/yuwxd/shiver-framework.