SWAGcommands
Commands

One command, two ways in

Share a callback between message and slash commands.

Use CommandType.BOTH when the same idea should work as /ping and !ping. The callback receives a union: an invocation has either interaction or message, so branch on that before using flow-specific data.

src/commands/ping.ts
import { CommandType } from "swagcommands";
import type { CommandObject } from "swagcommands";

const ping = {
  type: CommandType.BOTH,
  description: "Check whether the bot is responsive",
  aliases: ["p"],
  reply: true,
  callback: ({ interaction, message }) =>
    interaction ? "Slash pong!" : `Pong for ${message.author.username}!`,
} satisfies CommandObject;

export default ping;

The slash side needs deployment; the message side uses the prefix immediately. aliases apply only to messages. description and Discord option metadata are for slash deployment. The same guards run for both flows, although flow-specific precondition classes can implement different methods.

For a more involved command, move shared work into a normal function and keep the callback's branch small. Discord option values and message tokens rarely have exactly the same shape, so normalize them at the edge.