# Commands

Register console and chat commands with `command` / `command.server` / `command.admin` from the `@s2script/sdk` barrel — the spelling `s2s create` scaffolds:

```ts
import { command, ADMFLAG, HookResult } from '@s2script/sdk';
import type { Command, HookResultValue } from '@s2script/sdk';

export function OnPluginStart(): void {
	command('hello', (cmd) => {
		cmd.reply(`hi from slot ${cmd.callerSlot}`);
		return HookResult.Handled;
	});

	command.server('sm_reloadmap', (cmd) => {
		cmd.reply('reloading');
		return HookResult.Handled;
	});

	command.admin('sm_kick', ADMFLAG.KICK, kick);
}

function kick(cmd: Command): HookResultValue {
	if (cmd.argCount < 1) {
		cmd.reply('Usage: sm_kick <target>');
		return HookResult.Handled; // usage errors still consume the command
	}
	return HookResult.Handled;
}
```

- `command` — anyone / console (SourceMod `RegConsoleCmd`)
- `command.server` — server console only (`callerSlot < 0`; SourceMod `RegServerCmd`)
- `command.admin` — flag-gated via the host admin cache (SourceMod `RegAdminCmd`)
- `command.onClientCommand` — observe an existing CLIENT command by name (SourceMod `AddCommandListener`)

`command` is load-window only — it throws after settle. Call it from `OnPluginStart`. CS2 types stay on `@s2script/cs2`; `@s2script/sdk/unsafe` and `@s2script/sdk/console` stay subpaths (not re-exported on the barrel).

## Return values

A command you own is a SourceMod Action callback; ours is `HookResult`. Return `HookResult.Handled` (`Plugin_Handled`) on **every** path, including usage errors — the command was still consumed. Omit the return (`void`) to continue — the same as returning `HookResult.Continue`.

Chat listeners that must pass through — basetriggers, or a bare `rtv` / `nominate` word — return `HookResult.Continue` so the player's line still broadcasts:

```ts
export function OnClientSayCommand(slot: number, text: string, teamonly: boolean): HookResultValue {
	if (text.trim().toLowerCase() === 'rtv') {
		requestRtv(slot);
	}
	return HookResult.Continue; // never suppress — the trigger word still shows
}
```

The `cmd` invocation is engine-generic (`callerSlot`, `args`, `argString`, `reply`). `Command` on `@s2script/sdk` is an alias for `CommandInvocation`. Chat `!` / `/` triggers reach the same registry. Player replies are deferred one frame so they land after the chat line (SourceMod parity).

`cmd.replyT(key, …args)` replies in the caller's language (SourceMod's `%t`). Load the phrase files first — see [Translations](https://s2script.com/docs/concepts/translations). The key is checked at build against those files; a plugin that loads none widens it to `string`. A missing file or an unloaded `@s2script/translations` degrades to the raw key.

Cookbook recipes in the SDK repo are copy-pasteable named-public plugins: `export function OnPluginStart` plus any other publics they need (`OnGameFrame`, `OnClientConnected`, …).

See the [commands module](https://s2script.com/docs/api/modules/commands) and [admin module](https://s2script.com/docs/api/modules/admin).
