itsmybot
v0.3.2
Published
SDK for building ItsMyBot plugins.
Maintainers
Readme
itsmybot
Small SDK for authoring ItsMyBot plugins.
Install
npm install itsmybotBundle plugin metadata from src/index.ts exports:
npx itsmybot bundle .Publish generated artifact with Discord browser auth:
npx itsmybot publish .Discord app must allow CLI callback URL. Default:
http://127.0.0.1:8976/callbackCore shape
index.tsdefault plugin export is source of truth for both publish metadata and runtime logicCommand<Config, Options>holds slash-command metadata and typed optionsMessageCreateEvent,GuildMemberAddEvent,MessageReactionAddEvent,VoiceStateUpdateEventgive typed event hooksdefineConfig(...)keepsdefaultConfigtyped without extra schema DSL- plugin metadata lives directly on
Pluginclass fields defineCommandOptions(...)defines slash-command options once
itsmybot bundle reads default plugin export from index.ts and turns it into .itsmybot/manifest.json.
Commands and events are derived from plugin class arrays. Capabilities, storage, and outbound allowlist come from plugin class fields.
Capabilities used at runtime must be declared there too.
Optional presentation assets bundle automatically too:
README.mdicon.png(256x256)screenshots/*.png|jpg|jpeg(max5, max1920x1080)
Command plugin
src/index.ts
import {
Command,
Plugin,
CommandContext,
defineConfig,
defineCommandOptions,
PluginCapability,
PluginCategory,
stringOption,
integerOption,
} from "itsmybot";
export interface Config {
greeting: string;
}
export const defaultConfig = defineConfig<Config>({
greeting: "Hello",
});
export const greeterOptions = defineCommandOptions({
target: stringOption({
description: "Who should receive greeting",
required: true,
minLength: 1,
maxLength: 32,
}),
count: integerOption({
description: "How many times to repeat",
minValue: 1,
maxValue: 5,
}),
});
export class GreeterCommand extends Command<Config, typeof greeterOptions> {
readonly name = "greeter";
readonly description = "Send greeting";
readonly options = greeterOptions;
run(ctx: CommandContext<Config, typeof greeterOptions>) {
const count = ctx.event.options.count ?? 1;
const content = Array.from({ length: count }, () => `${ctx.config.greeting} ${ctx.event.options.target}`).join(" ");
return ctx.reply(content);
}
}
export default new (class GreeterPlugin extends Plugin<Config> {
readonly id = "greeter";
readonly version = "0.1.0";
readonly name = "Greeter";
readonly summary = "Send greeting";
readonly category = PluginCategory.Utilities;
readonly compatibilityDate = "2026-04-18";
readonly capabilities = [PluginCapability.DiscordInteractionReply] as const;
readonly storage = {
greetings: {
indexes: ["target"],
},
};
readonly commands = [new GreeterCommand()];
})();Config widgets
Config UI stays schema-driven. Add JSDoc tags on Config fields to hint richer dashboard widgets.
export interface Config {
/**
* @title Announcement channel
* @widget channel-select
* @channelTypes text,announcement
*/
announcementChannelId?: string;
/**
* @title Moderator roles
* @widget role-select
*/
moderatorRoleIds: string[];
/**
* @title Welcome message
* @widget message-builder
* @rows 8
*/
welcomeMessage: string;
theme: "light" | "dark";
}Supported widget tags now:
@widget textarea@widget message-builder@widget channel-select@widget role-select
Useful extra tags:
@channelTypes text,announcement,voice,stage,forum,media,category,thread@placeholder Your value here@rows 8
Message event plugin
import {
Plugin,
MessageCreateEvent,
MessageCreateContext,
PluginCategory,
} from "itsmybot";
export class MentionResponderEvent extends MessageCreateEvent {
run(ctx: MessageCreateContext) {
if (!ctx.event.mentionedBot) {
return ctx.ok();
}
return ctx.sendMessage("Need help? Run /help");
}
}
export default new (class HelpPlugin extends Plugin {
readonly id = "help-plugin";
readonly version = "0.1.0";
readonly name = "Help Plugin";
readonly summary = "Reply when bot mentioned";
readonly category = PluginCategory.Support;
readonly compatibilityDate = "2026-04-18";
readonly events = [new MentionResponderEvent()];
})();Mixed plugin
import {
Command,
GuildMemberAddEvent,
GuildMemberAddContext,
MessageCreateEvent,
MessageCreateContext,
PluginCapability,
PluginCategory,
} from "itsmybot";
class PingCommand extends Command {
readonly name = "ping";
readonly description = "Reply pong";
run(ctx) {
return ctx.reply("pong");
}
}
class WelcomeEvent extends GuildMemberAddEvent {
run(ctx: GuildMemberAddContext) {
return ctx.ok([], [ctx.info(`Member joined: ${ctx.event.userId}`)]);
}
}
class MentionEvent extends MessageCreateEvent {
run(ctx: MessageCreateContext) {
if (!ctx.event.mentionedBot) {
return ctx.ok();
}
return ctx.sendMessage("Bot online.");
}
}
export default new (class MixedPlugin extends Plugin {
readonly id = "mixed-plugin";
readonly version = "0.1.0";
readonly name = "Mixed Plugin";
readonly summary = "Command and event example";
readonly category = PluginCategory.Utilities;
readonly compatibilityDate = "2026-04-18";
readonly capabilities = [PluginCapability.DiscordMessageWrite] as const;
readonly commands = [new PingCommand()];
readonly events = [new WelcomeEvent(), new MentionEvent()];
})();Testing helpers
import { runPlugin, createCommandInvocation, assertOk } from "itsmybot/testing";
const result = await runPlugin(
plugin,
createCommandInvocation({
commandName: "greeter",
config: { greeting: "Hello" },
options: { target: "Theo", count: 2 },
}),
);
assertOk(result);Stable imports
itsmybotitsmybot/commandsitsmybot/configitsmybot/discorditsmybot/eventsitsmybot/testingitsmybot/types
