stoatx 0.2.1 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stoatx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -35,41 +35,33 @@ Make sure to enable decorators in your `tsconfig.json`:
35
35
 
36
36
  ```typescript
37
37
  // index.ts
38
- import 'reflect-metadata';
39
- import { Client } from 'stoat.js';
40
- import { MallyHandler } from 'stoatx';
38
+ import "reflect-metadata";
39
+ import { Client } from "stoatx";
41
40
 
42
- const client = new Client();
43
-
44
- const handler = new MallyHandler({
45
- client,
46
- prefix: '!',
47
- owners: ['your-user-id']
41
+ const client = new Client({
42
+ prefix: "!",
43
+ owners: ["your-user-id"],
48
44
  });
49
45
 
50
- await handler.init();
51
-
52
- client.on('messageCreate', (message) => {
53
- handler.handleMessage(message);
54
- });
46
+ await client.initCommands();
55
47
 
56
- client.login('your-token');
48
+ client.login("your-token");
57
49
  ```
58
50
 
59
51
  ### 2. Create commands
60
52
 
61
53
  ```typescript
62
54
  // commands/general.ts
63
- import { Stoat, SimpleCommand, Context } from 'stoatx';
55
+ import { Stoat, SimpleCommand, Context } from "stoatx";
64
56
 
65
57
  @Stoat()
66
58
  export class GeneralCommands {
67
- @SimpleCommand({ name: 'ping', description: 'Check bot latency' })
59
+ @SimpleCommand({ name: "ping", description: "Check bot latency" })
68
60
  async ping(ctx: Context) {
69
61
  await ctx.reply(`Pong! 🏓`);
70
62
  }
71
63
 
72
- @SimpleCommand({ name: 'hello', aliases: ['hi', 'hey'] })
64
+ @SimpleCommand({ name: "hello", aliases: ["hi", "hey"] })
73
65
  async hello(ctx: Context) {
74
66
  await ctx.reply(`Hello, <@${ctx.authorId}>!`);
75
67
  }
@@ -115,25 +107,25 @@ async ban(ctx: Context) {
115
107
  Adds a guard check before command execution.
116
108
 
117
109
  ```typescript
118
- import { Stoat, SimpleCommand, Guard, MallyGuard, Context } from 'stoatx';
110
+ import { Stoat, SimpleCommand, Guard, MallyGuard, Context } from "stoatx";
119
111
 
120
112
  // Define a guard
121
113
  class IsAdmin implements MallyGuard {
122
114
  run(ctx: Context): boolean {
123
- return ctx.message.member?.hasPermission('Administrator') ?? false;
115
+ return ctx.message.member?.hasPermission("Administrator") ?? false;
124
116
  }
125
117
 
126
118
  guardFail(ctx: Context): void {
127
- ctx.reply('You need Administrator permission!');
119
+ ctx.reply("You need Administrator permission!");
128
120
  }
129
121
  }
130
122
 
131
123
  @Stoat()
132
124
  @Guard(IsAdmin)
133
125
  export class AdminCommands {
134
- @SimpleCommand({ name: 'shutdown' })
126
+ @SimpleCommand({ name: "shutdown" })
135
127
  async shutdown(ctx: Context) {
136
- await ctx.reply('Shutting down...');
128
+ await ctx.reply("Shutting down...");
137
129
  }
138
130
  }
139
131
  ```
@@ -144,16 +136,16 @@ The `Context` object provides:
144
136
 
145
137
  ```typescript
146
138
  interface Context {
147
- client: Client; // Stoat client instance
148
- message: Message; // Original message
149
- content: string; // Raw message content
150
- authorId: string; // Author's user ID
151
- channelId: string; // Channel ID
152
- serverId?: string; // Server/Guild ID
153
- args: string[]; // Parsed arguments
154
- prefix: string; // Prefix used
155
- commandName: string; // Command name used
156
-
139
+ client: Client; // Stoat client instance
140
+ message: Message; // Original message
141
+ content: string; // Raw message content
142
+ authorId: string; // Author's user ID
143
+ channelId: string; // Channel ID
144
+ serverId?: string; // Server/Guild ID
145
+ args: string[]; // Parsed arguments
146
+ prefix: string; // Prefix used
147
+ commandName: string; // Command name used
148
+
157
149
  reply(content: string): Promise<void>;
158
150
  }
159
151
  ```
@@ -161,17 +153,17 @@ interface Context {
161
153
  ## Handler Options
162
154
 
163
155
  ```typescript
