stoatx 0.7.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,273 +6,12 @@ A high-performance, decorator-based command handler for Stoat bots. Inspired by
6
6
 
7
7
  - **Decorator-based** - Use `@Stoat()` and `@SimpleCommand()` decorators like discordx
8
8
  - **Guards** - Built-in guard system for permissions and checks
9
- - **Cooldowns** - Per-command cooldown support
9
+ - **Cooldowns** - Per-command cooldown support with custom storage support
10
10
  - **Organized** - Group multiple commands in a single class
11
11
  - **Type-safe** - Full TypeScript support
12
12
 
13
- ## Installation
14
-
15
- ```bash
16
- npm install stoatx reflect-metadata
17
- # or
18
- pnpm add stoatx reflect-metadata
19
- ```
20
-
21
- Make sure to enable decorators in your `tsconfig.json`:
22
-
23
- ```json
24
- {
25
- "compilerOptions": {
26
- "experimentalDecorators": true,
27
- "emitDecoratorMetadata": true
28
- }
29
- }
30
- ```
31
-
32
- ## Quick Start
33
-
34
- ### 1. Create your handler
35
-
36
- ```typescript
37
- // index.ts
38
- import "reflect-metadata";
39
- import { Client } from "stoatx";
40
-
41
- const client = new Client({
42
- prefix: "!",
43
- owners: ["your-user-id"],
44
- });
45
-
46
- await client.initCommands();
47
-
48
- client.login("your-token");
49
- ```
50
-
51
- ### 2. Create commands
52
-
53
- ```typescript
54
- // commands/general.ts
55
- import { Stoat, SimpleCommand, Context } from "stoatx";
56
-
57
- @Stoat()
58
- export class GeneralCommands {
59
- @SimpleCommand({ name: "ping", description: "Check bot latency" })
60
- async ping(ctx: Context) {
61
- await ctx.reply(`Pong! 🏓`);
62
- }
63
-
64
- @SimpleCommand({ name: "hello", aliases: ["hi", "hey"] })
65
- async hello(ctx: Context) {
66
- await ctx.reply(`Hello, <@${ctx.authorId}>!`);
67
- }
68
- }
69
- ```
70
-
71
- That's it! No manual imports needed - decorated command classes are auto-discovered.
72
-
73
- ## Decorators
74
-
75
- ### @Stoat()
76
-
77
- Marks a class as a command container. All `@SimpleCommand()` methods inside will be registered.
78
-
79
- ```typescript
80
- @Stoat()
81
- export class MyCommands {
82
- // commands go here
83
- }
84
- ```
85
-
86
- ### @SimpleCommand(options)
87
-
88
- Marks a method as a command.
89
-
90
- ```typescript
91
- @SimpleCommand({
92
- name: 'ban', // Command name (defaults to method name)
93
- description: 'Ban a user',
94
- aliases: ['b'], // Alternative names
95
- permissions: ['BanMembers'], // This is currently not implemented, but will be in the future
96
- cooldown: 5000, // 5 seconds
97
- cooldownStorage: "database", // A custom flag you can use in your CustomCooldownManager
98
- ownerOnly: false,
99
- nsfw: false,
100
- })
101
- async ban(ctx: Context) {
102
- // logic
103
- }
104
- ```
105
-
106
- ### StoatLifecycle Interface
107
-
108
- Provides intellisense for lifecycle hooks like `onError` and `onCooldown` on your `@Stoat()` classes.
109
-
110
- ```typescript
111
- import { Stoat, SimpleCommand, CommandContext, StoatLifecycle } from "stoatx";
112
-
113
- @Stoat()
114
- export class MyCommands implements StoatLifecycle {
115
- @SimpleCommand({ cooldown: 5000 })
116
- async ping(ctx: CommandContext) {
117
- await ctx.reply(`Pong! 🏓`);
118
- }
119
-
120
- // Called when the command is placed on cooldown
121
- async onCooldown(ctx: CommandContext, remaining: number) {
122
- await ctx.reply(`You are in cooldown, wait ${(remaining / 1000).toFixed(1)} seconds!`);
123
- }
124
-
125
- // Called when an error occurs during execution
126
- async onError(ctx: CommandContext, error: Error) {
127
- console.error(error);
128
- await ctx.reply("An error occurred");
129
- }
130
- }
131
- ```
132
-
133
- ## Custom Cooldowns
134
-
135
- By default, cooldowns are stored in memory. For persistent or distributed setups, you can implement the `CooldownManager` interface to store cooldowns in a database such as Redis, PostgreSQL or MongoDB.
136
-
137
- ```typescript
138
- import { Client, CooldownManager, CommandContext, CommandMetadata, DefaultCooldownManager } from "stoatx";
139
-
140
- class MixedCooldownManager implements CooldownManager {
141
- private memory = new DefaultCooldownManager();
142
-
143
- async check(ctx: CommandContext, metadata: CommandMetadata): Promise<boolean> {
144
- if (metadata.cooldownStorage === "database") {
145
- // Return true if action is allowed in DB
146
- return true;
147
- }
148
- return this.memory.check(ctx, metadata);
149
- }
150
-
151
- async getRemaining(ctx: CommandContext, metadata: CommandMetadata): Promise<number> {
152
- if (metadata.cooldownStorage === "database") {
153
- // Query remaining from DB
154
- return 0;
155
- }
156
- return this.memory.getRemaining(ctx, metadata);
157
- }
158
-
159
- async set(ctx: CommandContext, metadata: CommandMetadata): Promise<void> {
160
- if (metadata.cooldownStorage === "database") {
161
- // Store cooldown expiration in your database
162
- return;
163
- }
164
- this.memory.set(ctx, metadata);
165
- }
166
- }
167
-
168
- const client = new Client({
169
- prefix: "!",
170
- cooldownManager: new MixedCooldownManager(),
171
- });
172
- ```
173
-
174
- ## Guards
175
-
176
- ### @Guard(GuardClass)
177
-
178
- Adds a guard check before command execution.
179
-
180
- ```typescript
181
- import { Stoat, SimpleCommand, Guard, MallyGuard, Context } from "stoatx";
182
-
183
- // Define a guard
184
- class IsAdmin implements MallyGuard {
185
- run(ctx: Context): boolean {
186
- return ctx.message.member?.hasPermission("Administrator") ?? false;
187
- }
188
-
189
- guardFail(ctx: Context): void {
190
- ctx.reply("You need Administrator permission!");
191
- }
192
- }
193
-
194
- @Stoat()
195
- @Guard(IsAdmin)
196
- export class AdminCommands {
197
- @SimpleCommand({ name: "shutdown" })
198
- async shutdown(ctx: Context) {
199
- await ctx.reply("Shutting down...");
200
- }
201
- }
202
- ```
203
-
204
- ## Context
205
-
206
- The `Context` object provides:
207
-
208
- ```typescript
209
- interface Context {
210
- client: Client; // Stoat client instance
211
- message: Message; // Original message
212
- content: string; // Raw message content
213
- authorId: string; // Author's user ID
214
- channelId: string; // Channel ID
215
- serverId?: string; // Server/Guild ID
216
- args: string[]; // Parsed arguments
217
- prefix: string; // Prefix used
218
- commandName: string; // Command name used
219
-
220
- reply(content: string): Promise<void>;
221
- }
222
- ```
223
-
224
- ## Handler Options
225
-
226
- ```typescript
227
- interface StoatxHandlerOptions {
228
- client: Client;
229
- commandsDir?: string; // Legacy mode: explicitly scan this directory
230
- discovery?: {
231
- roots?: string[]; // Default: [process.cwd()]
232
- include?: string[]; // Glob patterns per root
233
- ignore?: string[]; // Additional ignore globs
234
- };
235
- prefix: string ((ctx: { serverId?: string }) => string Promise<string>);
236
- owners?: string[]; // Owner user IDs
237
- extensions?: string[]; // File extensions (default: ['.js', '.mjs', '.cjs'])
238
- disableMentionPrefix?: boolean; // Disable @bot prefix
239
- }
240
-
241
- // Default auto-discovery is discordx-like: scans broadly under process.cwd(),
242
- // then registers only files that look like decorated command modules.
243
- ```
244
-
245
- ## Dynamic Prefix
246
-
247
- ```typescript
248
- const client = new Client({
249
- prefix: async ({ serverId }) => {
250
- // Fetch from database, etc.
251
- return serverId ? await getServerPrefix(serverId) : "!";
252
- },
253
- });
254
-
255
- // Optional: constrain auto-discovery to specific roots/patterns
256
- const scopedClient = new Client({
257
- prefix: "!",
258
- discovery: {
259
- roots: [process.cwd()],
260
- include: ["apps/bot/dist/commands/**/*.js"],
261
- },
262
- });
263
-
264
- // TypeScript source discovery is opt-in and requires a TS runtime loader (tsx/ts-node)
265
- const tsRuntimeClient = new Client({
266
- prefix: "!",
267
- extensions: [".ts"],
268
- discovery: {
269
- include: ["apps/bot/src/commands/**/*.ts"],
270
- },
271
- });
272
- ```
273
-
274
- All commands are defined through `@Stoat()` classes and `@SimpleCommand()` methods.
13
+ For installation instructions, guides, and API references, visit the **[Stoatx Documentation](https://stoatx-ts.github.io/stoatx/)**.
275
14
 
276
15
  ## License
277
16
 
278
- MIT © [Stoatx / Stoatx Team]
17
+ MIT © [Stoatx / Stoatx Team]
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Message, Client as Client$1, PermissionResolvable } from '@stoatx/client';
1
+ import { Message, Client as Client$1, ClientOptions, PermissionResolvable } from '@stoatx/client';
2
2
  export * from '@stoatx/client';
