Search docs
Search modules and symbols
Modules

Docs / API / Modules / commands

Commands

Admin & chat

@s2script/sdk/commands

Load-window command() / command.admin / command.server. Command is an alias for CommandInvocation. Owned handlers return HookResult.Handled.

Import

import { ReplySource } from "@s2script/sdk/commands";

Author-time types ship in the npm package; the engine injects the runtime at plugin load. Add @s2script/sdk to your plugin's dependencies.

API reference

Generated from the shipped type definitions.

type
ReplySource

Where a command was invoked from — SourceMod's *reply source*. Set by the dispatch path and exposed as CommandInvocation.replySource; it is what CommandInvocation.reply routes on. - "server" — the server console or rcon (callerSlot is -1) - "console" — a player's own developer console - "chat" — a ! or / chat trigger

interface
CommandInvocation

The parsed invocation handed to a command callback: who called it, its arguments (multiple typed accessors), and a caller-appropriate reply channel. It is a plain object that captures no native handle, so it MAY be retained and used after an await/.then — a deferred reply (e.g. from inside delay(...).then(...) or an async DB/HTTP call) is safe to call once the awaited work completes. What it captures, though, is the caller's **slot** — not a stable identity. If the original caller disconnects before the deferred reply runs and a different player has since taken that slot, the reply routes to (or targets the console/chat channel of) whoever now occupies it, not the original caller. Prefer replying synchronously where the timing matters, or re-check the caller (e.g. via their user id) before trusting a slot held across a long-running await.

readonly callerSlot: number
0-based caller slot, or -1 for the server console.
readonly replySource: ReplySource
Where this invocation came from — what CommandInvocation.reply routes on.
readonly args: string[]
argString split on whitespace (0-based; the command name is NOT included). Kept for compat.
readonly argString: string
everything after the command name (raw) — SM GetCmdArgString.
readonly argCount: number
number of whitespace-split arguments — SM GetCmdArgs.
arg(n: number): string
the nth argument (0-based), or "" if absent — SM GetCmdArg.
argInt(n: number, fallback?: number): number
the nth argument parsed as an integer, or fallback (default 0) if absent/non-numeric.
argFloat(n: number, fallback?: number): number
the nth argument parsed as a float, or fallback (default 0) if absent/non-numeric.
argsFrom(n: number): string
every argument from index n onward, re-joined with a single space (a reason/message/value that spans spaces).
reply(message: string): void

Reply to the caller in the channel they used — SourceMod's ReplyToCommand. Routed by CommandInvocation.replySource: "server" → the server console, "console" → the caller's own developer console (both control-bytes-stripped), "chat" → their chat, one frame later. Chat.color's global prefix applies to the chat path only and never decorates a console reply. To pin a channel regardless of how the command was invoked, use * CommandInvocation.replyToChat or CommandInvocation.replyToConsole.

