Per the vote in the thread, I've been drafting code samples with how you can register commands in Sapphire. Feedback is important, so please reply in the thread attached to this with your thoughts.

ApplicationCommandRegistryStore

This will be present on the container that you can import from @sapphire/framework or @sapphire/pieces or even from pieces that have this.container present. It's purpose is to store all registries and let you use it if you prefer registering application commands outside the Command class. The only function present is acquire, which returns an ApplicationCommandRegistry for a given name.

ApplicationCommandRegistry

This refers to a single command's registry, where you can specify what commands to handle for that command name.

Please note that the register*Command methods accept both an @discordjs/builders builder or an object like how [client.application.commands.create](<https://discord.js.org/#/docs/main/main/class/ApplicationCommandManager?scrollTo=create>) does.

Type interfaces

interface ApplicationCommandRegistry {
	registerChatInputCommand(
		command:
			| ChatInputApplicationCommandData
			| SlashCommandBuilder
			| ((builder: SlashCommandBuilder) => SlashCommandBuilder),
		options?: ApplicationCommandRegistryRegisterOptions
	): ApplicationCommandRegistry;
	registerContextMenuCommand(
		command:
			| UserApplicationCommandData
			| MessageApplicationCommandData
			| ContextMenuCommandBuilder
			| ((builder: ContextMenuCommandBuilder) => ContextMenuCommandBuilder),
		options?: ApplicationCommandRegistryRegisterOptions
	): ApplicationCommandRegistry;

	// For those that have commands already registered
	// Functions that register a name will ALWAYS emit a warning to the console when registering.
	// You should register ids instead
	addChatInputCommandNames(...names: string[] | string[][]): ApplicationCommandRegistry;
	addContextMenuCommandNames(...names: string[] | string[][]): ApplicationCommandRegistry;

	addChatInputCommandIds(...commandIds: string[] | string[][]): ApplicationCommandRegistry;
	addContextMenuCommandIds(...commandIds: string[] | string[][]): ApplicationCommandRegistry;
}

interface ApplicationCommandRegistryRegisterOptions {
	/**
	 * If this is specified, the application commands will only be registered for these guild ids.
	 */
	guildIds?: string[];
	/**
	 * If we should register the command when it is missing
	 * @default true
	 */
	registerCommandIfMissing?: boolean;
	/**
	 * Specifies what we should do when the command is present,
	 * but not identical with the builder you provided
	 * @default RegisterBehavior.LogToConsole
	 */
	actionWhenCommandIsPresentButNotIdentical?: RegisterBehavior;
}

export const enum RegisterBehavior {
	Ignore = 'IGNORE',
	Overwrite = 'OVERWRITE',
	LogToConsole = 'LOG_TO_CONSOLE'
}

Full usage:

Using the registry, you're able to define what application commands ids/names should bind to what command classes you build! Please note that, as this is a draft, there are no live examples of how this is used. If this passes with everyone, I will create a repository that shows how you can use it! 😄

Registering in a general file, not tied to a Command class:

const store: ApplicationCommandRegistryStore = container.applicationCommandRegistryStore;

// Get the registry for a command by name
// NOTE: this refers to the Command#name property in your commands that are present
// in the commands folder.
const registry = store.acquire('ping');

// Register commands 
registry.registerChatInputCommand(
	(builder) =>
		builder
			.setName('ping')
			.setDescription('We are pinging and ponging boys!')
);
registry.registerChatInputCommand(
	(builder) =>
		builder
			.setName('eval')
			.setDescription('Making Ian 2 crawl in his skin! (inside joke)'),
	{
		guildIds: ['123456789012345678'],
		actionWhenCommandIsPresentButNotIdentical: RegisterBehavior.LogToConsole
	}
);

// For those that don't want to give us the full builder / data,
// you can register just names (which will warn you every time), or ids
registry.addChatInputCommandNames('hello-world');
registry.addChatInputCommandIds('123456789012345678');

// The same usage is present for context menus
registry.registerContextMenuCommand(
	(builder) =>
		builder
			.setName('ping')
			.setType(ApplicationCommandType.User)
);
registry.registerContextMenuCommand(
	(builder) =>
		builder
			.setName('eval')
			.setType(ApplicationCommandType.User),
	{
		guildIds: ['123456789012345678'],
		actionWhenCommandIsPresentButNotIdentical: RegisterBehavior.LogToConsole
	}
);

// For those that don't want to give us the full builder / data,
// you can register just names (which will warn you every time), or ids
registry.addContextMenuCommandNames('Boop User');
registry.addContextMenuCommandIds('123456789012345678');

Registering in a Command class

export class Example extends Command {
	public override registerApplicationCommands(registry: ApplicationCommandRegistry) {
		// You already get a registry here, for convenience. You don't have to use this method,
		// you can define it in the constructor too via `this.applicationCommandRegistry`.
		// However, we recommend you use this method instead for organization purposes.
		registry.registerChatInputCommand(
			{
				name: this.name,
				description: this.description
			},
			{
				actionWhenCommandIsPresentButNotIdentical: RegisterBehavior.Overwrite
			}
		);
	}
}

Feedback received