3
3
 
4
4
  interface AutoDiscoveryOptions {
@@ -150,6 +150,7 @@ declare class StoatxHandler {
150
150
  private readonly cooldownManager;
151
151
  private readonly disableMentionPrefix;
152
152
  private readonly client;
153
+ private readonly flagPrefix;
153
154
  constructor(options: StoatxHandlerOptions);
154
155
  /**
155
156
  * Initialize the handler - load all commands
@@ -159,6 +160,10 @@ declare class StoatxHandler {
159
160
  * Attach registered events to the client
160
161
  */
161
162
  private attachEvents;
163
+ /**
164
+ * Parses raw string arguments into positional args and key-value options
165
+ */
166
+ private parseCommandOptions;
162
167
  /**
163
168
  * Parse a raw message into command context
164
169
  */
@@ -167,7 +172,7 @@ declare class StoatxHandler {
167
172
  channelId: string;
168
173
  serverId?: string | undefined;
169
174
  reply: (content: string) => Promise<Message>;
170
- }): Promise<CommandContext<Client> | null>;
175
+ }): Promise<CommandContext | null>;
171
176
  /**
172
177
  * Handle a message object using the configured message adapter
173
178
  *
@@ -205,7 +210,7 @@ declare class StoatxHandler {
205
210
  /**
206
211
  * Execute a command with the given context
207
212
  */
