Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/noskap/kojima-bot/llms.txt

Use this file to discover all available pages before exploring further.

Kojima Bot’s command system is designed to be extended without modifying core files. Any TypeScript or JavaScript file you place directly in src/commands/ is picked up automatically when the bot starts — no registration in a manifest file, no changes to src/index.ts required. A ready-to-copy template lives at src/commands/custom/example.ts.

How command loading works

At startup, the loadCommands() function in src/index.ts reads every .ts and .js file from the top-level src/commands/ directory (non-recursive), excluding index.ts. Subdirectories such as src/commands/custom/ are not scanned — only files placed directly in src/commands/ are loaded. Each file must export a Command object as its default export. The loader checks that the export has both a data property (a SlashCommandBuilder or equivalent) and an execute property (an async handler function). Files missing either property are skipped with a warning in the console. After loadCommands() finishes, meme-image commands are registered via registerMemeSlashHandlers(). All commands are then stored in a Collection<string, Command> keyed by command name and dispatched from the InteractionCreate event handler.

Creating a custom command

1

Create a new file in src/commands/

Name the file after your command, for example src/commands/hello.ts. You can use src/commands/custom/example.ts as a template — copy it up one level to src/commands/.
2

Export a default Command object

Here is the full example from src/commands/custom/example.ts — copy it as a starting point:
src/commands/hello.ts
import { MessageFlags, SlashCommandBuilder } from "discord.js";
import type { Command } from "../index";
import { CONFIG } from "../config";

const command: Command = {
    data: new SlashCommandBuilder()
        .setName("mycommand")
        .setDescription("A custom command example!"),
    async execute(interaction) {
        // You can use CONFIG.ENTITY_NAME here too!
        await interaction.reply({
            content: `Hello! This is a custom command. I see you like ${CONFIG.ENTITY_NAME}s!`,
            flags: MessageFlags.Ephemeral
        });
    }
};

export default command;
Replace "mycommand" with the name of your slash command (lowercase, no spaces) and update the description and handler logic as needed.
3

Register the command with Discord

Run the deploy script to push the new slash command to your guild:
bun run deploy
Commands are registered guild-scoped (using GUILD_ID from .env) and appear in Discord immediately.
4

Restart the bot

The new command file is loaded at startup, so a restart is required for it to become active:
bun start
# or, if using PM2:
pm2 restart kojima-bot

The Command interface

The Command interface is exported from src/index.ts and is the only contract your custom command file needs to satisfy:
export interface Command {
    data: { name: string; toJSON: () => unknown };
    execute: (interaction: import("discord.js").ChatInputCommandInteraction) => Promise<void>;
}
data is typically a SlashCommandBuilder instance — it provides both the name string and the toJSON() method used when registering with Discord’s API. execute receives a fully typed ChatInputCommandInteraction and must return a Promise<void>.

Accessing config

Import CONFIG from ../config to read runtime configuration values in your command handler. The most commonly useful property for custom commands is CONFIG.ENTITY_NAME, which reflects whatever name the server operator has set in .env:
import { CONFIG } from "../config";

// Inside execute:
const entityName = CONFIG.ENTITY_NAME; // e.g. "Kojima", "Cat", "Gnome"
await interaction.reply({ content: `The current entity is: ${entityName}` });
Other available properties include CONFIG.GUILD_ID, CONFIG.CATCH_TRIGGER, and the link-fixup flags. See src/config.ts for the full list.

Accessing the database

Import the db instance and schema tables to read or write bot data from a custom command:
import { db } from "../db";
import { profiles, channels } from "../db/schema";
import { eq, and } from "drizzle-orm";

// Inside execute — read a player's profile for this guild:
const profile = await db.query.profiles.findFirst({
    where: and(
        eq(profiles.userId, interaction.user.id),
        eq(profiles.guildId, interaction.guildId ?? "")
    ),
});

if (profile) {
    await interaction.reply({
        content: `You have caught ${profile.totalCatches} entities total.`,
        flags: MessageFlags.Ephemeral,
    });
}
All schema tables (users, channels, profiles, achievementUnlocks) are available as named exports from ../db/schema.
The src/commands/custom/ directory exists as a template folder and is not gitignored. Place your actual command files directly in src/commands/ so they are picked up by loadCommands().
If your command name collides with a core command (kojima, gamble, profile, colonel, ping) or a meme command, the first loaded one wins and a warning is logged. Choose unique names to avoid silently shadowing built-in functionality.

Build docs developers (and LLMs) love