164
- interface MallyHandlerOptions {
156
+ interface StoatxHandlerOptions {
165
157
  client: Client;
166
- commandsDir?: string; // Legacy mode: explicitly scan this directory
158
+ commandsDir?: string; // Legacy mode: explicitly scan this directory
167
159
  discovery?: {
168
- roots?: string[]; // Default: [process.cwd()]
169
- include?: string[]; // Glob patterns per root
170
- ignore?: string[]; // Additional ignore globs
160
+ roots?: string[]; // Default: [process.cwd()]
161
+ include?: string[]; // Glob patterns per root
162
+ ignore?: string[]; // Additional ignore globs
171
163
  };
172
- prefix: string | ((ctx: { serverId?: string }) => string | Promise<string>);
173
- owners?: string[]; // Owner user IDs
174
- extensions?: string[]; // File extensions (default: ['.js', '.mjs', '.cjs'])
164
+ prefix: string ((ctx: { serverId?: string }) => string Promise<string>);
165
+ owners?: string[]; // Owner user IDs
166
+ extensions?: string[]; // File extensions (default: ['.js', '.mjs', '.cjs'])
175
167
  disableMentionPrefix?: boolean; // Disable @bot prefix
176
168
  }
177
169
 
@@ -182,31 +174,28 @@ interface MallyHandlerOptions {
182
174
  ## Dynamic Prefix
183
175
 
184
176
  ```typescript
185
- const handler = new MallyHandler({
186
- client,
177
+ const client = new Client({
187
178
  prefix: async ({ serverId }) => {
188
179
  // Fetch from database, etc.
189
- return serverId ? await getServerPrefix(serverId) : '!';
180
+ return serverId ? await getServerPrefix(serverId) : "!";
190
181
  },
191
182
  });
192
183
 
193
184
  // Optional: constrain auto-discovery to specific roots/patterns
194
- const scopedHandler = new MallyHandler({
195
- client,
196
- prefix: '!',
185
+ const scopedClient = new Client({
186
+ prefix: "!",
197
187
  discovery: {
198
188
  roots: [process.cwd()],
199
- include: ['apps/bot/dist/commands/**/*.js'],
189
+ include: ["apps/bot/dist/commands/**/*.js"],
200
190
  },
201
191
  });
202
192
 
203
193
  // TypeScript source discovery is opt-in and requires a TS runtime loader (tsx/ts-node)
204
- const tsRuntimeHandler = new MallyHandler({
205
- client,
206
- prefix: '!',
207
- extensions: ['.ts'],
194
+ const tsRuntimeClient = new Client({
195
+ prefix: "!",
196
+ extensions: [".ts"],
208
197
  discovery: {
209
- include: ['apps/bot/src/commands/**/*.ts'],
198
+ include: ["apps/bot/src/commands/**/*.ts"],
210
199
  },
211
200
  });
212
201
  ```
@@ -216,6 +205,3 @@ All commands are defined through `@Stoat()` classes and `@SimpleCommand()` metho
216
205
  ## License
217
206
 
218
207
  AGPL-3.0-or-later
219
-
220
-
221
-
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Client, Message } from 'stoat.js';
1
+ import { Client as Client$1, Message } from 'stoat.js';
2
2
 
3
3
  /**
4
4
  * Permission types for commands
@@ -44,7 +44,7 @@ interface CommandMetadata {
44
44
  */
45
45
  interface CommandContext {
46
46
  /** The client instance */
47
- client: Client;
47
+ client: Client$1;
48
48
  /** The raw message content */
49
49
  content: string;
50
50
  /** The author ID */
@@ -73,14 +73,14 @@ interface StoatLifecycle {
73
73
  /** Optional: Called when a cooldown is active */
74
74
  onCooldown?(ctx: CommandContext, remaining: number): Promise<void>;
75
75
  }
76
- interface MallyGuard {
76
+ interface StoatxGuard {
77
77
  run(ctx: CommandContext): Promise<boolean> | boolean;
78
78
  guardFail?(ctx: CommandContext): Promise<void> | void;
79
79
  }
80
80
  /**
81
81
  * Discovery options for automatic command module loading
82
82
  */
83
- interface MallyDiscoveryOptions {
83
+ interface StoatxDiscoveryOptions {
84
84
  /** Root directories to scan (default: [process.cwd()]) */
85
85
  roots?: string[];
86
86
  /** Glob patterns relative to each root */
@@ -91,13 +91,13 @@ interface MallyDiscoveryOptions {
91
91
  /**
92
92
  * Handler options
93
93
  */
94
- interface MallyHandlerOptions {
94
+ interface StoatxHandlerOptions {
95
95
  /** The client instance */
96
- client: Client;
96
+ client: Client$1;
97
97
  /** Directory to scan for command modules (absolute path) */
98
98
  commandsDir?: string;
99
99
  /** Auto-discovery options used when commandsDir is not provided */
100
- discovery?: MallyDiscoveryOptions;
100
+ discovery?: StoatxDiscoveryOptions;
101
101
  /** Command prefix or prefix resolver function */
102
102
  prefix: string | ((ctx: {
103
103
  serverId?: string;
@@ -183,7 +183,7 @@ declare function getSimpleCommands(target: Function): SimpleCommandDefinition[];
183
183
  * import { Guard, Stoat, SimpleCommand, CommandContext } from 'stoatx';
184
184
  *
185
185
  * // Define a guard
186
- * class NotBot implements MallyGuard {
186
+ * class NotBot implements StoatxGuard {
187
187
  * run(ctx: CommandContext): boolean {
188
188
  * return !ctx.message.author.bot;
189
189
  * }
@@ -209,6 +209,60 @@ declare function Guard(guardClass: Function): ClassDecorator;
209
209
  */
210
210
  declare function getGuards(target: Function): Function[];
211
211
 
212
+ interface EventDefinition {
213
+ methodName: string;
214
+ event: string;
215
+ type: "on" | "once";
216
+ }
217
+ /**
218
+ * @On
219
+ * Triggered on every occurrence of the event.
220
+ * Marks a method to be executed whenever the specified client event is emitted.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * import { Stoat, On } from 'stoatx';
225
+ * import { Message, Client } from 'stoat.js';
226
+ *
227
+ * @Stoat()
228
+ * class BotEvents {
229
+ * @On('messageCreate')
230
+ * async onMessage(message: Message, client: Client) {
231
+ * console.log('New message received:', message.content);
232
+ * }
233
+ * }
234
+ * ```
235
+ *
236
+ * @param event The name of the client event to listen to
237
+ */
238
+ declare function On(event: string): MethodDecorator;
239
+ /**
240
+ * @Once
241
+ * Triggered only fully once.
242
+ * Marks a method to be executed only the FIRST time the specified client event is emitted.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * import { Stoat, Once } from 'stoatx';
247
+ * import { Client } from 'stoat.js';
248
+ *
249
+ * @Stoat()
250
+ * class BotEvents {
251
+ * @Once('ready')
252
+ * async onReady(client: Client) {
253
+ * console.log('Bot successfully started and logged in!');
254
+ * }
255
+ * }
256
+ * ```
257
+ *
258
+ * @param event The name of the client event to listen to
259
+ */
260
+ declare function Once(event: string): MethodDecorator;
261
+ /**
262
+ * Get all event definitions from a @Stoat class
263
+ */
264
+ declare function getEventsMetadata(target: Function): EventDefinition[];
265
+
212
266
  /**
213
267
  * Build CommandMetadata from SimpleCommandOptions
214
268
  */
@@ -220,7 +274,8 @@ declare function buildSimpleCommandMetadata(options: SimpleCommandOptions, metho
220
274
  declare const METADATA_KEYS: {
221
275
  readonly IS_STOAT_CLASS: symbol;
222
276
  readonly SIMPLE_COMMANDS: symbol;
223
- readonly GUARDS: "mally:command:guards";
277
+ readonly GUARDS: "stoatx:command:guards";
278
+ readonly EVENTS: symbol;
224
279
  };
225
280
 
226
281
  interface AutoDiscoveryOptions {
@@ -241,6 +296,15 @@ interface RegisteredCommand {
241
296
  /** The original class constructor (for guard validation) */
242
297
  classConstructor: Function;
243
298
  }
299
+ /**
300
+ * Stored event entry from @On/@Once registration.
301
+ */
302
+ interface RegisteredEvent {
303
+ instance: object;
304
+ methodName: string;
305
+ event: string;
306
+ type: "on" | "once";
307
+ }
244
308
  /**
245
309
  * CommandRegistry - Scans directories and stores commands in a Map
246
310
  *
@@ -257,6 +321,7 @@ declare class CommandRegistry {
257
321
  private static readonly DEFAULT_AUTO_DISCOVERY_IGNORES;
258
322
  private readonly commands;
259
323
  private readonly aliases;
324
+ private readonly registeredEvents;
260
325
  private readonly extensions;
261
326
  private readonly processedStoatClasses;
262
327
  constructor(extensions?: string[]);
@@ -294,6 +359,10 @@ declare class CommandRegistry {
294
359
  * Get all command metadata
295
360
  */
296
361
  getAllMetadata(): CommandMetadata[];
362
+ /**
363
+ * Get all registered events
364
+ */
365
+ getEvents(): RegisteredEvent[];
297
366
  /**
298
367
  * Get commands grouped by category
299
368
  */
@@ -333,44 +402,53 @@ declare class CommandRegistry {
333
402
  }
334
403
 
335
404
  /**
336
- * MallyHandler - The execution engine for commands
337
- *
338
- * Handles message parsing, middleware execution, and command dispatching
405
+ * Client - An extended Client that integrates StoatxHandler directly
339
406
  *
340
407
  * @example
341
408
  * ```ts
342
- * import { MallyHandler } from 'stoatx';
343
- * import { Client } from 'stoat.js';
344
- *
345
- * const client = new Client();
409
+ * import { Client } from 'stoatx';
346
410
  *
347
- * const handler = new MallyHandler({
348
- * client,
411
+ * const client = new Client({
349
412
  * prefix: '!',
350
413
  * owners: ['owner-user-id'],
351
414
  * });
352
415
  *
353
- * await handler.init();
354
- *
355
- * client.on('message', (message) => {
356
- * handler.handleMessage(message);
357
- * });
416
+ * await client.initCommands();
358
417
  * ```
359
418
  */
360
- declare class MallyHandler {
361
- private readonly commandsDir?;
362
- private readonly discoveryOptions?;
419
+ declare class Client extends Client$1 {
420
+ readonly handler: StoatxHandler;
421
+ constructor(options: Omit<StoatxHandlerOptions, "client">);
422
+ /**
423
+ * Initialize the StoatxHandler commands
424
+ */
425
+ initCommands(): Promise<void>;
426
+ }
427
+ /**
428
+ * StoatxHandler - The execution engine for commands
429
+ *
430
+ * Handles message parsing, middleware execution, and command dispatching
431
+ *
432
+ * @internal This class is not intended to be instantiated directly. Use the `Client` from `stoatx` instead.
433
+ */
434
+ declare class StoatxHandler {
435
+ private readonly commandsDir;
436
+ private readonly discoveryOptions;
363
437
  private readonly prefixResolver;
364
438
  private readonly owners;
365
439
  private readonly registry;
366
440
  private readonly cooldowns;
367
441
  private readonly disableMentionPrefix;
368
442
  private readonly client;
369
- constructor(options: MallyHandlerOptions);
443
+ constructor(options: StoatxHandlerOptions);
370
444
  /**
371
445
  * Initialize the handler - load all commands
372
446
  */
373
447
  init(): Promise<void>;
448
+ /**
449
+ * Attach registered events to the client
450
+ */
451
+ private attachEvents;
374
452
  /**
375
453
  * Parse a raw message into command context
376
454
  */
@@ -464,4 +542,4 @@ declare class MallyHandler {
464
542
  private setCooldown;
465
543
  }
466
544
 
467
- export { type CommandContext, type CommandMetadata, CommandRegistry, type CommandContext as Context, Guard, METADATA_KEYS, type MallyDiscoveryOptions, type MallyGuard, MallyHandler, type MallyHandlerOptions, type Permission, type RegisteredCommand, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, buildSimpleCommandMetadata, getGuards, getSimpleCommands, isStoatClass };
545
+ export { Client, type CommandContext, type CommandMetadata, CommandRegistry, type EventDefinition, Guard, METADATA_KEYS, On, Once, type Permission, 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 { Client, Message } from 'stoat.js';
1
+ import { Client as Client$1, Message } from 'stoat.js';
2
2
 
3
3
  /**
4
4
  * Permission types for commands
@@ -44,7 +44,7 @@ interface CommandMetadata {
44
44
  */
45
45
  interface CommandContext {
46
46
  /** The client instance */
47
- client: Client;
47
+ client: Client$1;
48
48
  /** The raw message content */
49
49
  content: string;
50
50
  /** The author ID */
@@ -73,14 +73,14 @@ interface StoatLifecycle {
73
73
  /** Optional: Called when a cooldown is active */
74
74
  onCooldown?(ctx: CommandContext, remaining: number): Promise<void>;
75
75
  }
76
- interface MallyGuard {
76
+ interface StoatxGuard {
77
77
  run(ctx: CommandContext): Promise<boolean> | boolean;
78
78
  guardFail?(ctx: CommandContext): Promise<void> | void;
79
79
  }
80
80
  /**
81
81
  * Discovery options for automatic command module loading
82
82
  */
83
- interface MallyDiscoveryOptions {
83
+ interface StoatxDiscoveryOptions {
84
84
  /** Root directories to scan (default: [process.cwd()]) */
85
85
  roots?: string[];
86
86
  /** Glob patterns relative to each root */
@@ -91,13 +91,13 @@ interface MallyDiscoveryOptions {
91
91
  /**
92
92
  * Handler options
93
93
  */
94
- interface MallyHandlerOptions {
94
+ interface StoatxHandlerOptions {
95
95
  /** The client instance */
96
- client: Client;
96
+ client: Client$1;
97
97
  /** Directory to scan for command modules (absolute path) */
98
98
  commandsDir?: string;
99
99
  /** Auto-discovery options used when commandsDir is not provided */
100
- discovery?: MallyDiscoveryOptions;
100
+ discovery?: StoatxDiscoveryOptions;
101
101
  /** Command prefix or prefix resolver function */
102
102
  prefix: string | ((ctx: {
103
103
  serverId?: string;
@@ -183,7 +183,7 @@ declare function getSimpleCommands(target: Function): SimpleCommandDefinition[];
183
183
  * import { Guard, Stoat, SimpleCommand, CommandContext } from 'stoatx';
184
184
  *
185
185
  * // Define a guard
186
- * class NotBot implements MallyGuard {
186
+ * class NotBot implements StoatxGuard {
187
187
  * run(ctx: CommandContext): boolean {
188
188
  * return !ctx.message.author.bot;
189
189
  * }
@@ -209,6 +209,60 @@ declare function Guard(guardClass: Function): ClassDecorator;
209
209
  */
210
210
  declare function getGuards(target: Function): Function[];
211
211
 
212
+ interface EventDefinition {
213
+ methodName: string;
214
+ event: string;
215
+ type: "on" | "once";
216
+ }
217
+ /**
218
+ * @On
219
+ * Triggered on every occurrence of the event.
220
+ * Marks a method to be executed whenever the specified client event is emitted.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * import { Stoat, On } from 'stoatx';
225
+ * import { Message, Client } from 'stoat.js';
226
+ *
227
+ * @Stoat()
228
+ * class BotEvents {
229
+ * @On('messageCreate')
230
+ * async onMessage(message: Message, client: Client) {
231
+ * console.log('New message received:', message.content);
232
+ * }
233
+ * }
234
+ * ```
235
+ *
236
+ * @param event The name of the client event to listen to
237
+ */
238
+ declare function On(event: string): MethodDecorator;
239
+ /**
240
+ * @Once
241
+ * Triggered only fully once.
242
+ * Marks a method to be executed only the FIRST time the specified client event is emitted.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * import { Stoat, Once } from 'stoatx';
247
+ * import { Client } from 'stoat.js';
248
+ *
249
+ * @Stoat()
250
+ * class BotEvents {
251
+ * @Once('ready')
252
+ * async onReady(client: Client) {
253
+ * console.log('Bot successfully started and logged in!');
254
+ * }
255
+ * }
256
+ * ```
257
+ *
258
+ * @param event The name of the client event to listen to
259
+ */
260
+ declare function Once(event: string): MethodDecorator;
261
+ /**
262
+ * Get all event definitions from a @Stoat class
263
+ */
264
+ declare function getEventsMetadata(target: Function): EventDefinition[];
265
+
212
266
  /**
213
267
  * Build CommandMetadata from SimpleCommandOptions
214
268
  */
@@ -220,7 +274,8 @@ declare function buildSimpleCommandMetadata(options: SimpleCommandOptions, metho
220
274
  declare const METADATA_KEYS: {
221
275
  readonly IS_STOAT_CLASS: symbol;
222
276
  readonly SIMPLE_COMMANDS: symbol;
223
- readonly GUARDS: "mally:command:guards";
277
+ readonly GUARDS: "stoatx:command:guards";
278
+ readonly EVENTS: symbol;
224
279
  };
225
280
 
226
281
  interface AutoDiscoveryOptions {
@@ -241,6 +296,15 @@ interface RegisteredCommand {
241
296
  /** The original class constructor (for guard validation) */
242
297
  classConstructor: Function;
243
298
  }
299
+ /**
300
+ * Stored event entry from @On/@Once registration.
301
+ */
302
+ interface RegisteredEvent {
303
+ instance: object;
304
+ methodName: string;
305
+ event: string;
306
+ type: "on" | "once";
307
+ }
244
308
  /**
245
309
  * CommandRegistry - Scans directories and stores commands in a Map
246
310
  *
@@ -257,6 +321,7 @@ declare class CommandRegistry {
257
321
  private static readonly DEFAULT_AUTO_DISCOVERY_IGNORES;
258
322
  private readonly commands;
259
323
  private readonly aliases;
324
+ private readonly registeredEvents;
260
325
  private readonly extensions;
261
326
  private readonly processedStoatClasses;
262
327
  constructor(extensions?: string[]);
@@ -294,6 +359,10 @@ declare class CommandRegistry {
294
359
  * Get all command metadata
295
360
  */
296
361
  getAllMetadata(): CommandMetadata[];
362
+ /**
363
+ * Get all registered events
364
+ */
365
+ getEvents(): RegisteredEvent[];
297
366
  /**
298
367
  * Get commands grouped by category
299
368
  */
@@ -333,44 +402,53 @@ declare class CommandRegistry {
333
402
  }
334
403
 
335
404
  /**
336
- * MallyHandler - The execution engine for commands
337
- *
338
- * Handles message parsing, middleware execution, and command dispatching
405
+ * Client - An extended Client that integrates StoatxHandler directly
339
406
  *
340
407
  * @example
341
408
  * ```ts
342
- * import { MallyHandler } from 'stoatx';
343
- * import { Client } from 'stoat.js';
344
- *
345
- * const client = new Client();
409
+ * import { Client } from 'stoatx';
346
410
  *
347
- * const handler = new MallyHandler({
348
- * client,
411
+ * const client = new Client({
349
412
  * prefix: '!',
350
413
  * owners: ['owner-user-id'],
351
414
  * });
352
415
  *
353
- * await handler.init();
354
- *
355
- * client.on('message', (message) => {
356
- * handler.handleMessage(message);
357
- * });
416
+ * await client.initCommands();
358
417
  * ```
359
418
  */
360
- declare class MallyHandler {
361
- private readonly commandsDir?;
362
- private readonly discoveryOptions?;
419
+ declare class Client extends Client$1 {
420
+ readonly handler: StoatxHandler;
421
+ constructor(options: Omit<StoatxHandlerOptions, "client">);
422
+ /**
423
+ * Initialize the StoatxHandler commands
424
+ */
425
+ initCommands(): Promise<void>;
426
+ }
427
+ /**
428
+ * StoatxHandler - The execution engine for commands
429
+ *
430
+ * Handles message parsing, middleware execution, and command dispatching
431
+ *
432
+ * @internal This class is not intended to be instantiated directly. Use the `Client` from `stoatx` instead.
433
+ */
434
+ declare class StoatxHandler {
435
+ private readonly commandsDir;
436
+ private readonly discoveryOptions;
363
437
  private readonly prefixResolver;
364
438
  private readonly owners;
365
439
  private readonly registry;
366
440
  private readonly cooldowns;
367
441
  private readonly disableMentionPrefix;
368
442
  private readonly client;
369
- constructor(options: MallyHandlerOptions);
443
+ constructor(options: StoatxHandlerOptions);
370
444
  /**
371
445
  * Initialize the handler - load all commands
372
446
  */
373
447
  init(): Promise<void>;
448
+ /**
449
+ * Attach registered events to the client
450
+ */
451
+ private attachEvents;
374
452
  /**
375
453
  * Parse a raw message into command context
376
454
  */
@@ -464,4 +542,4 @@ declare class MallyHandler {
464
542
  private setCooldown;
465
543
  }
466
544
 
467
- export { type CommandContext, type CommandMetadata, CommandRegistry, type CommandContext as Context, Guard, METADATA_KEYS, type MallyDiscoveryOptions, type MallyGuard, MallyHandler, type MallyHandlerOptions, type Permission, type RegisteredCommand, SimpleCommand, type SimpleCommandDefinition, type SimpleCommandOptions, Stoat, type StoatLifecycle, buildSimpleCommandMetadata, getGuards, getSimpleCommands, isStoatClass };
545
+ export { Client, type CommandContext, type CommandMetadata, CommandRegistry, type EventDefinition, Guard, METADATA_KEYS, On, Once, type Permission, 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.js CHANGED
@@ -30,13 +30,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Client: () => Client,
33
34
  CommandRegistry: () => CommandRegistry,
34
35
  Guard: () => Guard,
35
36
  METADATA_KEYS: () => METADATA_KEYS,
36
- MallyHandler: () => MallyHandler,
37
+ On: () => On,
38
+ Once: () => Once,
37
39
  SimpleCommand: () => SimpleCommand,
38
40
  Stoat: () => Stoat,
39
41
  buildSimpleCommandMetadata: () => buildSimpleCommandMetadata,
42
+ getEventsMetadata: () => getEventsMetadata,
40
43
  getGuards: () => getGuards,
41
44
  getSimpleCommands: () => getSimpleCommands,
42
45
  isStoatClass: () => isStoatClass
@@ -48,9 +51,10 @@ var import_reflect_metadata = require("reflect-metadata");
48
51
 
49
52
  // src/decorators/keys.ts
50
53
  var METADATA_KEYS = {
51
- IS_STOAT_CLASS: /* @__PURE__ */ Symbol("mally:stoat:isClass"),
52
- SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("mally:stoat:simpleCommands"),
53
- GUARDS: "mally:command:guards"
54
+ IS_STOAT_CLASS: /* @__PURE__ */ Symbol("stoatx:stoat:isClass"),
55
+ SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("stoatx:stoat:simpleCommands"),
56
+ GUARDS: "stoatx:command:guards",
57
+ EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events")
54
58
  };
55
59
 
56
60
  // src/decorators/store.ts
@@ -161,6 +165,31 @@ function getGuards(target) {
161
165
  return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
162
166
  }
163
167
 
168
+ // src/decorators/Events.ts
169
+ var import_reflect_metadata4 = require("reflect-metadata");
170
+ function createEventDecorator(event, type) {
171
+ return (target, propertyKey, descriptor) => {
172
+ const constructor = target.constructor;
173
+ const existingEvents = Reflect.getMetadata(METADATA_KEYS.EVENTS, constructor) || [];
174
+ existingEvents.push({
175
+ methodName: String(propertyKey),
176
+ event,
177
+ type
178
+ });
179
+ Reflect.defineMetadata(METADATA_KEYS.EVENTS, existingEvents, constructor);
180
+ return descriptor;
181
+ };
182
+ }
183
+ function On(event) {
184
+ return createEventDecorator(event, "on");
185
+ }
186
+ function Once(event) {
187
+ return createEventDecorator(event, "once");
188
+ }
189
+ function getEventsMetadata(target) {
190
+ return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
191
+ }
192
+
164
193
  // src/decorators/utils.ts
165
194
  function buildSimpleCommandMetadata(options, methodName, category) {
166
195
  return {
@@ -184,6 +213,7 @@ var _CommandRegistry = class _CommandRegistry {
184
213
  constructor(extensions = [".js", ".mjs", ".cjs"]) {
185
214
  this.commands = /* @__PURE__ */ new Map();
186
215
  this.aliases = /* @__PURE__ */ new Map();
216
+ this.registeredEvents = [];
187
217
  this.processedStoatClasses = /* @__PURE__ */ new Set();
188
218
  this.extensions = extensions;
189
219
  }
@@ -207,7 +237,7 @@ var _CommandRegistry = class _CommandRegistry {
207
237
  await this.loadFile(file, directory);
208
238
  }
209
239
  }
210
- console.log(`[Mally] Loaded ${this.commands.size} command(s)`);
240
+ console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
211
241
  }
212
242
  /**
213
243
  * Auto-discover command files across one or more roots.
@@ -235,7 +265,7 @@ var _CommandRegistry = class _CommandRegistry {
235
265
  }) ?? roots[0];
236
266
  await this.loadFile(file, baseDir);
237
267
  }
238
- console.log(`[Mally] Auto-discovered ${candidateFiles} candidate file(s), loaded ${this.commands.size} command(s)`);
268
+ console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
239
269
  }
240
270
  getDefaultAutoDiscoveryPatterns() {
241
271
  return this.extensions.map((ext) => `**/*${ext}`);
@@ -243,7 +273,7 @@ var _CommandRegistry = class _CommandRegistry {
243
273
  async isLikelyCommandModule(filePath) {
244
274
  try {
245
275
  const source = await fs.readFile(filePath, "utf8");
246
- return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("mally:command");
276
+ return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("stoatx:command");
247
277
  } catch {
248
278
  return true;
249
279
  }
@@ -254,7 +284,7 @@ var _CommandRegistry = class _CommandRegistry {
254
284
  register(instance, metadata, classConstructor, methodName) {
255
285
  const name = metadata.name.toLowerCase();
256
286
  if (this.commands.has(name)) {
257
- console.warn(`[Mally] Duplicate command name: ${name}. Skipping...`);
287
+ console.warn(`[Stoatx] Duplicate command name: ${name}. Skipping...`);
258
288
  return;
259
289
  }
260
290
  this.validateGuards(classConstructor, metadata.name);
@@ -262,7 +292,7 @@ var _CommandRegistry = class _CommandRegistry {
262
292
  for (const alias of metadata.aliases) {
263
293
  const aliasLower = alias.toLowerCase();
264
294
  if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
265
- console.warn(`[Mally] Duplicate alias: ${aliasLower}. Skipping...`);
295
+ console.warn(`[Stoatx] Duplicate alias: ${aliasLower}. Skipping...`);
266
296
  continue;
267
297
  }
268
298
  this.aliases.set(aliasLower, name);
@@ -295,6 +325,12 @@ var _CommandRegistry = class _CommandRegistry {
295
325
  getAllMetadata() {
296
326
  return this.getAll().map((c) => c.metadata);
297
327
  }
328
+ /**
329
+ * Get all registered events
330
+ */
331
+ getEvents() {
332
+ return this.registeredEvents;
333
+ }
298
334
  /**
299
335
  * Get commands grouped by category
300
336
  */
@@ -314,6 +350,7 @@ var _CommandRegistry = class _CommandRegistry {
314
350
  clear() {
315
351
  this.commands.clear();
316
352
  this.aliases.clear();
353
+ this.registeredEvents.length = 0;
317
354
  this.processedStoatClasses.clear();
318
355
  }
319
356
  /**
@@ -341,20 +378,20 @@ var _CommandRegistry = class _CommandRegistry {
341
378
  * @private
342
379
  */
343
380
  validateGuards(commandClass, commandName) {
344
- const guards = Reflect.getMetadata("mally:command:guards", commandClass) || [];
381
+ const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
345
382
  for (const GuardClass of guards) {
346
383
  const guardInstance = new GuardClass();
347
384
  if (typeof guardInstance.run !== "function") {
348
385
  console.error(
349
- `[Mally] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
386
+ `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
350
387
  );
351
388
  process.exit(1);
352
389
  }
353
390
  if (typeof guardInstance.guardFail !== "function") {
354
391
  console.error(
355
- `[Mally] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
392
+ `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
356
393
  );
357
- console.error(`[Mally] All guards must implement guardFail() to handle failed checks.`);
394
+ console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
358
395
  process.exit(1);
359
396
  }
360
397
  }
@@ -375,15 +412,16 @@ var _CommandRegistry = class _CommandRegistry {
375
412
  this.registerStoatClassCommands(stoatClass, stoatInstance, filePath, baseDir);
376
413
  }
377
414
  } catch (error) {
378
- console.error(`[Mally] Failed to load command file: ${filePath}`, error);
415
+ console.error(`[Stoatx] Failed to load command file: ${filePath}`, error);
379
416
  }
380
417
  }
381
418
  registerStoatClassCommands(stoatClass, instance, filePath, baseDir) {
382
419
  const simpleCommands = getSimpleCommands(stoatClass);
420
+ const events = getEventsMetadata(stoatClass);
383
421
  const category = this.getCategoryFromPath(filePath, baseDir);
384
- if (simpleCommands.length === 0) {
422
+ if (simpleCommands.length === 0 && events.length === 0) {
385
423
  console.warn(
386
- `[Mally] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand methods. Skipping...`
424
+ `[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
387
425
  );
388
426
  this.processedStoatClasses.add(stoatClass);
389
427
  return;
@@ -391,12 +429,25 @@ var _CommandRegistry = class _CommandRegistry {
391
429
  for (const cmdDef of simpleCommands) {
392
430
  const method = instance[cmdDef.methodName];
393
431
  if (typeof method !== "function") {
394
- console.warn(`[Mally] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
432
+ console.warn(`[Stoatx] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
395
433
  continue;
396
434
  }
397
435
  const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category);
398
436
  this.register(instance, metadata, stoatClass, cmdDef.methodName);
399
437
  }
438
+ for (const eventDef of events) {
439
+ const method = instance[eventDef.methodName];
440
+ if (typeof method !== "function") {
441
+ console.warn(`[Stoatx] Method ${eventDef.methodName} not found on ${stoatClass.name}. Skipping...`);
442
+ continue;
443
+ }
444
+ this.registeredEvents.push({
445
+ instance,
446
+ methodName: eventDef.methodName,
447
+ event: eventDef.event,
448
+ type: eventDef.type
449
+ });
450
+ }
400
451
  this.processedStoatClasses.add(stoatClass);
401
452
  }
402
453
  /**
@@ -421,8 +472,24 @@ _CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES = [
421
472
  var CommandRegistry = _CommandRegistry;
422
473
 
423
474
  // src/handler.ts
424
- var import_reflect_metadata4 = require("reflect-metadata");
425
- var MallyHandler = class {
475
+ var import_reflect_metadata5 = require("reflect-metadata");
476
+ var import_stoat = require("stoat.js");
477
+ var Client = class extends import_stoat.Client {
478
+ constructor(options) {
479
+ super();
480
+ this.handler = new StoatxHandler({ ...options, client: this });
481
+ this.on("messageCreate", async (message) => {
482
+ await this.handler.handle(message);
483
+ });
484
+ }
485
+ /**
486
+ * Initialize the StoatxHandler commands
487
+ */
488
+ async initCommands() {
489
+ await this.handler.init();
490
+ }
491
+ };
492
+ var StoatxHandler = class {
426
493
  constructor(options) {
427
494
  this.cooldowns = /* @__PURE__ */ new Map();
428
495
  this.client = options.client;
@@ -439,9 +506,33 @@ var MallyHandler = class {
439
506
  async init() {
440
507
  if (this.commandsDir) {
441
508
  await this.registry.loadFromDirectory(this.commandsDir);
442
- return;
509
+ } else {
510
+ await this.registry.autoDiscover(this.discoveryOptions);
511
+ }
512
+ this.attachEvents();
513
+ }
514
+ /**
515
+ * Attach registered events to the client
516
+ */
517
+ attachEvents() {
518
+ const events = this.registry.getEvents();
519
+ for (const eventDef of events) {
520
+ const handler = async (...args) => {
521
+ try {
522
+ await eventDef.instance[eventDef.methodName](...args, this.client);
523
+ } catch (error) {
524
+ console.error(
525
+ `[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
526
+ error
527
+ );
528
+ }
529
+ };
530
+ if (eventDef.type === "once") {
531
+ this.client.once(eventDef.event, handler);
532
+ } else {
533
+ this.client.on(eventDef.event, handler);
534
+ }
443
535
  }
444
- await this.registry.autoDiscover(this.discoveryOptions);
445
536
  }
446
537
  /**
447
538
  * Parse a raw message into command context
@@ -553,7 +644,7 @@ var MallyHandler = class {
553
644
  await ctx.reply("This command is owner-only.");
554
645
  return false;
555
646
  }
556
- const guards = Reflect.getMetadata("mally:command:guards", classConstructor) || [];
647
+ const guards = Reflect.getMetadata("stoatx:command:guards", classConstructor) || [];
557
648
  for (const guardClass of guards) {
558
649
  const guardInstance = new guardClass();
559
650
  if (typeof guardInstance.run === "function") {
@@ -562,7 +653,7 @@ var MallyHandler = class {
562
653
  if (typeof guardInstance.guardFail === "function") {
563
654
  await guardInstance.guardFail(ctx);
564
655
  } else {
565
- console.error("[Mally] Guard check failed but no guardFail method defined on", guardClass.name);
656
+ console.error("[Stoatx] Guard check failed but no guardFail method defined on", guardClass.name);
566
657
  }
567
658
  return false;
568
659
  }
@@ -587,7 +678,7 @@ var MallyHandler = class {
587
678
  if (typeof instance.onError === "function") {
588
679
  await instance.onError(ctx, error);
589
680
  } else {
590
- console.error(`[Mally] Error in command ${metadata.name}:`, error);
681
+ console.error(`[Stoatx] Error in command ${metadata.name}:`, error);
591
682
  await ctx.reply(`An error occurred: ${error.message}`);
592
683
  }
593
684
  return false;
@@ -684,13 +775,16 @@ var MallyHandler = class {
684
775
  };
685
776
  // Annotate the CommonJS export names for ESM import in node:
686
777
  0 && (module.exports = {
778
+ Client,
687
779
  CommandRegistry,
688
780
  Guard,
689
781
  METADATA_KEYS,
690
- MallyHandler,
782
+ On,
783
+ Once,
691
784
  SimpleCommand,
692
785
  Stoat,
693
786
  buildSimpleCommandMetadata,
787
+ getEventsMetadata,
694
788
  getGuards,
695
789
  getSimpleCommands,
696
790
  isStoatClass
package/dist/index.mjs CHANGED
@@ -3,9 +3,10 @@ import "reflect-metadata";
3
3
 
4
4
  // src/decorators/keys.ts
5
5
  var METADATA_KEYS = {
6
- IS_STOAT_CLASS: /* @__PURE__ */ Symbol("mally:stoat:isClass"),
7
- SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("mally:stoat:simpleCommands"),
8
- GUARDS: "mally:command:guards"
6
+ IS_STOAT_CLASS: /* @__PURE__ */ Symbol("stoatx:stoat:isClass"),
7
+ SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("stoatx:stoat:simpleCommands"),
8
+ GUARDS: "stoatx:command:guards",
9
+ EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events")
9
10
  };
10
11
 
11
12
  // src/decorators/store.ts
@@ -116,6 +117,31 @@ function getGuards(target) {
116
117
  return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
117
118
  }
118
119
 
120
+ // src/decorators/Events.ts
121
+ import "reflect-metadata";
122
+ function createEventDecorator(event, type) {
123
+ return (target, propertyKey, descriptor) => {
124
+ const constructor = target.constructor;
125
+ const existingEvents = Reflect.getMetadata(METADATA_KEYS.EVENTS, constructor) || [];
126
+ existingEvents.push({
127
+ methodName: String(propertyKey),
128
+ event,
129
+ type
130
+ });
131
+ Reflect.defineMetadata(METADATA_KEYS.EVENTS, existingEvents, constructor);
132
+ return descriptor;
133
+ };
134
+ }
135
+ function On(event) {
136
+ return createEventDecorator(event, "on");
137
+ }
138
+ function Once(event) {
139
+ return createEventDecorator(event, "once");
140
+ }
141
+ function getEventsMetadata(target) {
142
+ return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
143
+ }
144
+
119
145
  // src/decorators/utils.ts
120
146
  function buildSimpleCommandMetadata(options, methodName, category) {
121
147
  return {
@@ -139,6 +165,7 @@ var _CommandRegistry = class _CommandRegistry {
139
165
  constructor(extensions = [".js", ".mjs", ".cjs"]) {
140
166
  this.commands = /* @__PURE__ */ new Map();
141
167
  this.aliases = /* @__PURE__ */ new Map();
168
+ this.registeredEvents = [];
142
169
  this.processedStoatClasses = /* @__PURE__ */ new Set();
143
170
  this.extensions = extensions;
144
171
  }
@@ -162,7 +189,7 @@ var _CommandRegistry = class _CommandRegistry {
162
189
  await this.loadFile(file, directory);
163
190
  }
164
191
  }
165
- console.log(`[Mally] Loaded ${this.commands.size} command(s)`);
192
+ console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
166
193
  }
167
194
  /**
168
195
  * Auto-discover command files across one or more roots.
@@ -190,7 +217,7 @@ var _CommandRegistry = class _CommandRegistry {
190
217
  }) ?? roots[0];
191
218
  await this.loadFile(file, baseDir);
192
219
  }
193
- console.log(`[Mally] Auto-discovered ${candidateFiles} candidate file(s), loaded ${this.commands.size} command(s)`);
220
+ console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
194
221
  }
195
222
  getDefaultAutoDiscoveryPatterns() {
196
223
  return this.extensions.map((ext) => `**/*${ext}`);
@@ -198,7 +225,7 @@ var _CommandRegistry = class _CommandRegistry {
198
225
  async isLikelyCommandModule(filePath) {
199
226
  try {
200
227
  const source = await fs.readFile(filePath, "utf8");
201
- return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("mally:command");
228
+ return source.includes("Stoat") || source.includes("SimpleCommand") || source.includes("Command") || source.includes("stoatx:command");
202
229
  } catch {
203
230
  return true;
204
231
  }
@@ -209,7 +236,7 @@ var _CommandRegistry = class _CommandRegistry {
209
236
  register(instance, metadata, classConstructor, methodName) {
210
237
  const name = metadata.name.toLowerCase();
211
238
  if (this.commands.has(name)) {
212
- console.warn(`[Mally] Duplicate command name: ${name}. Skipping...`);
239
+ console.warn(`[Stoatx] Duplicate command name: ${name}. Skipping...`);
213
240
  return;
214
241
  }
215
242
  this.validateGuards(classConstructor, metadata.name);
@@ -217,7 +244,7 @@ var _CommandRegistry = class _CommandRegistry {
217
244
  for (const alias of metadata.aliases) {
218
245
  const aliasLower = alias.toLowerCase();
219
246
  if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
220
- console.warn(`[Mally] Duplicate alias: ${aliasLower}. Skipping...`);
247
+ console.warn(`[Stoatx] Duplicate alias: ${aliasLower}. Skipping...`);
221
248
  continue;
222
249
  }
223
250
  this.aliases.set(aliasLower, name);
@@ -250,6 +277,12 @@ var _CommandRegistry = class _CommandRegistry {
250
277
  getAllMetadata() {
251
278
  return this.getAll().map((c) => c.metadata);
252
279
  }
280
+ /**
281
+ * Get all registered events
282
+ */
283
+ getEvents() {
284
+ return this.registeredEvents;
285
+ }
253
286
  /**
254
287
  * Get commands grouped by category
255
288
  */
@@ -269,6 +302,7 @@ var _CommandRegistry = class _CommandRegistry {
269
302
  clear() {
270
303
  this.commands.clear();
271
304
  this.aliases.clear();
305
+ this.registeredEvents.length = 0;
272
306
  this.processedStoatClasses.clear();
273
307
  }
274
308
  /**
@@ -296,20 +330,20 @@ var _CommandRegistry = class _CommandRegistry {
296
330
  * @private
297
331
  */
298
332
  validateGuards(commandClass, commandName) {
299
- const guards = Reflect.getMetadata("mally:command:guards", commandClass) || [];
333
+ const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
300
334
  for (const GuardClass of guards) {
301
335
  const guardInstance = new GuardClass();
302
336
  if (typeof guardInstance.run !== "function") {
303
337
  console.error(
304
- `[Mally] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
338
+ `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
305
339
  );
306
340
  process.exit(1);
307
341
  }
308
342
  if (typeof guardInstance.guardFail !== "function") {
309
343
  console.error(
310
- `[Mally] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
344
+ `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
311
345
  );
312
- console.error(`[Mally] All guards must implement guardFail() to handle failed checks.`);
346
+ console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
313
347
  process.exit(1);
314
348
  }
315
349
  }
@@ -330,15 +364,16 @@ var _CommandRegistry = class _CommandRegistry {
330
364
  this.registerStoatClassCommands(stoatClass, stoatInstance, filePath, baseDir);
331
365
  }
332
366
  } catch (error) {
333
- console.error(`[Mally] Failed to load command file: ${filePath}`, error);
367
+ console.error(`[Stoatx] Failed to load command file: ${filePath}`, error);
334
368
  }
335
369
  }
336
370
  registerStoatClassCommands(stoatClass, instance, filePath, baseDir) {
337
371
  const simpleCommands = getSimpleCommands(stoatClass);
372
+ const events = getEventsMetadata(stoatClass);
338
373
  const category = this.getCategoryFromPath(filePath, baseDir);
339
- if (simpleCommands.length === 0) {
374
+ if (simpleCommands.length === 0 && events.length === 0) {
340
375
  console.warn(
341
- `[Mally] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand methods. Skipping...`
376
+ `[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
342
377
  );
343
378
  this.processedStoatClasses.add(stoatClass);
344
379
  return;
@@ -346,12 +381,25 @@ var _CommandRegistry = class _CommandRegistry {
346
381
  for (const cmdDef of simpleCommands) {
347
382
  const method = instance[cmdDef.methodName];
348
383
  if (typeof method !== "function") {
349
- console.warn(`[Mally] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
384
+ console.warn(`[Stoatx] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
350
385
  continue;
351
386
  }
352
387
  const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category);
353
388
  this.register(instance, metadata, stoatClass, cmdDef.methodName);
354
389
  }
390
+ for (const eventDef of events) {
391
+ const method = instance[eventDef.methodName];
392
+ if (typeof method !== "function") {
393
+ console.warn(`[Stoatx] Method ${eventDef.methodName} not found on ${stoatClass.name}. Skipping...`);
394
+ continue;
395
+ }
396
+ this.registeredEvents.push({
397
+ instance,
398
+ methodName: eventDef.methodName,
399
+ event: eventDef.event,
400
+ type: eventDef.type
401
+ });
402
+ }
355
403
  this.processedStoatClasses.add(stoatClass);
356
404
  }
357
405
  /**
@@ -377,7 +425,23 @@ var CommandRegistry = _CommandRegistry;
377
425
 
378
426
  // src/handler.ts
379
427
  import "reflect-metadata";
380
- var MallyHandler = class {
428
+ import { Client as StoatClient } from "stoat.js";
429
+ var Client = class extends StoatClient {
430
+ constructor(options) {
431
+ super();
432
+ this.handler = new StoatxHandler({ ...options, client: this });
433
+ this.on("messageCreate", async (message) => {
434
+ await this.handler.handle(message);
435
+ });
436
+ }
437
+ /**
438
+ * Initialize the StoatxHandler commands
439
+ */
440
+ async initCommands() {
441
+ await this.handler.init();
442
+ }
443
+ };
444
+ var StoatxHandler = class {
381
445
  constructor(options) {
382
446
  this.cooldowns = /* @__PURE__ */ new Map();
383
447
  this.client = options.client;
@@ -394,9 +458,33 @@ var MallyHandler = class {
394
458
  async init() {
395
459
  if (this.commandsDir) {
396
460
  await this.registry.loadFromDirectory(this.commandsDir);
397
- return;
461
+ } else {
462
+ await this.registry.autoDiscover(this.discoveryOptions);
463
+ }
464
+ this.attachEvents();
465
+ }
466
+ /**
467
+ * Attach registered events to the client
468
+ */
469
+ attachEvents() {
470
+ const events = this.registry.getEvents();
471
+ for (const eventDef of events) {
472
+ const handler = async (...args) => {
473
+ try {
474
+ await eventDef.instance[eventDef.methodName](...args, this.client);
475
+ } catch (error) {
476
+ console.error(
477
+ `[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
478
+ error
479
+ );
480
+ }
481
+ };
482
+ if (eventDef.type === "once") {
483
+ this.client.once(eventDef.event, handler);
484
+ } else {
485
+ this.client.on(eventDef.event, handler);
486
+ }
398
487
  }
399
- await this.registry.autoDiscover(this.discoveryOptions);
400
488
  }
401
489
  /**
402
490
  * Parse a raw message into command context
@@ -508,7 +596,7 @@ var MallyHandler = class {
508
596
  await ctx.reply("This command is owner-only.");
509
597
  return false;
510
598
  }
511
- const guards = Reflect.getMetadata("mally:command:guards", classConstructor) || [];
599
+ const guards = Reflect.getMetadata("stoatx:command:guards", classConstructor) || [];
512
600
  for (const guardClass of guards) {
513
601
  const guardInstance = new guardClass();
514
602
  if (typeof guardInstance.run === "function") {
@@ -517,7 +605,7 @@ var MallyHandler = class {
517
605
  if (typeof guardInstance.guardFail === "function") {
518
606
  await guardInstance.guardFail(ctx);
519
607
  } else {
520
- console.error("[Mally] Guard check failed but no guardFail method defined on", guardClass.name);
608
+ console.error("[Stoatx] Guard check failed but no guardFail method defined on", guardClass.name);
521
609
  }
522
610
  return false;
523
611
  }
@@ -542,7 +630,7 @@ var MallyHandler = class {
542
630
  if (typeof instance.onError === "function") {
543
631
  await instance.onError(ctx, error);
544
632
  } else {
545
- console.error(`[Mally] Error in command ${metadata.name}:`, error);
633
+ console.error(`[Stoatx] Error in command ${metadata.name}:`, error);
546
634
  await ctx.reply(`An error occurred: ${error.message}`);
547
635
  }
548
636
  return false;
@@ -638,13 +726,16 @@ var MallyHandler = class {
638
726
  }
639
727
  };
640
728
  export {
729
+ Client,
641
730
  CommandRegistry,
642
731
  Guard,
643
732
  METADATA_KEYS,
644
- MallyHandler,
733
+ On,
734
+ Once,
645
735
  SimpleCommand,
646
736
  Stoat,
647
737
  buildSimpleCommandMetadata,
738
+ getEventsMetadata,
648
739
  getGuards,
649
740
  getSimpleCommands,
650
741
  isStoatClass
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stoatx",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A high-performance, decorator-based command handler for the Stoat ecosystem.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,16 +23,10 @@
23
23
  "files": [
24
24
  "dist"
25
25
  ],
26
- "license": "AGPL-3.0-or-later",
26
+ "license": "MIT",
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "scripts": {
31
- "build": "tsup src/index.ts --format cjs,esm --dts",
32
- "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
33
- "lint": "eslint src/**/*.ts",
34
- "format": "prettier --write src/**/*.ts"
35
- },
36
30
  "dependencies": {
37
31
  "reflect-metadata": "^0.2.2",
38
32
  "stoat.js": "^7.3.6",
@@ -46,5 +40,10 @@
46
40
  },
47
41
  "peerDependencies": {
48
42
  "reflect-metadata": "^0.2.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.build.json",
46
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts --tsconfig tsconfig.build.json",
47
+ "lint": "eslint src/**/*.ts"
49
48
  }
50
- }
49
+ }