208
- execute(ctx: CommandContext<Client>): Promise<boolean>;
213
+ execute(ctx: CommandContext): Promise<boolean>;
209
214
  /**
210
215
  * Get the command registry
211
216
  */
@@ -257,10 +262,22 @@ declare class StoatxHandler {
257
262
  */
258
263
  declare class Client extends Client$1 {
259
264
  readonly handler: StoatxHandler;
260
- constructor(options: Omit<StoatxHandlerOptions, "client">);
261
- initCommands(): Promise<void>;
265
+ constructor(options: Omit<StoatxHandlerOptions, "client"> & ClientOptions);
266
+ login(token: string): Promise<string>;
267
+ executeCommand(message: Message): Promise<void>;
262
268
  }
263
269
 
270
+ type OptionType = "string" | "number" | "boolean" | "user" | "channel" | "role";
271
+ interface CommandOptionDefinition {
272
+ /** The name of the flag (e.g., "deleteMessages") */
273
+ name: string;
274
+ /** The expected data type */
275
+ type: OptionType;
276
+ /** Whether the user MUST provide this flag */
277
+ required?: boolean;
278
+ /** Description for help menus */
279
+ description?: string;
280
+ }
264
281
  /**
265
282
  * Simple command options passed to @SimpleCommand decorator
266
283
  * Used with @Stoat() decorated classes for method-based commands
@@ -284,6 +301,10 @@ interface SimpleCommandOptions {
284
301
  nsfw?: boolean;
285
302
  /** Whether the command is owner only */
286
303
  ownerOnly?: boolean;
304
+ /** Command options/flags (e.g., --deleteMessages true) */
305
+ options?: CommandOptionDefinition[];
306
+ /** Command args that act like flags without passing flags */
307
+ args?: CommandOptionDefinition[];
287
308
  }
