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/dist/index.js CHANGED
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
9
  var __export = (target, all) => {
9
10
  for (var name in all)
10
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -33,6 +34,7 @@ var index_exports = {};
33
34
  __export(index_exports, {
34
35
  Client: () => Client,
35
36
  CommandRegistry: () => CommandRegistry,
37
+ CommandValidationError: () => CommandValidationError,
36
38
  DefaultCooldownManager: () => DefaultCooldownManager,
37
39
  Guard: () => Guard,
38
40
  METADATA_KEYS: () => METADATA_KEYS,
@@ -61,6 +63,9 @@ var METADATA_KEYS = {
61
63
 
62
64
  // src/decorators/store.ts
63
65
  var DecoratorStore = class _DecoratorStore {
66
+ static {
67
+ __name(this, "DecoratorStore");
68
+ }
64
69
  static instance;
65
70
  /** Stoat classes with their SimpleCommand methods */
66
71
  stoatClasses = /* @__PURE__ */ new Map();
@@ -77,8 +82,8 @@ var DecoratorStore = class _DecoratorStore {
77
82
  return _DecoratorStore.instance;
78
83
  }
79
84
  /**
80
- * Register a @Stoat decorated class
81
- */
85
+ * Register a @Stoat decorated class
86
+ */
82
87
  registerStoatClass(classConstructor) {
83
88
  if (!this.stoatClasses.has(classConstructor)) {
84
89
  const instance = new classConstructor();
@@ -86,40 +91,40 @@ var DecoratorStore = class _DecoratorStore {
86
91
  }
87
92
  }
88
93
  /**
89
- * Get all registered Stoat classes with their instances
90
- */
94
+ * Get all registered Stoat classes with their instances
95
+ */
91
96
  getStoatClasses() {
92
97
  return this.stoatClasses;
93
98
  }
94
99
  /**
95
- * Add a registered command
96
- */
100
+ * Add a registered command
101
+ */
97
102
  addCommand(command) {
98
103
  this.commands.push(command);
99
104
  }
100
105
  /**
101
- * Get all registered commands
102
- */
106
+ * Get all registered commands
107
+ */
103
108
  getCommands() {
104
109
  return this.commands;
105
110
  }
106
111
  /**
107
- * Clear all registered classes (useful for testing)
108
- */
112
+ * Clear all registered classes (useful for testing)
113
+ */
109
114
  clear() {
110
115
  this.stoatClasses.clear();
111
116
  this.commands = [];
112
117
  this.initialized = false;
113
118
  }
114
119
  /**
115
- * Mark as initialized
116
- */
120
+ * Mark as initialized
121
+ */
117
122
  markInitialized() {
118
123
  this.initialized = true;
119
124
  }
120
125
  /**
121
- * Check if initialized
122
- */
126
+ * Check if initialized
127
+ */
123
128
  isInitialized() {
124
129
  return this.initialized;
125
130
  }
@@ -133,9 +138,11 @@ function Stoat() {
133
138
  decoratorStore.registerStoatClass(target);
134
139
  };
135
140
  }
141
+ __name(Stoat, "Stoat");
136
142
  function isStoatClass(target) {
137
143
  return Reflect.getMetadata(METADATA_KEYS.IS_STOAT_CLASS, target) === true;
138
144
  }
145
+ __name(isStoatClass, "isStoatClass");
139
146
 
140
147
  // src/decorators/SimpleCommand.ts
141
148
  var import_reflect_metadata2 = require("reflect-metadata");
@@ -151,9 +158,11 @@ function SimpleCommand(options = {}) {
151
158
  return descriptor;
152
159
  };
153
160
  }
161
+ __name(SimpleCommand, "SimpleCommand");
154
162
  function getSimpleCommands(target) {
155
163
  return Reflect.getMetadata(METADATA_KEYS.SIMPLE_COMMANDS, target) || [];
156
164
  }
165
+ __name(getSimpleCommands, "getSimpleCommands");
157
166
 
158
167
  // src/decorators/Guard.ts
159
168
  var import_reflect_metadata3 = require("reflect-metadata");
@@ -164,9 +173,11 @@ function Guard(guardClass) {
164
173
  Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
165
174
  };
166
175
  }
176
+ __name(Guard, "Guard");
167
177
  function getGuards(target) {
168
178
  return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
169
179
  }
180
+ __name(getGuards, "getGuards");
170
181
 
171
182
  // src/decorators/Events.ts
172
183
  var import_reflect_metadata4 = require("reflect-metadata");
@@ -183,15 +194,19 @@ function createEventDecorator(event, type) {
183
194
  return descriptor;
184
195
  };
185
196
  }
197
+ __name(createEventDecorator, "createEventDecorator");
186
198
  function On(event) {
187
199
  return createEventDecorator(event, "on");
188
200
  }
201
+ __name(On, "On");
189
202
  function Once(event) {
190
203
  return createEventDecorator(event, "once");
191
204
  }
205
+ __name(Once, "Once");
192
206
  function getEventsMetadata(target) {
193
207
  return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
194
208
  }
209
+ __name(getEventsMetadata, "getEventsMetadata");
195
210
 
196
211
  // src/decorators/utils.ts
197
212
  function buildSimpleCommandMetadata(options, methodName, category) {
@@ -202,11 +217,16 @@ function buildSimpleCommandMetadata(options, methodName, category) {
202
217
  permissions: options.permissions ?? [],
203
218
  category: options.category ?? category ?? "uncategorized",
204
219
  cooldown: options.cooldown ?? 0,
205
- ...options.cooldownStorage !== void 0 ? { cooldownStorage: options.cooldownStorage } : {},
220
+ ...options.cooldownStorage !== void 0 ? {
221
+ cooldownStorage: options.cooldownStorage
222
+ } : {},
206
223
  nsfw: options.nsfw ?? false,
207
- ownerOnly: options.ownerOnly ?? false
224
+ ownerOnly: options.ownerOnly ?? false,
225
+ options: options.options ?? [],
226
+ args: options.args ?? []
208
227
  };
209
228
  }
229
+ __name(buildSimpleCommandMetadata, "buildSimpleCommandMetadata");
210
230
 
211
231
  // src/registry.ts
212
232
  var path = __toESM(require("path"));
@@ -214,6 +234,9 @@ var fs = __toESM(require("fs/promises"));
214
234
  var import_node_url = require("url");
215
235
  var import_tinyglobby = require("tinyglobby");
216
236
  var CommandRegistry = class _CommandRegistry {
237
+ static {
238
+ __name(this, "CommandRegistry");
239
+ }
217
240
  static DEFAULT_AUTO_DISCOVERY_IGNORES = [
218
241
  "**/node_modules/**",
219
242
  "**/.git/**",
@@ -226,23 +249,31 @@ var CommandRegistry = class _CommandRegistry {
226
249
  registeredEvents = [];
227
250
  extensions;
228
251
  processedStoatClasses = /* @__PURE__ */ new Set();
229
- constructor(extensions = [".js", ".mjs", ".cjs"]) {
252
+ constructor(extensions = [
253
+ ".js",
254
+ ".mjs",
255
+ ".cjs"
256
+ ]) {
230
257
  this.extensions = extensions;
231
258
  }
232
259
  /**
233
- * Get the number of registered commands
234
- */
260
+ * Get the number of registered commands
261
+ */
235
262
  get size() {
236
263
  return this.commands.size;
237
264
  }
238
265
  /**
239
- * Load commands from a directory using glob pattern matching
240
- */
266
+ * Load commands from a directory using glob pattern matching
267
+ */
241
268
  async loadFromDirectory(directory) {
242
269
  const patterns = this.extensions.map((ext) => path.join(directory, "**", `*${ext}`).replace(/\\/g, "/"));
243
270
  for (const pattern of patterns) {
244
271
  const files = await (0, import_tinyglobby.glob)(pattern, {
245
- ignore: ["**/*.d.ts", "**/*.test.ts", "**/*.spec.ts"],
272
+ ignore: [
273
+ "**/*.d.ts",
274
+ "**/*.test.ts",
275
+ "**/*.spec.ts"
276
+ ],
246
277
  absolute: true
247
278
  });
248
279
  for (const file of files) {
@@ -252,19 +283,24 @@ var CommandRegistry = class _CommandRegistry {
252
283
  console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
253
284
  }
254
285
  /**
255
- * Auto-discover command files across one or more roots.
256
- */
286
+ * Auto-discover command files across one or more roots.
287
+ */
257
288
  async autoDiscover(options = {}) {
258
- const roots = options.roots?.length ? options.roots : [process.cwd()];
289
+ const roots = options.roots?.length ? options.roots : [
290
+ process.cwd()
291
+ ];
259
292
  const includePatterns = options.include?.length ? options.include : this.getDefaultAutoDiscoveryPatterns();
260
- const patterns = roots.flatMap(
261
- (root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/"))
262
- );
293
+ const patterns = roots.flatMap((root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/")));
263
294
  const files = await (0, import_tinyglobby.glob)(patterns, {
264
- ignore: [..._CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES, ...options.ignore ?? []],
295
+ ignore: [
296
+ ..._CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES,
297
+ ...options.ignore ?? []
298
+ ],
265
299
  absolute: true
266
300
  });
267
- const uniqueFiles = [...new Set(files)];
301
+ const uniqueFiles = [
302
+ ...new Set(files)
303
+ ];
268
304
  let candidateFiles = 0;
269
305
  for (const file of uniqueFiles) {
270
306
  if (!await this.isLikelyCommandModule(file)) {
@@ -291,8 +327,8 @@ var CommandRegistry = class _CommandRegistry {
291
327
  }
292
328
  }
293
329
  /**
294
- * Register a command instance
295
- */
330
+ * Register a command instance
331
+ */
296
332
  register(instance, metadata, classConstructor, methodName) {
297
333
  const name = metadata.name.toLowerCase();
298
334
  if (this.commands.has(name)) {
@@ -300,7 +336,12 @@ var CommandRegistry = class _CommandRegistry {
300
336
  return;
301
337
  }
302
338
  this.validateGuards(classConstructor, metadata.name);
303
- this.commands.set(name, { instance, metadata, methodName, classConstructor });
339
+ this.commands.set(name, {
340
+ instance,
341
+ metadata,
342
+ methodName,
343
+ classConstructor
344
+ });
304
345
  for (const alias of metadata.aliases) {
305
346
  const aliasLower = alias.toLowerCase();
306
347
  if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
@@ -311,41 +352,41 @@ var CommandRegistry = class _CommandRegistry {
311
352
  }
312
353
  }
313
354
  /**
314
- * Get a command by name or alias
315
- */
355
+ * Get a command by name or alias
356
+ */
316
357
  get(name) {
317
358
  const lowerName = name.toLowerCase();
318
359
  const resolvedName = this.aliases.get(lowerName) ?? lowerName;
319
360
  return this.commands.get(resolvedName);
320
361
  }
321
362
  /**
322
- * Check if a command exists
323
- */
363
+ * Check if a command exists
364
+ */
324
365
  has(name) {
325
366
  const lowerName = name.toLowerCase();
326
367
  return this.commands.has(lowerName) || this.aliases.has(lowerName);
327
368
  }
328
369
  /**
329
- * Get all registered commands
330
- */
370
+ * Get all registered commands
371
+ */
331
372
  getAll() {
332
373
  return Array.from(this.commands.values());
333
374
  }
334
375
  /**
335
- * Get all command metadata
336
- */
376
+ * Get all command metadata
377
+ */
337
378
  getAllMetadata() {
338
379
  return this.getAll().map((c) => c.metadata);
339
380
  }
340
381
  /**
341
- * Get all registered events
342
- */
382
+ * Get all registered events
383
+ */
343
384
  getEvents() {
344
385
  return this.registeredEvents;
345
386
  }
346
387
  /**
347
- * Get commands grouped by category
348
- */
388
+ * Get commands grouped by category
389
+ */
349
390
  getByCategory() {
350
391
  const categories = /* @__PURE__ */ new Map();
351
392
  for (const cmd of this.commands.values()) {
@@ -357,8 +398,8 @@ var CommandRegistry = class _CommandRegistry {
357
398
  return categories;
358
399
  }
359
400
  /**
360
- * Clear all commands
361
- */
401
+ * Clear all commands
402
+ */
362
403
  clear() {
363
404
  this.commands.clear();
364
405
  this.aliases.clear();
@@ -366,51 +407,47 @@ var CommandRegistry = class _CommandRegistry {
366
407
  this.processedStoatClasses.clear();
367
408
  }
368
409
  /**
369
- * Iterate over commands
370
- */
410
+ * Iterate over commands
411
+ */
371
412
  [Symbol.iterator]() {
372
413
  return this.commands.entries();
373
414
  }
374
415
  /**
375
- * Iterate over command values
376
- */
416
+ * Iterate over command values
417
+ */
377
418
  values() {
378
419
  return this.commands.values();
379
420
  }
380
421
  /**
381
- * Iterate over command names
382
- */
422
+ * Iterate over command names
423
+ */
383
424
  keys() {
384
425
  return this.commands.keys();
385
426
  }
386
427
  /**
387
- * Validate that all guards on a command implement the required methods
388
- * @param commandClass
389
- * @param commandName
390
- * @private
391
- */
428
+ * Validate that all guards on a command implement the required methods
429
+ * @param commandClass
430
+ * @param commandName
431
+ * @private
432
+ */
392
433
  validateGuards(commandClass, commandName) {
393
434
  const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
394
435
  for (const GuardClass of guards) {
395
436
  const guardInstance = new GuardClass();
396
437
  if (typeof guardInstance.run !== "function") {
397
- console.error(
398
- `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
399
- );
438
+ console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`);
400
439
  process.exit(1);
401
440
  }
402
441
  if (typeof guardInstance.guardFail !== "function") {
403
- console.error(
404
- `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
405
- );
442
+ console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`);
406
443
  console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
407
444
  process.exit(1);
408
445
  }
409
446
  }
410
447
  }
411
448
  /**
412
- * Load commands from a single file
413
- */
449
+ * Load commands from a single file
450
+ */
414
451
  async loadFile(filePath, baseDir) {
415
452
  try {
416
453
  const knownStoatClasses = new Set(decoratorStore.getStoatClasses().keys());
@@ -432,9 +469,7 @@ var CommandRegistry = class _CommandRegistry {
432
469
  const events = getEventsMetadata(stoatClass);
433
470
  const category = this.getCategoryFromPath(filePath, baseDir);
434
471
  if (simpleCommands.length === 0 && events.length === 0) {
435
- console.warn(
436
- `[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
437
- );
472
+ console.warn(`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`);
438
473
  this.processedStoatClasses.add(stoatClass);
439
474
  return;
440
475
  }
@@ -463,8 +498,8 @@ var CommandRegistry = class _CommandRegistry {
463
498
  this.processedStoatClasses.add(stoatClass);
464
499
  }
465
500
  /**
466
- * Derive category from file path relative to base directory
467
- */
501
+ * Derive category from file path relative to base directory
502
+ */
468
503
  getCategoryFromPath(filePath, baseDir) {
469
504
  const relative2 = path.relative(baseDir, filePath);
470
505
  const parts = relative2.split(path.sep);
@@ -477,7 +512,24 @@ var CommandRegistry = class _CommandRegistry {
477
512
 
478
513
  // src/handler.ts
479
514
  var import_reflect_metadata5 = require("reflect-metadata");
515
+
516
+ // src/error.ts
517
+ var CommandValidationError = class extends Error {
518
+ static {
519
+ __name(this, "CommandValidationError");
520
+ }
521
+ optionName;
522
+ constructor(optionName, message) {
523
+ super(message), this.optionName = optionName;
524
+ this.name = "CommandValidationError";
525
+ }
526
+ };
527
+
528
+ // src/handler.ts
480
529
  var DefaultCooldownManager = class {
530
+ static {
531
+ __name(this, "DefaultCooldownManager");
532
+ }
481
533
  cooldowns = /* @__PURE__ */ new Map();
482
534
  check(ctx, metadata) {
483
535
  if (metadata.cooldown <= 0) return true;
@@ -510,6 +562,9 @@ var DefaultCooldownManager = class {
510
562
  }
511
563
  };
512
564
  var StoatxHandler = class {
565
+ static {
566
+ __name(this, "StoatxHandler");
567
+ }
513
568
  commandsDir;
514
569
  discoveryOptions;
515
570
  prefixResolver;
@@ -518,6 +573,7 @@ var StoatxHandler = class {
518
573
  cooldownManager;
519
574
  disableMentionPrefix;
520
575
  client;
576
+ flagPrefix;
521
577
  constructor(options) {
522
578
  this.client = options.client;
523
579
  this.commandsDir = options.commandsDir;
@@ -527,10 +583,11 @@ var StoatxHandler = class {
527
583
  this.registry = new CommandRegistry(options.extensions);
528
584
  this.disableMentionPrefix = options.disableMentionPrefix ?? false;
529
585
  this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
586
+ this.flagPrefix = options.flagPrefix || "-";
530
587
  }
531
588
  /**
532
- * Initialize the handler - load all commands
533
- */
589
+ * Initialize the handler - load all commands
590
+ */
534
591
  async init() {
535
592
  if (this.commandsDir) {
536
593
  await this.registry.loadFromDirectory(this.commandsDir);
@@ -540,21 +597,18 @@ var StoatxHandler = class {
540
597
  this.attachEvents();
541
598
  }
542
599
  /**
543
- * Attach registered events to the client
544
- */
600
+ * Attach registered events to the client
601
+ */
545
602
  attachEvents() {
546
603
  const events = this.registry.getEvents();
547
604
  for (const eventDef of events) {
548
- const handler = async (...args) => {
605
+ const handler = /* @__PURE__ */ __name(async (...args) => {
549
606
  try {
550
607
  await eventDef.instance[eventDef.methodName](...args, this.client);
551
608
  } catch (error) {
552
- console.error(
553
- `[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
554
- error
555
- );
609
+ console.error(`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`, error);
556
610
  }
557
- };
611
+ }, "handler");
558
612
  const eventName = eventDef.event;
559
613
  if (eventDef.type === "once") {
560
614
  this.client.once(eventName, handler);
@@ -564,8 +618,38 @@ var StoatxHandler = class {
564
618
  }
565
619
  }
566
620
  /**
567
- * Parse a raw message into command context
568
- */
621
+ * Parses raw string arguments into positional args and key-value options
622
+ */
623
+ parseCommandOptions(rawArgs) {
624
+ const args = [];
625
+ const options = {};
626
+ for (let i = 0; i < rawArgs.length; i++) {
627
+ const arg = rawArgs[i];
628
+ if (arg === void 0) continue;
629
+ if (arg.startsWith(this.flagPrefix)) {
630
+ let key = arg;
631
+ while (key.startsWith(this.flagPrefix)) {
632
+ key = key.slice(this.flagPrefix.length);
633
+ }
634
+ const nextArg = rawArgs[i + 1];
635
+ if (nextArg !== void 0 && !nextArg.startsWith(this.flagPrefix)) {
636
+ options[key] = nextArg;
637
+ i++;
638
+ } else {
639
+ options[key] = true;
640
+ }
641
+ } else {
642
+ args.push(arg);
643
+ }
644
+ }
645
+ return {
646
+ args,
647
+ options
648
+ };
649
+ }
650
+ /**
651
+ * Parse a raw message into command context
652
+ */
569
653
  async parseMessage(rawContent, message, meta) {
570
654
  const prefix = await this.resolvePrefix(meta.serverId);
571
655
  let usedPrefix = prefix;
@@ -588,10 +672,11 @@ var StoatxHandler = class {
588
672
  if (!withoutPrefix) {
589
673
  return null;
590
674
  }
591
- const [commandName, ...args] = withoutPrefix.split(/\s+/);
675
+ const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
592
676
  if (!commandName) {
593
677
  return null;
594
678
  }
679
+ const { args, options } = this.parseCommandOptions(rawArgs);
595
680
  return {
596
681
  client: this.client,
597
682
  content: rawContent,
@@ -599,6 +684,7 @@ var StoatxHandler = class {
599
684
  channelId: meta.channelId,
600
685
  serverId: meta.serverId,
601
686
  args,
687
+ options,
602
688
  prefix: usedPrefix,
603
689
  commandName: commandName.toLowerCase(),
604
690
  reply: meta.reply,
@@ -606,16 +692,16 @@ var StoatxHandler = class {
606
692
  };
607
693
  }
608
694
  /**
609
- * Handle a message object using the configured message adapter
610
- *
611
- * @example
612
- * ```ts
613
- * // With message adapter configured
614
- * client.on('messageCreate', (message) => {
615
- * handler.handle(message);
616
- * });
617
- * ```
618
- */
695
+ * Handle a message object using the configured message adapter
696
+ *
697
+ * @example
698
+ * ```ts
699
+ * // With message adapter configured
700
+ * client.on('messageCreate', (message) => {
701
+ * handler.handle(message);
702
+ * });
703
+ * ```
704
+ */
619
705
  async handle(message) {
620
706
  if (!message.channel || !message.author || !message.content) {
621
707
  return false;
@@ -627,9 +713,9 @@ var StoatxHandler = class {
627
713
  const authorId = message.author.id;
628
714
  const channelId = message.channel.id;
629
715
  const serverId = message.server?.id;
630
- const reply = async (content) => {
716
+ const reply = /* @__PURE__ */ __name(async (content) => {
631
717
  return await message.channel.send(content);
632
- };
718
+ }, "reply");
633
719
  await this.handleMessage(rawContent, message, {
634
720
  authorId,
635
721
  channelId,
@@ -639,21 +725,21 @@ var StoatxHandler = class {
639
725
  return true;
640
726
  }
641
727
  /**
642
- * Handle a raw message string with metadata
643
- *
644
- * @example
645
- * ```ts
646
- * // Manual usage without message adapter
647
- * client.on('messageCreate', (message) => {
648
- * handler.handleMessage(message.content, message, {
649
- * authorId: message.author.id,
650
- * channelId: message.channel.id,
651
- * serverId: message.server?.id,
652
- * reply: (content) => message.channel.sendMessage(content),
653
- * });
654
- * });
655
- * ```
656
- */
728
+ * Handle a raw message string with metadata
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * // Manual usage without message adapter
733
+ * client.on('messageCreate', (message) => {
734
+ * handler.handleMessage(message.content, message, {
735
+ * authorId: message.author.id,
736
+ * channelId: message.channel.id,
737
+ * serverId: message.server?.id,
738
+ * reply: (content) => message.channel.sendMessage(content),
739
+ * });
740
+ * });
741
+ * ```
742
+ */
657
743
  async handleMessage(rawContent, message, meta) {
658
744
  const ctx = await this.parseMessage(rawContent, message, meta);
659
745
  if (!ctx) {
@@ -662,14 +748,15 @@ var StoatxHandler = class {
662
748
  await this.execute(ctx);
663
749
  }
664
750
  /**
665
- * Execute a command with the given context
666
- */
751
+ * Execute a command with the given context
752
+ */
667
753
  async execute(ctx) {
668
754
  const registered = this.registry.get(ctx.commandName);
669
755
  if (!registered) {
670
756
  return false;
671
757
  }
672
758
  const { instance, metadata, methodName, classConstructor } = registered;
759
+ console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
673
760
  if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
674
761
  await ctx.reply("This command is owner-only.");
675
762
  return false;
@@ -702,6 +789,190 @@ var StoatxHandler = class {
702
789
  }
703
790
  }
704
791
  }
792
+ if (metadata.args) {
793
+ const finalArgs = [];
794
+ for (let i = 0; i < metadata.args.length; i++) {
795
+ const def = metadata.args[i];
796
+ const rawValue = ctx.args[i];
797
+ if (def === void 0) continue;
798
+ if (rawValue === void 0) {
799
+ if (def.required) {
800
+ try {
801
+ throw new CommandValidationError(def.name, `Missing required argument: \`<${def.name}>\``);
802
+ } catch (error) {
803
+ if (typeof instance.onError === "function") {
804
+ await instance.onError(ctx, error);
805
+ } else {
806
+ await ctx.reply(`Missing required argument: \`<${def.name}>\``);
807
+ }
808
+ return false;
809
+ }
810
+ }
811
+ break;
812
+ }
813
+ if (def.type === "number") {
814
+ const numValue = Number(rawValue);
815
+ if (isNaN(numValue)) {
816
+ try {
817
+ throw new CommandValidationError(def.name, `Invalid value for \`<${def.name}>\`. Expected a number.`);
818
+ } catch (error) {
819
+ if (typeof instance.onError === "function") {
820
+ await instance.onError(ctx, error);
821
+ } else {
822
+ await ctx.reply(`Invalid value for \`<${def.name}>\`. Expected a number.`);
823
+ }
824
+ return false;
825
+ }
826
+ }
827
+ finalArgs[i] = numValue;
828
+ } else if (def.type === "boolean") {
829
+ finalArgs[i] = rawValue === "false" ? false : Boolean(rawValue);
830
+ } else if (def.type === "user") {
831
+ const match = String(rawValue).match(/^(?:<@)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
832
+ if (!match) {
833
+ try {
834
+ throw new CommandValidationError(def.name, `Invalid user mention for \`<${def.name}>\`.`);
835
+ } catch (error) {
836
+ if (typeof instance.onError === "function") {
837
+ await instance.onError(ctx, error);
838
+ } else {
839
+ await ctx.reply(`Invalid user mention for \`<${def.name}>\`.`);
840
+ }
841
+ return false;
842
+ }
843
+ }
844
+ finalArgs[i] = match[1];
845
+ } else if (def.type === "channel") {
846
+ const match = String(rawValue).match(/^(?:<#)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
847
+ if (!match) {
848
+ try {
849
+ throw new CommandValidationError(def.name, `Invalid channel mention for \`<${def.name}>\`.`);
850
+ } catch (error) {
851
+ if (typeof instance.onError === "function") {
852
+ await instance.onError(ctx, error);
853
+ } else {
854
+ await ctx.reply(`Invalid channel mention for \`<${def.name}>\`.`);
855
+ }
856
+ return false;
857
+ }
858
+ }
859
+ finalArgs[i] = match[1];
860
+ } else if (def.type === "role") {
861
+ const match = String(rawValue).match(/^(?:<%)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
862
+ if (!match) {
863
+ try {
864
+ throw new CommandValidationError(def.name, `Invalid role mention for \`<${def.name}>\`.`);
865
+ } catch (error) {
866
+ if (typeof instance.onError === "function") {
867
+ await instance.onError(ctx, error);
868
+ } else {
869
+ await ctx.reply(`Invalid role mention for \`<${def.name}>\`.`);
870
+ }
871
+ return false;
872
+ }
873
+ }
874
+ finalArgs[i] = match[1];
875
+ } else {
876
+ finalArgs[i] = String(rawValue);
877
+ }
878
+ }
879
+ if (ctx.args.length > metadata.args.length) {
880
+ for (let i = metadata.args.length; i < ctx.args.length; i++) {
881
+ if (ctx.args[i] !== void 0) {
882
+ finalArgs.push(ctx.args[i]);
883
+ }
884
+ }
885
+ }
886
+ ctx.args = finalArgs;
887
+ }
888
+ const finalOptions = {};
889
+ const currentOptions = ctx.options || {};
890
+ if (metadata.options) {
891
+ for (const def of metadata.options) {
892
+ const rawValue = currentOptions[def.name];
893
+ if (rawValue === void 0) {
894
+ if (def.required) {
895
+ try {
896
+ throw new CommandValidationError(def.name, `Missing required option: \`--${def.name}\``);
897
+ } catch (error) {
898
+ if (typeof instance.onError === "function") {
899
+ await instance.onError(ctx, error);
900
+ } else {
901
+ await ctx.reply(`Missing required option: \`--${def.name}\``);
902
+ }
903
+ return false;
904
+ }
905
+ }
906
+ continue;
907
+ }
908
+ if (def.type === "number") {
909
+ const numValue = Number(rawValue);
910
+ if (isNaN(numValue)) {
911
+ try {
912
+ throw new CommandValidationError(def.name, `Invalid value for \`--${def.name}\`. Expected a number.`);
913
+ } catch (error) {
914
+ if (typeof instance.onError === "function") {
915
+ await instance.onError(ctx, error);
916
+ } else {
917
+ await ctx.reply(`Invalid value for \`--${def.name}\`. Expected a number.`);
918
+ }
919
+ return false;
920
+ }
921
+ }
922
+ finalOptions[def.name] = numValue;
923
+ } else if (def.type === "boolean") {
924
+ finalOptions[def.name] = rawValue === "false" ? false : Boolean(rawValue);
925
+ } else if (def.type === "user") {
926
+ const match = String(rawValue).match(/^(?:<@)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
927
+ if (!match) {
928
+ try {
929
+ throw new CommandValidationError(def.name, `Invalid user mention for \`--${def.name}\`.`);
930
+ } catch (error) {
931
+ if (typeof instance.onError === "function") {
932
+ await instance.onError(ctx, error);
933
+ } else {
934
+ await ctx.reply(`Invalid user mention for \`--${def.name}\`.`);
935
+ }
936
+ return false;
937
+ }
938
+ }
939
+ finalOptions[def.name] = match[1];
940
+ } else if (def.type === "channel") {
941
+ const match = String(rawValue).match(/^(?:<#)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
942
+ if (!match) {
943
+ try {
944
+ throw new CommandValidationError(def.name, `Invalid channel mention for \`--${def.name}\`.`);
945
+ } catch (error) {
946
+ if (typeof instance.onError === "function") {
947
+ await instance.onError(ctx, error);
948
+ } else {
949
+ await ctx.reply(`Invalid channel mention for \`--${def.name}\`.`);
950
+ }
951
+ return false;
952
+ }
953
+ }
954
+ finalOptions[def.name] = match[1];
955
+ } else if (def.type === "role") {
956
+ const match = String(rawValue).match(/^(?:<%)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
957
+ if (!match) {
958
+ try {
959
+ throw new CommandValidationError(def.name, `Invalid role mention for \`--${def.name}\`.`);
960
+ } catch (error) {
961
+ if (typeof instance.onError === "function") {
962
+ await instance.onError(ctx, error);
963
+ } else {
964
+ await ctx.reply(`Invalid role mention for \`--${def.name}\`.`);
965
+ }
966
+ return false;
967
+ }
968
+ }
969
+ finalOptions[def.name] = match[1];
970
+ } else {
971
+ finalOptions[def.name] = String(rawValue);
972
+ }
973
+ }
974
+ ctx.options = finalOptions;
975
+ }
705
976
  if (!await this.cooldownManager.check(ctx, metadata)) {
706
977
  const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
707
978
  if (typeof instance.onCooldown === "function") {
@@ -727,26 +998,26 @@ var StoatxHandler = class {
727
998
  }
728
999
  }
729
1000
  /**
730
- * Get the command registry
731
- */
1001
+ * Get the command registry
1002
+ */
732
1003
  getRegistry() {
733
1004
  return this.registry;
734
1005
  }
735
1006
  /**
736
- * Get a command by name or alias
737
- */
1007
+ * Get a command by name or alias
1008
+ */
738
1009
  getCommand(name) {
739
1010
  return this.registry.get(name);
740
1011
  }
741
1012
  /**
742
- * Get all commands
743
- */
1013
+ * Get all commands
1014
+ */
744
1015
  getCommands() {
745
1016
  return this.registry.getAll();
746
1017
  }
747
1018
  /**
748
- * Reload all commands
749
- */
1019
+ * Reload all commands
1020
+ */
750
1021
  async reload() {
751
1022
  this.registry.clear();
752
1023
  if (this.cooldownManager.clear) {
@@ -759,29 +1030,31 @@ var StoatxHandler = class {
759
1030
  await this.registry.autoDiscover(this.discoveryOptions);
760
1031
  }
761
1032
  /**
762
- * Check if a user is an owner
763
- */
1033
+ * Check if a user is an owner
1034
+ */
764
1035
  isOwner(userId) {
765
1036
  return this.owners.has(userId);
766
1037
  }
767
1038
  /**
768
- * Add an owner
769
- */
1039
+ * Add an owner
1040
+ */
770
1041
  addOwner(userId) {
771
1042
  this.owners.add(userId);
772
1043
  }
773
1044
  /**
774
- * Remove an owner
775
- */
1045
+ * Remove an owner
1046
+ */
776
1047
  removeOwner(userId) {
777
1048
  this.owners.delete(userId);
778
1049
  }
779
1050
  /**
780
- * Resolve the prefix for a context
781
- */
1051
+ * Resolve the prefix for a context
1052
+ */
782
1053
  async resolvePrefix(serverId) {
783
1054
  if (typeof this.prefixResolver === "function") {
784
- return this.prefixResolver({ serverId });
1055
+ return this.prefixResolver({
1056
+ serverId
1057
+ });
785
1058
  }
786
1059
  return this.prefixResolver;
787
1060
  }
@@ -790,16 +1063,23 @@ var StoatxHandler = class {
790
1063
  // src/client.ts
791
1064
  var import_client = require("@stoatx/client");
792
1065
  var Client = class extends import_client.Client {
1066
+ static {
1067
+ __name(this, "Client");
1068
+ }
793
1069
  handler;
794
1070
  constructor(options) {
795
- super();
796
- this.handler = new StoatxHandler({ ...options, client: this });
797
- this.on("messageCreate", async (message) => {
798
- await this.handler.handle(message);
1071
+ super(options);
1072
+ this.handler = new StoatxHandler({
1073
+ ...options,
1074
+ client: this
799
1075
  });
800
1076
  }
801
- async initCommands() {
1077
+ async login(token) {
802
1078
  await this.handler.init();
1079
+ return super.login(token);
1080
+ }
1081
+ async executeCommand(message) {
1082
+ await this.handler.handle(message);
803
1083
  }
804
1084
  };
805
1085
 
@@ -809,6 +1089,7 @@ __reExport(index_exports, require("@stoatx/client"), module.exports);
809
1089
  0 && (module.exports = {
810
1090
  Client,
811
1091
  CommandRegistry,
1092
+ CommandValidationError,
812
1093
  DefaultCooldownManager,
813
1094
  Guard,
814
1095
  METADATA_KEYS,