feat: Add slashcommand api

This commit is contained in:
2026-06-05 17:12:26 +02:00
parent 4070ef6caf
commit 8ecfc9a1fe
101 changed files with 3526 additions and 147 deletions

View File

@@ -318,6 +318,55 @@ interface PluginApiDomMountRequest {
| `ui.registerEmbedRenderer(id, contribution)` | `ui.embeds` | Adds an embed renderer. |
| `ui.mountElement(id, request)` | `ui.dom` | Mounts plugin-owned DOM into a target element or selector. |
## Slash Commands
Slash commands appear in a Discord-style autocomplete menu when a user types `/` in the chat composer. A command with `scope: 'global'` (the default) is offered in every chat surface, including direct messages; a command with `scope: 'server'` only appears while a chat server is active. The user picks a command from the menu (or types it and presses Enter) and the `run` callback executes with the parsed arguments and the current interaction context.
```ts
type PluginApiSlashCommandScope = 'global' | 'server';
interface PluginApiSlashCommandOption {
description?: string;
name: string;
required?: boolean;
// 'rest' captures all remaining text; otherwise a single whitespace-delimited token
type?: 'string' | 'number' | 'boolean' | 'rest';
}
interface PluginApiSlashCommandContext extends PluginApiActionContext {
args: Record<string, string>; // parsed values keyed by option name
command: string; // invoked name without the leading slash
rawArgs: string; // raw text typed after the command name
}
interface PluginApiSlashCommandContribution {
description?: string;
icon?: string;
name: string;
options?: PluginApiSlashCommandOption[];
run: (context: PluginApiSlashCommandContext) => Promise<void> | void;
scope?: PluginApiSlashCommandScope;
}
```
| Method | Capability | Description |
| ----------------------------------- | -------------- | ------------------------------------------------------------------- |
| `commands.register(id, command)` | `ui.commands` | Registers a `/` slash command for the chat composer. |
| `commands.list()` | `ui.commands` | Lists every slash command currently registered across all plugins. |
```ts
context.subscriptions.push(
api.commands.register('shout', {
description: 'Shout a message in uppercase',
icon: '📢',
name: 'shout',
options: [{ name: 'message', required: true, type: 'rest' }],
run: (slash) => api.messages.send(slash.args.message.toUpperCase()),
scope: 'server'
})
);
```
## Context and Logger
| Method | Capability | Description |

View File

@@ -0,0 +1,114 @@
---
sidebar_position: 12
---
# Slash Commands API
The Commands API lets plugins register `/` slash commands. When a user types `/` in the chat composer, MetoYou shows a Discord-style autocomplete menu of available commands. Selecting a command (click, `Enter`, or `Tab`) runs it — either immediately when it declares no options, or after the user types the requested arguments.
## Required Capabilities
| Method | Capability |
| --------------------------------- | ------------- |
| `commands.register(id, command)` | `ui.commands` |
| `commands.list()` | `ui.commands` |
Every registration returns a disposable. Push it into `context.subscriptions` so the command is removed when the plugin unloads.
## Command Scope
A command's `scope` controls where it appears:
| Scope | Available in |
| ------------------- | --------------------------------------------- |
| `global` (default) | Chat servers **and** direct messages |
| `server` | Only while a chat server is the active surface |
Use `global` for commands that work without a server context (e.g. `/help`, `/shrug`). Use `server` for commands that act on the current server, channel, or members.
## Options and Argument Parsing
Declare `options` to describe the arguments a command accepts. MetoYou parses what the user typed after the command name and passes the result to `run` as `context.args`, keyed by option name.
```ts
interface PluginApiSlashCommandOption {
description?: string;
name: string;
required?: boolean;
// 'rest' captures all remaining text; otherwise a single whitespace-delimited token
type?: 'string' | 'number' | 'boolean' | 'rest';
}
```
- Positional options are filled left-to-right from whitespace-delimited tokens.
- A `rest` option captures all remaining text verbatim (use it last, for free-form text).
- Missing positional values are passed as empty strings.
- The autocomplete menu shows required options as `<name>` and optional ones as `[name]`.
Values arrive as strings; convert `number`/`boolean` types yourself inside `run`.
## Command Context
`run` receives a context that extends the standard action context (`source: 'slashCommand'`) with the invocation details:
```ts
interface PluginApiSlashCommandContext extends PluginApiActionContext {
args: Record<string, string>; // parsed values keyed by option name
command: string; // invoked name without the leading slash
rawArgs: string; // raw text typed after the command name
// inherited: server, textChannel, voiceChannel, user, source
}
```
## Register a Command
```js
export function activate(context) {
const api = context.api;
// Server-scoped command with a free-form message argument.
context.subscriptions.push(
api.commands.register('announce', {
name: 'announce',
description: 'Post an announcement to the current channel',
icon: '📢',
scope: 'server',
options: [{ name: 'message', type: 'rest', required: true }],
run: (slash) => {
api.messages.send(`📢 ${slash.args.message}`, slash.textChannel?.id);
}
})
);
// Global command that works in servers and DMs.
context.subscriptions.push(
api.commands.register('shrug', {
name: 'shrug',
description: 'Append the shrug emoticon',
scope: 'global',
run: () => api.messages.send('¯\\_(ツ)_/¯')
})
);
}
```
`api.messages.send` requires the `messages.send` capability, so the example above declares both `ui.commands` and `messages.send` in its manifest.
## List Registered Commands
```js
const allCommands = context.api.commands.list();
```
Returns every slash command currently registered across all active plugins, including their scope and options.
## Built-in Commands
MetoYou ships first-party commands that are always available without any plugin, such as `/lenny` (posts `( ͡° ͜ʖ ͡°)`). They appear in the same autocomplete menu tagged as **Built-in**. Plugin commands are listed alongside them; if a plugin registers a command with the same name as a built-in, both appear and the user can pick either.
## How Input Is Handled
- Typing `/` opens the menu; typing more characters filters by command name (prefix matches rank first).
- Picking a command **without options** runs it immediately and clears the composer.
- Picking a command **with options** fills `/name ` so the user can type arguments, then `Enter` runs it.
- Slash input is intercepted and never posted as a chat message. Text that starts with `/` but matches no registered command falls through and is sent as a normal message.

View File

@@ -6,6 +6,8 @@ sidebar_position: 11
The UI API lets plugins add pages, settings pages, side panels, channel sections, actions, embed renderers, and controlled DOM mounts.
For `/` slash commands in the chat composer, see the [Slash Commands API](./commands.md) (`api.commands`).
Prefer registered UI contributions over direct DOM mounting. Contribution APIs let Angular render the plugin UI when the matching app surface exists. Direct DOM mounting runs immediately and throws if the target selector is not present.
## Required Capabilities

View File

@@ -37,6 +37,7 @@ Capabilities protect privileged app surfaces. A plugin must declare a capability
| `ui.channelsSection` | `ui.registerChannelSection()` | Adds channel sections. |
| `ui.embeds` | `ui.registerEmbedRenderer()` | Renders custom embeds. |
| `ui.dom` | `ui.mountElement()` | Mounts plugin-owned DOM into app targets. |
| `ui.commands` | `commands.register()`, `commands.list()` | Registers `/` slash commands (global or server scope) and lists registered commands. |
| `storage.local` | `storage.*`, `clientData.*` | Reads and writes plugin-local data. |
| `storage.serverData.read` | `serverData.read()` | Reads local per-user/per-server plugin data. |
| `storage.serverData.write` | `serverData.write()`, `serverData.remove()` | Writes or removes local per-user/per-server plugin data. |

View File

@@ -45,6 +45,61 @@ export function activate(context) {
The action appears as a tile in the server side panel's View plugins menu and runs with `source: 'toolbarAction'`.
## Slash Command Plugin
`toju-plugin.json`
```json
{
"schemaVersion": 1,
"id": "example.slash-commands",
"title": "Slash Commands",
"description": "Registers / commands available from the chat composer.",
"version": "1.0.0",
"kind": "client",
"scope": "client",
"apiVersion": "1.0.0",
"compatibility": {
"minimumTojuVersion": "1.0.0",
"verifiedTojuVersion": "1.0.0"
},
"entrypoint": "./main.js",
"capabilities": ["messages.send", "ui.commands"]
}
```
`main.js`
```js
export function activate(context) {
const { api } = context;
// Global: works in chat servers and direct messages.
context.subscriptions.push(
api.commands.register('shrug', {
name: 'shrug',
description: 'Append the shrug emoticon',
scope: 'global',
run: () => api.messages.send('¯\\_(ツ)_/¯')
})
);
// Server-scoped: only offered while a chat server is active.
context.subscriptions.push(
api.commands.register('announce', {
name: 'announce',
description: 'Post an announcement to the current channel',
icon: '📢',
scope: 'server',
options: [{ name: 'message', type: 'rest', required: true }],
run: (slash) => api.messages.send(`📢 ${slash.args.message}`, slash.textChannel?.id)
})
);
}
```
Typing `/` in the composer opens the autocomplete menu. `/shrug` runs immediately; `/announce <message>` fills the composer so the user can type the announcement before sending. See the [Slash Commands API](./api/commands.md) for option parsing and the command context.
## Settings Page Plugin
```json

View File

@@ -41,6 +41,7 @@ type PluginCapabilityId =
| 'ui.channelsSection'
| 'ui.embeds'
| 'ui.dom'
| 'ui.commands'
| 'storage.local'
| 'storage.serverData.read'
| 'storage.serverData.write'