288
309
  /**
289
310
  * Resolved command metadata with required fields
@@ -298,11 +319,13 @@ interface CommandMetadata {
298
319
  cooldownStorage?: string;
299
320
  nsfw: boolean;
300
321
  ownerOnly: boolean;
322
+ options?: CommandOptionDefinition[];
323
+ args?: CommandOptionDefinition[];
301
324
  }
302
325
  /**
303
326
  * Command execution context
304
327
  */
305
- interface CommandContext<TClient extends Client$1 = Client> {
328
+ interface CommandContext<TOptions = Record<string, string | number | boolean>, TArgs extends (string | number | boolean)[] = (string | number | boolean)[], TClient extends Client$1 = Client> {
306
329
  /** The client instance */
307
330
  client: TClient;
308
331
  /** The raw message content */
@@ -314,7 +337,8 @@ interface CommandContext<TClient extends Client$1 = Client> {
314
337
  /** The server/guild ID (if applicable) */
315
338
  serverId?: string | undefined;
316
339
  /** Parsed command arguments */
317
- args: string[];
340
+ args: TArgs;
341
+ options?: TOptions;
318
342
  /** The prefix used */
319
343
  prefix: string;
320
344
  /** The command name used (could be an alias) */
@@ -383,6 +407,10 @@ interface StoatxHandlerOptions {
383
407
  disableMentionPrefix?: boolean;
384
408
  /** Custom cooldown manager */
385
409
  cooldownManager?: CooldownManager;
410
+ /** * The prefix used to identify flags/options (defaults to "-")
411
+ * @example "+" for +force, "/" for /force
412
+ */
413
+ flagPrefix?: string;
386
414
  }
387
415
 
388
416
  /**
@@ -421,7 +449,7 @@ interface SimpleCommandDefinition {
421
449
  methodName: string;
422
450
  options: SimpleCommandOptions;
423
451
  }
424
- type CommandMethod = (ctx: CommandContext<Client>) => Promise<void>;
452
+ type CommandMethod = (ctx: CommandContext) => Promise<void>;
425
453
  /**
426
454
  * @SimpleCommand
427
455
  * Marks a method as a simple command within a @Stoat() decorated class.
@@ -554,4 +582,9 @@ declare const METADATA_KEYS: {
554
582
  readonly EVENTS: symbol;
555
583
  };
556
584
 
557
- export { Client, type CommandContext, type CommandMetadata, CommandRegistry, type CooldownManager, DefaultCooldownManager, type EventDefinition, Guard, METADATA_KEYS, On, Once, type RegisteredCommand, type RegisteredEvent, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, type StoatxDiscoveryOptions, type StoatxGuard, StoatxHandler, type StoatxHandlerOptions, buildSimpleCommandMetadata, getEventsMetadata, getGuards, getSimpleCommands, isStoatClass };
585
+ declare class CommandValidationError extends Error {
586
+ readonly optionName: string;
587
+ constructor(optionName: string, message: string);
588
+ }
589
+
590
+ export { Client, type CommandContext, type CommandMetadata, type CommandOptionDefinition, CommandRegistry, CommandValidationError, type CooldownManager, DefaultCooldownManager, type EventDefinition, Guard, METADATA_KEYS, On, Once, type OptionType, type RegisteredCommand, type RegisteredEvent, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, type StoatxDiscoveryOptions, type StoatxGuard, StoatxHandler, type StoatxHandlerOptions, buildSimpleCommandMetadata, getEventsMetadata, getGuards, getSimpleCommands, isStoatClass };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Message, Client as Client$1, PermissionResolvable } from '@stoatx/client';
1
+ import { Message, Client as Client$1, ClientOptions, PermissionResolvable } from '@stoatx/client';
2
2
  export * from '@stoatx/client';
3
3
 
4
4
  interface AutoDiscoveryOptions {
@@ -150,6 +150,7 @@ declare class StoatxHandler {
150
150
  private readonly cooldownManager;
151
151
  private readonly disableMentionPrefix;
152
152
  private readonly client;
153
+ private readonly flagPrefix;
153
154
  constructor(options: StoatxHandlerOptions);
154
155
  /**
155
156
  * Initialize the handler - load all commands
@@ -159,6 +160,10 @@ declare class StoatxHandler {
159
160
  * Attach registered events to the client
160
161
  */
161
162
  private attachEvents;
163
+ /**
164
+ * Parses raw string arguments into positional args and key-value options
165
+ */
166
+ private parseCommandOptions;
162
167
  /**
163
168
  * Parse a raw message into command context
164
169
  */
@@ -167,7 +172,7 @@ declare class StoatxHandler {
167
172
  channelId: string;
168
173
  serverId?: string | undefined;
169
174
  reply: (content: string) => Promise<Message>;
170
- }): Promise<CommandContext<Client> | null>;
175
+ }): Promise<CommandContext | null>;
171
176
  /**
172
177
  * Handle a message object using the configured message adapter
173
178
  *
@@ -205,7 +210,7 @@ declare class StoatxHandler {
205
210
  /**
206
211
  * Execute a command with the given context
207
212
  */
