Skip to main content

Register plugin slash commands

Task

Register a custom slash command from a plugin so it appears in /help, tab-complete, and ethos skill list across every surface.

Result

A /weather command that users can invoke from CLI, web, Telegram, Discord, and Slack. The command shows in /help output and responds with structured text.

Prereqs

  • A working plugin with activate() exported (see Create a plugin).
  • @ethosagent/plugin-sdk installed.
  • Ethos running locally (pnpm dev or ethos).

Steps

1. Call registerSlashCommand in activate()

The PluginApi exposes registerSlashCommand(). Pass a name, description, usage string, and an async handler.

export function activate(api: EthosPluginApi) {
api.registerSlashCommand({
name: 'weather',
description: 'Get current weather for a city',
usage: '/weather <city>',
handler: async (args, ctx) => {
const city = args.trim() || 'London';
const data = await fetchWeather(city);
return `${city}: ${data.temp}°C, ${data.conditions}`;
},
});
}

The name field is the command text users type after /. It must be unique across all loaded plugins.

2. Understand the handler signature

The handler receives two arguments:

(args: string, ctx: SlashCommandContext) => Promise<string>

args is the raw text after the command name. For /weather London, args is "London". Parse it however you need — the framework does no argument splitting.

The return value is sent as the command's reply. Return a string; the framework handles formatting per platform.

3. Use SlashCommandContext

The ctx object provides session state and platform utilities.

FieldTypeDescription
sessionIdstringActive session key.
personalityIdstringCurrent personality id.
platformstring'cli', 'web', 'telegram', 'discord', or 'slack'.
send()(text: string) => Promise<void>Send an intermediate reply before the final return.
toolRegistryToolRegistryAccess to the tool registry for the current session.
storageStorageScoped storage for persisting command state.

4. Send multi-part replies with ctx.send()

For long-running commands, send progress updates before returning the final result. Each ctx.send() call delivers an intermediate message to the user immediately.

handler: async (args, ctx) => {
const cities = args.split(',').map((c) => c.trim());
for (const city of cities) {
const data = await fetchWeather(city);
await ctx.send(`${city}: ${data.temp}°C, ${data.conditions}`);
}
return `Done — checked ${cities.length} cities.`;
},

The final return value is always sent as the last message. Calls to ctx.send() appear before it in conversation order.

5. Handle platform-specific behavior

Each channel adapter processes command registration differently.

PlatformBehavior
CLI / WebCommands register instantly. Tab-complete works immediately.
TelegramCommand names are sanitized: lowercased, spaces replaced with underscores, truncated to 32 characters. My Command becomes my_command.
DiscordApplication commands are re-registered with the Discord API on plugin load. This can take up to one hour to propagate globally.
SlackCommands are logged for manual registration. Add them to your Slack app's slash command configuration in the Slack API dashboard.

If your command name contains uppercase letters or spaces, test on Telegram first — the sanitized name may differ from what you expect.

6. Build and install

pnpm build && ethos plugin install .

Verify

Run these commands to confirm registration:

ethos skill list # command appears in the skill/command list

Then in a chat session:

  • Type /help — the command appears with its description and usage.
  • Type /wea and press Tab — the command auto-completes.
  • Type /weather Paris — the handler executes and returns a response.

Troubleshoot

SymptomCauseFix
Command missing from /helpPlugin not loaded or activate() threw.Run ethos plugin list and check the plugin appears. Check logs for activation errors.
Duplicate command name errorAnother plugin registered the same name.Rename the command or uninstall the conflicting plugin.
Tab-complete works but command returns nothingHandler returned undefined or an empty string.Ensure the handler returns a non-empty string.
Telegram shows wrong command nameName was sanitized (lowercase, underscores, 32 chars).Use a name that survives sanitization unchanged.
Discord command not visibleApplication command propagation delay.Wait up to one hour, or test with a guild-scoped command.
ctx.send() messages appear after the final replyPlatform does not support ordered intermediate messages.Acceptable on some platforms — the final return value is always last.

See also