// `!help` answers in chat; `sm_help` typed at a console answers in that console.
command("sm_help", (cmd) => { cmd.reply("[SM] Commands: …"); return HookResult.Handled; });
replyToChat(message: string): void
Force the reply into the caller's chat, whichever channel they actually used — SM PrintToChat. Sent raw (colour is content you own) and deferred one frame, so a !cmd answer lands *after* the player's own chat line rather than above it. The server console (callerSlot -1) has no chat channel and degrades to the server console.
replyToConsole(message: string): void
Force the reply into the caller's developer console — SM PrintToConsole. Control bytes are stripped (a chat colour *is* a control byte, and renders as garbage in a console), and the line is printed immediately. The server console (callerSlot -1) prints to the server console.
replyT(key: PhraseKey, ...args: (string | number)[]): void
Reply to the caller, translated for THEIR language (SM's %t on the reply path). Soft-deps @s2script/translations — degrades to the raw key if it isn't loaded. key is checked against this plugin's phrase file plus the shared one (see @s2script/sdk/phrases); it widens to string in a plugin that has no phrase file, so this is never in the way.
type
Command

Alias for CommandInvocation — the SourceMod-shaped name for a parsed invocation.

type
CommandHandler

A command callback: the parsed invocation, plus an optional HookResultValue. SourceMod Plugin_Handled is HookResult.Handled — return it from a command you own (usage errors included; the command was still consumed). Omit the return (void) to continue — the same as returning HookResult.Continue. Engine SUPERCEDE-on-Continue is not this API; chat ! vs / suppress is unchanged.

const
command

Register a public command in the load window (SourceMod RegConsoleCmd). Callable only while OnPluginStart is running — throws after settle. .admin / .server are the RegAdminCmd / RegServerCmd shapes: name the handler anything.

import { command, ADMFLAG, HookResult } from "@s2script/sdk";
export function OnPluginStart(): void {
  command.admin("sm_kick", ADMFLAG.KICK, kick);
}
function kick(cmd: CommandInvocation): HookResultValue {
  cmd.reply("kicked");
  return HookResult.Handled;
}
admin(name: string, flags: number, handler: CommandHandler): void
Register an admin command gated by flags (an ADMFLAG bitmask).
server(name: string, handler: CommandHandler): void
Register a server-only command (console/rcon, not client-runnable).
onClientCommand(name: string, handler: (slot: number, argString: string) => HookResultValue | void): void
Observe an existing CLIENT command by name — SourceMod's AddCommandListener. Load-window only. Same contract as CtxCommands.onClientCommand (on PluginContext).
interface
ChatTrigger

A parsed chat trigger: which command + args, and whether it was the silent (/) trigger.

readonly silent: boolean
true = the silent trigger (/, hidden); false = the public trigger (!).
readonly name: string
the command name (the first token after the trigger char; NOT sm_-prefixed).
readonly argString: string
everything after the command name.
const
Commands

Command-registry utilities: dispatch by name, parse/route chat triggers, and enumerate the global registry. Commands themselves are registered in the load window via command.

import { Commands } from "@s2script/sdk";
// sm_help backend: every registered command + its required admin flag mask.
const cmds = Commands.list().slice().sort((a, b) => (a.name < b.name ? -1 : 1));
dispatch(name: string, slot: number, argString: string, replySource?: ReplySource): boolean
Invoke a registered command by name in THIS plugin (applying its gating). Returns true if it exists. replySource sets where the command's CommandInvocation.reply lands. Omit it and it falls back to the slot — the server console at -1, else that player's own console, matching SourceMod's FakeClientCommand. Pass "chat" when re-dispatching on a player's behalf from a chat context.
parseChatTrigger(message: string): ChatTrigger | null
Parse a chat message for a trigger (!//). Returns the parsed trigger, or null if it's ordinary chat.
handleChatTrigger(slot: number, message: string): { silent: boolean; ran: boolean } | null
If message is a trigger, dispatch the command (tries name then sm_<name>) as slot; returns { silent, ran } (the caller should suppress the chat message), or null if it was ordinary chat. Always dispatches with replySource "chat" — including the silent / trigger, where silent suppresses the player's own line but the answer still belongs in chat.
readonly triggers: { public: string; silent: string }
The trigger characters — SM PublicChatTrigger ("!") / SilentChatTrigger ("/"). Mutate to reconfigure.
onClientCommand(name: string, handler: (slot: number, argString: string) => HookResultValue | void): void

Observe a CLIENT command by name — SourceMod's AddCommandListener. Unlike command, which CREATES a command, this hooks one that already exists — including commands the engine itself owns (player_ping, jointeam, drop, buy). Registering a ConCommand of an engine-owned name fails outright ("unable to link multiple ConCommands named X"), so this is the only way to see those. OBSERVE-BY-DEFAULT: the engine still handles the command as normal. Return >= HookResult.Handled to suppress its engine-side handling — but note that for a command with a visible effect (a ping marker, a team change) suppressing is usually NOT what you want.

// Middle-mouse ping opens the shop, and still places the ping.
command.onClientCommand("player_ping", (slot) => { openShop(slot); });
list(): { name: string; flags: number }[]
Every globally-registered command with its required admin flags: 0 = anyone, -1 = console/server-only, else the ADMFLAG bit mask (map bits→names in your plugin). The sm_help backend.
Back to modules All packages

s2script — Source 2 plugin framework

GitHub