208
- execute(ctx: CommandContext<Client>): Promise<boolean>;
213
+ execute(ctx: CommandContext): Promise<boolean>;
209
214
  /**
210
215
  * Get the command registry
211
216
  */
@@ -257,10 +262,22 @@ declare class StoatxHandler {
257
262
  */
258
263
  declare class Client extends Client$1 {
259
264
  readonly handler: StoatxHandler;
260
- constructor(options: Omit<StoatxHandlerOptions, "client">);
261
- initCommands(): Promise<void>;
265
+ constructor(options: Omit<StoatxHandlerOptions, "client"> & ClientOptions);
266
+ login(token: string): Promise<string>;
267
+ executeCommand(message: Message): Promise<void>;
262
268
  }
263
269
 
270
+ type OptionType = "string" | "number" | "boolean" | "user" | "channel" | "role";
271
+ interface CommandOptionDefinition {
272
+ /** The name of the flag (e.g., "deleteMessages") */
273
+ name: string;
274
+ /** The expected data type */
275
+ type: OptionType;
276
+ /** Whether the user MUST provide this flag */
277
+ required?: boolean;
278
+ /** Description for help menus */
279
+ description?: string;
280
+ }
264
281
  /**
265
282
  * Simple command options passed to @SimpleCommand decorator
266
283
  * Used with @Stoat() decorated classes for method-based commands
@@ -284,6 +301,10 @@ interface SimpleCommandOptions {
284
301
  nsfw?: boolean;
285
302
  /** Whether the command is owner only */
286
303
  ownerOnly?: boolean;
304
+ /** Command options/flags (e.g., --deleteMessages true) */
305
+ options?: CommandOptionDefinition[];
306
+ /** Command args that act like flags without passing flags */
307
+ args?: CommandOptionDefinition[];
287
308
  }
288
309
  /**
289
310
  * Resolved command metadata with required fields
@@ -298,11 +319,13 @@ interface CommandMetadata {
298
319
  cooldownStorage?: string;
299
320
  nsfw: boolean;
300
321
  ownerOnly: boolean;
322
+ options?: CommandOptionDefinition[];
323
+ args?: CommandOptionDefinition[];
301
324
  }
302
325
  /**
303
326
  * Command execution context
304
327
  */
305
- interface CommandContext<TClient extends Client$1 = Client> {
328
+ interface CommandContext<TOptions = Record<string, string | number | boolean>, TArgs extends (string | number | boolean)[] = (string | number | boolean)[], TClient extends Client$1 = Client> {
306
329
  /** The client instance */
307
330
  client: TClient;
308
331
  /** The raw message content */
@@ -314,7 +337,8 @@ interface CommandContext<TClient extends Client$1 = Client> {
314
337
  /** The server/guild ID (if applicable) */
315
338
  serverId?: string | undefined;
316
339
  /** Parsed command arguments */
317
- args: string[];
340
+ args: TArgs;
341
+ options?: TOptions;
318
342
  /** The prefix used */
319
343
  prefix: string;
320
344
  /** The command name used (could be an alias) */
@@ -383,6 +407,10 @@ interface StoatxHandlerOptions {
383
407
  disableMentionPrefix?: boolean;
384
408
  /** Custom cooldown manager */
385
409
  cooldownManager?: CooldownManager;
410
+ /** * The prefix used to identify flags/options (defaults to "-")
411
+ * @example "+" for +force, "/" for /force
412
+ */
413
+ flagPrefix?: string;
386
414
  }
387
415
 
388
416
  /**
@@ -421,7 +449,7 @@ interface SimpleCommandDefinition {
421
449
  methodName: string;
422
450
  options: SimpleCommandOptions;
423
451
  }
424
- type CommandMethod = (ctx: CommandContext<Client>) => Promise<void>;
452
+ type CommandMethod = (ctx: CommandContext) => Promise<void>;
425
453
  /**
426
454
  * @SimpleCommand
427
455
  * Marks a method as a simple command within a @Stoat() decorated class.
@@ -554,4 +582,9 @@ declare const METADATA_KEYS: {
554
582
  readonly EVENTS: symbol;
555
583
  };
556
584
 
557
- export { Client, type CommandContext, type CommandMetadata, CommandRegistry, type CooldownManager, DefaultCooldownManager, type EventDefinition, Guard, METADATA_KEYS, On, Once, type RegisteredCommand, type RegisteredEvent, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, type StoatxDiscoveryOptions, type StoatxGuard, StoatxHandler, type StoatxHandlerOptions, buildSimpleCommandMetadata, getEventsMetadata, getGuards, getSimpleCommands, isStoatClass };
585
+ declare class CommandValidationError extends Error {
586
+ readonly optionName: string;
587
+ constructor(optionName: string, message: string);
588
+ }
589
+
590
+ export { Client, type CommandContext, type CommandMetadata, type CommandOptionDefinition, CommandRegistry, CommandValidationError, type CooldownManager, DefaultCooldownManager, type EventDefinition, Guard, METADATA_KEYS, On, Once, type OptionType, type RegisteredCommand, type RegisteredEvent, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, type StoatxDiscoveryOptions, type StoatxGuard, StoatxHandler, type StoatxHandlerOptions, buildSimpleCommandMetadata, getEventsMetadata, getGuards, getSimpleCommands, isStoatClass };