stoatx 0.8.0 → 1.0.1

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
@@ -32,16 +32,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  // src/index.ts
33
33
  var index_exports = {};
34
34
  __export(index_exports, {
35
+ Arg: () => Arg,
35
36
  Client: () => Client,
37
+ CommandGroup: () => CommandGroup,
36
38
  CommandRegistry: () => CommandRegistry,
37
39
  CommandValidationError: () => CommandValidationError,
38
40
  DefaultCooldownManager: () => DefaultCooldownManager,
41
+ FetchFailedError: () => FetchFailedError,
39
42
  Guard: () => Guard,
43
+ Injectable: () => Injectable,
44
+ InvalidMentionError: () => InvalidMentionError,
45
+ InvalidTypeError: () => InvalidTypeError,
40
46
  METADATA_KEYS: () => METADATA_KEYS,
47
+ MissingArgumentError: () => MissingArgumentError,
48
+ MissingOptionError: () => MissingOptionError,
49
+ NoServerContextError: () => NoServerContextError,
41
50
  On: () => On,
42
51
  Once: () => Once,
52
+ Option: () => Option,
53
+ PARAM_TYPE_MAP: () => PARAM_TYPE_MAP,
43
54
  SimpleCommand: () => SimpleCommand,
44
55
  Stoat: () => Stoat,
56
+ StoatxError: () => StoatxError,
57
+ SubCommand: () => SubCommand,
45
58
  buildSimpleCommandMetadata: () => buildSimpleCommandMetadata,
46
59
  getEventsMetadata: () => getEventsMetadata,
47
60
  getGuards: () => getGuards,
@@ -50,6 +63,35 @@ __export(index_exports, {
50
63
  });
51
64
  module.exports = __toCommonJS(index_exports);
52
65
 
66
+ // src/types.ts
67
+ var import_client = require("@stoatx/client");
68
+ var PARAM_TYPE_MAP = /* @__PURE__ */ new Map([
69
+ [
70
+ String,
71
+ "string"
72
+ ],
73
+ [
74
+ Number,
75
+ "number"
76
+ ],
77
+ [
78
+ Boolean,
79
+ "boolean"
80
+ ],
81
+ [
82
+ import_client.User,
83
+ "user"
84
+ ],
85
+ [
86
+ import_client.BaseChannel,
87
+ "channel"
88
+ ],
89
+ [
90
+ import_client.Role,
91
+ "role"
92
+ ]
93
+ ]);
94
+
53
95
  // src/decorators/Stoat.ts
54
96
  var import_reflect_metadata = require("reflect-metadata");
55
97
 
@@ -58,7 +100,12 @@ var METADATA_KEYS = {
58
100
  IS_STOAT_CLASS: /* @__PURE__ */ Symbol("stoatx:stoat:isClass"),
59
101
  SIMPLE_COMMANDS: /* @__PURE__ */ Symbol("stoatx:stoat:simpleCommands"),
60
102
  GUARDS: "stoatx:command:guards",
61
- EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events")
103
+ EVENTS: /* @__PURE__ */ Symbol("stoatx:stoat:events"),
104
+ ARGS: /* @__PURE__ */ Symbol("stoatx:param:args"),
105
+ OPTIONS: /* @__PURE__ */ Symbol("stoatx:param:options"),
106
+ INJECTABLE: /* @__PURE__ */ Symbol("stoatx:injectable"),
107
+ SUBCOMMAND: /* @__PURE__ */ Symbol("stoatx:subcommand"),
108
+ COMMAND_GROUP: /* @__PURE__ */ Symbol("stoatx:commandGroup")
62
109
  };
63
110
 
64
111
  // src/decorators/store.ts
@@ -68,7 +115,7 @@ var DecoratorStore = class _DecoratorStore {
68
115
  }
69
116
  static instance;
70
117
  /** Stoat classes with their SimpleCommand methods */
71
- stoatClasses = /* @__PURE__ */ new Map();
118
+ stoatClasses = /* @__PURE__ */ new Set();
72
119
  /** Registered commands from @Stoat/@SimpleCommand decorators */
73
120
  commands = [];
74
121
  /** Whether the store has been initialized */
@@ -85,10 +132,7 @@ var DecoratorStore = class _DecoratorStore {
85
132
  * Register a @Stoat decorated class
86
133
  */
87
134
  registerStoatClass(classConstructor) {
88
- if (!this.stoatClasses.has(classConstructor)) {
89
- const instance = new classConstructor();
90
- this.stoatClasses.set(classConstructor, instance);
91
- }
135
+ this.stoatClasses.add(classConstructor);
92
136
  }
93
137
  /**
94
138
  * Get all registered Stoat classes with their instances
@@ -167,14 +211,23 @@ __name(getSimpleCommands, "getSimpleCommands");
167
211
  // src/decorators/Guard.ts
168
212
  var import_reflect_metadata3 = require("reflect-metadata");
169
213
  function Guard(guardClass) {
170
- return (target) => {
171
- const existingGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
172
- existingGuards.push(guardClass);
173
- Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
214
+ return (target, propertyKey) => {
215
+ if (propertyKey) {
216
+ const existingGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, target, propertyKey) || [];
217
+ existingGuards.push(guardClass);
218
+ Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target, propertyKey);
219
+ } else {
220
+ const existingGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
221
+ existingGuards.push(guardClass);
222
+ Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
223
+ }
174
224
  };
175
225
  }
176
226
  __name(Guard, "Guard");
177
- function getGuards(target) {
227
+ function getGuards(target, propertyKey) {
228
+ if (propertyKey) {
229
+ return Reflect.getMetadata(METADATA_KEYS.GUARDS, target.prototype, propertyKey) || [];
230
+ }
178
231
  return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
179
232
  }
180
233
  __name(getGuards, "getGuards");
@@ -208,8 +261,74 @@ function getEventsMetadata(target) {
208
261
  }
209
262
  __name(getEventsMetadata, "getEventsMetadata");
210
263
 
264
+ // src/decorators/Arg.ts
265
+ var import_reflect_metadata5 = require("reflect-metadata");
266
+ function Arg(options = {}) {
267
+ return (target, propertyKey, parameterIndex) => {
268
+ const existing = Reflect.getMetadata(METADATA_KEYS.ARGS, target, propertyKey) || [];
269
+ existing.push({
270
+ ...options,
271
+ index: parameterIndex
272
+ });
273
+ Reflect.defineMetadata(METADATA_KEYS.ARGS, existing, target, propertyKey);
274
+ };
275
+ }
276
+ __name(Arg, "Arg");
277
+ function getArgs(target, propertyKey) {
278
+ return Reflect.getMetadata(METADATA_KEYS.ARGS, target, propertyKey) || [];
279
+ }
280
+ __name(getArgs, "getArgs");
281
+
282
+ // src/decorators/Option.ts
283
+ var import_reflect_metadata6 = require("reflect-metadata");
284
+ function Option(options) {
285
+ return (target, propertyKey, parameterIndex) => {
286
+ const existing = Reflect.getMetadata(METADATA_KEYS.OPTIONS, target, propertyKey) || [];
287
+ existing.push({
288
+ ...options,
289
+ index: parameterIndex
290
+ });
291
+ Reflect.defineMetadata(METADATA_KEYS.OPTIONS, existing, target, propertyKey);
292
+ };
293
+ }
294
+ __name(Option, "Option");
295
+ function getOptions(target, propertyKey) {
296
+ return Reflect.getMetadata(METADATA_KEYS.OPTIONS, target, propertyKey) || [];
297
+ }
298
+ __name(getOptions, "getOptions");
299
+
300
+ // src/decorators/Injectable.ts
301
+ var import_reflect_metadata7 = require("reflect-metadata");
302
+ function Injectable() {
303
+ return (target) => {
304
+ Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
305
+ };
306
+ }
307
+ __name(Injectable, "Injectable");
308
+
309
+ // src/decorators/CommandGroup.ts
310
+ var import_reflect_metadata8 = require("reflect-metadata");
311
+ function CommandGroup(name) {
312
+ return (target) => {
313
+ Reflect.defineMetadata(METADATA_KEYS.COMMAND_GROUP, name, target);
314
+ };
315
+ }
316
+ __name(CommandGroup, "CommandGroup");
317
+
318
+ // src/decorators/SubCommand.ts
319
+ var import_reflect_metadata9 = require("reflect-metadata");
320
+ function SubCommand(options) {
321
+ const opts = typeof options === "string" ? {
322
+ name: options
323
+ } : options;
324
+ return (target, propertyKey) => {
325
+ Reflect.defineMetadata(METADATA_KEYS.SUBCOMMAND, opts, target, propertyKey);
326
+ };
327
+ }
328
+ __name(SubCommand, "SubCommand");
329
+
211
330
  // src/decorators/utils.ts
212
- function buildSimpleCommandMetadata(options, methodName, category) {
331
+ function buildSimpleCommandMetadata(options, methodName, category, params) {
213
332
  return {
214
333
  name: options.name ?? methodName.toLowerCase(),
215
334
  description: options.description ?? "No description provided",
@@ -222,11 +341,25 @@ function buildSimpleCommandMetadata(options, methodName, category) {
222
341
  } : {},
223
342
  nsfw: options.nsfw ?? false,
224
343
  ownerOnly: options.ownerOnly ?? false,
225
- options: options.options ?? [],
226
- args: options.args ?? []
344
+ params
227
345
  };
228
346
  }
229
347
  __name(buildSimpleCommandMetadata, "buildSimpleCommandMetadata");
348
+ function getSubCommands(target) {
349
+ const methods = Object.getOwnPropertyNames(target.prototype);
350
+ const subCommands = [];
351
+ for (const method of methods) {
352
+ const options = Reflect.getMetadata(METADATA_KEYS.SUBCOMMAND, target.prototype, method);
353
+ if (options) {
354
+ subCommands.push({
355
+ methodName: method,
356
+ options
357
+ });
358
+ }
359
+ }
360
+ return subCommands;
361
+ }
362
+ __name(getSubCommands, "getSubCommands");
230
363
 
231
364
  // src/registry.ts
232
365
  var path = __toESM(require("path"));
@@ -249,22 +382,18 @@ var CommandRegistry = class _CommandRegistry {
249
382
  registeredEvents = [];
250
383
  extensions;
251
384
  processedStoatClasses = /* @__PURE__ */ new Set();
252
- constructor(extensions = [
385
+ container;
386
+ constructor(container, extensions = [
253
387
  ".js",
254
388
  ".mjs",
255
389
  ".cjs"
256
390
  ]) {
391
+ this.container = container;
257
392
  this.extensions = extensions;
258
393
  }
259
- /**
260
- * Get the number of registered commands
261
- */
262
394
  get size() {
263
395
  return this.commands.size;
264
396
  }
265
- /**
266
- * Load commands from a directory using glob pattern matching
267
- */
268
397
  async loadFromDirectory(directory) {
269
398
  const patterns = this.extensions.map((ext) => path.join(directory, "**", `*${ext}`).replace(/\\/g, "/"));
270
399
  for (const pattern of patterns) {
@@ -282,9 +411,6 @@ var CommandRegistry = class _CommandRegistry {
282
411
  }
283
412
  console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
284
413
  }
285
- /**
286
- * Auto-discover command files across one or more roots.
287
- */
288
414
  async autoDiscover(options = {}) {
289
415
  const roots = options.roots?.length ? options.roots : [
290
416
  process.cwd()
@@ -301,12 +427,10 @@ var CommandRegistry = class _CommandRegistry {
301
427
  const uniqueFiles = [
302
428
  ...new Set(files)
303
429
  ];
304
- let candidateFiles = 0;
305
430
  for (const file of uniqueFiles) {
306
431
  if (!await this.isLikelyCommandModule(file)) {
307
432
  continue;
308
433
  }
309
- candidateFiles++;
310
434
  const baseDir = roots.find((root) => {
311
435
  const relative2 = path.relative(root, file);
312
436
  return relative2 && !relative2.startsWith("..") && !path.isAbsolute(relative2);
@@ -326,11 +450,11 @@ var CommandRegistry = class _CommandRegistry {
326
450
  return true;
327
451
  }
328
452
  }
329
- /**
330
- * Register a command instance
331
- */
332
453
  register(instance, metadata, classConstructor, methodName) {
333
- const name = metadata.name.toLowerCase();
454
+ const groupOptions = Reflect.getMetadata(METADATA_KEYS.COMMAND_GROUP, classConstructor);
455
+ const subCommandOptions = Reflect.getMetadata(METADATA_KEYS.SUBCOMMAND, instance, methodName);
456
+ const subCommandName = subCommandOptions?.name;
457
+ const name = groupOptions && subCommandName ? `${groupOptions.name}:${subCommandName}`.toLowerCase() : metadata.name.toLowerCase();
334
458
  if (this.commands.has(name)) {
335
459
  console.warn(`[Stoatx] Duplicate command name: ${name}. Skipping...`);
336
460
  return;
@@ -351,42 +475,24 @@ var CommandRegistry = class _CommandRegistry {
351
475
  this.aliases.set(aliasLower, name);
352
476
  }
353
477
  }
354
- /**
355
- * Get a command by name or alias
356
- */
357
478
  get(name) {
358
479
  const lowerName = name.toLowerCase();
359
480
  const resolvedName = this.aliases.get(lowerName) ?? lowerName;
360
481
  return this.commands.get(resolvedName);
361
482
  }
362
- /**
363
- * Check if a command exists
364
- */
365
483
  has(name) {
366
484
  const lowerName = name.toLowerCase();
367
485
  return this.commands.has(lowerName) || this.aliases.has(lowerName);
368
486
  }
369
- /**
370
- * Get all registered commands
371
- */
372
487
  getAll() {
373
488
  return Array.from(this.commands.values());
374
489
  }
375
- /**
376
- * Get all command metadata
377
- */
378
490
  getAllMetadata() {
379
491
  return this.getAll().map((c) => c.metadata);
380
492
  }
381
- /**
382
- * Get all registered events
383
- */
384
493
  getEvents() {
385
494
  return this.registeredEvents;
386
495
  }
387
- /**
388
- * Get commands grouped by category
389
- */
390
496
  getByCategory() {
391
497
  const categories = /* @__PURE__ */ new Map();
392
498
  for (const cmd of this.commands.values()) {
@@ -397,89 +503,70 @@ var CommandRegistry = class _CommandRegistry {
397
503
  }
398
504
  return categories;
399
505
  }
400
- /**
401
- * Clear all commands
402
- */
403
506
  clear() {
404
507
  this.commands.clear();
405
508
  this.aliases.clear();
406
509
  this.registeredEvents.length = 0;
407
510
  this.processedStoatClasses.clear();
408
511
  }
409
- /**
410
- * Iterate over commands
411
- */
412
512
  [Symbol.iterator]() {
413
513
  return this.commands.entries();
414
514
  }
415
- /**
416
- * Iterate over command values
417
- */
418
515
  values() {
419
516
  return this.commands.values();
420
517
  }
421
- /**
422
- * Iterate over command names
423
- */
424
518
  keys() {
425
519
  return this.commands.keys();
426
520
  }
427
- /**
428
- * Validate that all guards on a command implement the required methods
429
- * @param commandClass
430
- * @param commandName
431
- * @private
432
- */
433
521
  validateGuards(commandClass, commandName) {
434
- const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
522
+ const guards = Reflect.getMetadata(METADATA_KEYS.GUARDS, commandClass) || [];
435
523
  for (const GuardClass of guards) {
436
- const guardInstance = new GuardClass();
524
+ const guardInstance = this.container.resolve(GuardClass);
437
525
  if (typeof guardInstance.run !== "function") {
438
526
  console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`);
439
527
  process.exit(1);
440
528
  }
441
529
  if (typeof guardInstance.guardFail !== "function") {
442
530
  console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`);
443
- console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
444
531
  process.exit(1);
445
532
  }
446
533
  }
447
534
  }
448
- /**
449
- * Load commands from a single file
450
- */
451
535
  async loadFile(filePath, baseDir) {
452
536
  try {
453
537
  const knownStoatClasses = new Set(decoratorStore.getStoatClasses().keys());
454
538
  const fileUrl = (0, import_node_url.pathToFileURL)(filePath).href;
455
539
  await import(fileUrl);
456
540
  const allStoatClasses = decoratorStore.getStoatClasses();
457
- for (const [stoatClass, stoatInstance] of allStoatClasses.entries()) {
541
+ for (const [stoatClass] of allStoatClasses.entries()) {
458
542
  if (knownStoatClasses.has(stoatClass) || this.processedStoatClasses.has(stoatClass)) {
459
543
  continue;
460
544
  }
461
- this.registerStoatClassCommands(stoatClass, stoatInstance, filePath, baseDir);
545
+ this.registerStoatClassCommands(stoatClass, filePath, baseDir);
462
546
  }
463
547
  } catch (error) {
464
548
  console.error(`[Stoatx] Failed to load command file: ${filePath}`, error);
465
549
  }
466
550
  }
467
- registerStoatClassCommands(stoatClass, instance, filePath, baseDir) {
551
+ registerStoatClassCommands(stoatClass, filePath, baseDir) {
552
+ const instance = this.container.resolve(stoatClass);
468
553
  const simpleCommands = getSimpleCommands(stoatClass);
554
+ const subCommands = getSubCommands(stoatClass);
469
555
  const events = getEventsMetadata(stoatClass);
470
556
  const category = this.getCategoryFromPath(filePath, baseDir);
471
- if (simpleCommands.length === 0 && events.length === 0) {
472
- console.warn(`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`);
557
+ const allCommands = [
558
+ ...simpleCommands,
559
+ ...subCommands
560
+ ];
561
+ if (allCommands.length === 0 && events.length === 0) {
473
562
  this.processedStoatClasses.add(stoatClass);
474
563
  return;
475
564
  }
476
- for (const cmdDef of simpleCommands) {
565
+ for (const cmdDef of allCommands) {
477
566
  const method = instance[cmdDef.methodName];
478
- if (typeof method !== "function") {
479
- console.warn(`[Stoatx] Method ${cmdDef.methodName} not found on ${stoatClass.name}. Skipping...`);
480
- continue;
481
- }
482
- const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category);
567
+ if (typeof method !== "function") continue;
568
+ const params = this.buildParamSchema(stoatClass.prototype, cmdDef.methodName);
569
+ const metadata = buildSimpleCommandMetadata(cmdDef.options, cmdDef.methodName, category, params);
483
570
  this.register(instance, metadata, stoatClass, cmdDef.methodName);
484
571
  }
485
572
  for (const eventDef of events) {
@@ -498,32 +585,174 @@ var CommandRegistry = class _CommandRegistry {
498
585
  this.processedStoatClasses.add(stoatClass);
499
586
  }
500
587
  /**
501
- * Derive category from file path relative to base directory
588
+ * Build the parameter schema for a command method by combining
589
+ * reflect-metadata param types with @Arg/@Option decorator metadata.
502
590
  */
591
+ buildParamSchema(prototype, methodName) {
592
+ const paramTypes = Reflect.getMetadata("design:paramtypes", prototype, methodName) ?? [];
593
+ const argDefs = getArgs(prototype, methodName);
594
+ const optionDefs = getOptions(prototype, methodName);
595
+ const argByIndex = new Map(argDefs.map((a) => [
596
+ a.index,
597
+ a
598
+ ]));
599
+ const optionByIndex = new Map(optionDefs.map((o) => [
600
+ o.index,
601
+ o
602
+ ]));
603
+ const params = [];
604
+ for (let i = 0; i < paramTypes.length; i++) {
605
+ const reflectedType = paramTypes[i];
606
+ if (optionByIndex.has(i)) {
607
+ const optDef = optionByIndex.get(i);
608
+ const resolvedType = reflectedType ? PARAM_TYPE_MAP.get(reflectedType) ?? "string" : "string";
609
+ params.push({
610
+ index: i,
611
+ kind: "option",
612
+ resolvedType,
613
+ name: optDef.name,
614
+ required: optDef.required
615
+ });
616
+ continue;
617
+ }
618
+ if (argByIndex.has(i)) {
619
+ const argDef = argByIndex.get(i);
620
+ const resolvedType = reflectedType ? PARAM_TYPE_MAP.get(reflectedType) ?? "string" : "string";
621
+ params.push({
622
+ index: i,
623
+ kind: "arg",
624
+ resolvedType,
625
+ required: argDef.required,
626
+ fetch: argDef.fetch
627
+ });
628
+ continue;
629
+ }
630
+ params.push({
631
+ index: i,
632
+ kind: "ctx",
633
+ resolvedType: "ctx"
634
+ });
635
+ }
636
+ return params;
637
+ }
503
638
  getCategoryFromPath(filePath, baseDir) {
504
639
  const relative2 = path.relative(baseDir, filePath);
505
640
  const parts = relative2.split(path.sep);
506
- if (parts.length > 1) {
507
- return parts[0];
508
- }
509
- return void 0;
641
+ return parts.length > 1 ? parts[0] : void 0;
510
642
  }
511
643
  };
512
644
 
513
645
  // src/handler.ts
514
- var import_reflect_metadata5 = require("reflect-metadata");
646
+ var import_reflect_metadata11 = require("reflect-metadata");
515
647
 
516
648
  // src/error.ts
517
- var CommandValidationError = class extends Error {
649
+ var StoatxError = class extends Error {
650
+ static {
651
+ __name(this, "StoatxError");
652
+ }
653
+ constructor(message) {
654
+ super(message);
655
+ this.name = "StoatxError";
656
+ }
657
+ };
658
+ var CommandValidationError = class extends StoatxError {
518
659
  static {
519
660
  __name(this, "CommandValidationError");
520
661
  }
521
- optionName;
522
- constructor(optionName, message) {
523
- super(message), this.optionName = optionName;
662
+ paramName;
663
+ paramKind;
664
+ constructor(paramName, paramKind, message) {
665
+ super(message), this.paramName = paramName, this.paramKind = paramKind;
524
666
  this.name = "CommandValidationError";
525
667
  }
526
668
  };
669
+ var MissingArgumentError = class extends CommandValidationError {
670
+ static {
671
+ __name(this, "MissingArgumentError");
672
+ }
673
+ constructor(paramName) {
674
+ super(paramName, "arg", `Missing required argument: \`<${paramName}>\``);
675
+ this.name = "MissingArgumentError";
676
+ }
677
+ };
678
+ var MissingOptionError = class extends CommandValidationError {
679
+ static {
680
+ __name(this, "MissingOptionError");
681
+ }
682
+ constructor(paramName, flagPrefix) {
683
+ super(paramName, "option", `Missing required option: \`${flagPrefix.repeat(2)}${paramName}\``);
684
+ this.name = "MissingOptionError";
685
+ }
686
+ };
687
+ var InvalidTypeError = class extends CommandValidationError {
688
+ static {
689
+ __name(this, "InvalidTypeError");
690
+ }
691
+ expected;
692
+ received;
693
+ constructor(paramName, paramKind, expected, received) {
694
+ super(paramName, paramKind, `Invalid value for \`${paramName}\`. Expected ${expected}, got \`${received}\`.`), this.expected = expected, this.received = received;
695
+ this.name = "InvalidTypeError";
696
+ }
697
+ };
698
+ var InvalidMentionError = class extends CommandValidationError {
699
+ static {
700
+ __name(this, "InvalidMentionError");
701
+ }
702
+ mentionKind;
703
+ rawValue;
704
+ constructor(paramName, paramKind, mentionKind, rawValue) {
705
+ super(paramName, paramKind, `Invalid ${mentionKind} mention for \`${paramName}\`.`), this.mentionKind = mentionKind, this.rawValue = rawValue;
706
+ this.name = "InvalidMentionError";
707
+ }
708
+ };
709
+ var FetchFailedError = class extends CommandValidationError {
710
+ static {
711
+ __name(this, "FetchFailedError");
712
+ }
713
+ mentionKind;
714
+ resolvedId;
715
+ constructor(paramName, paramKind, mentionKind, resolvedId) {
716
+ super(paramName, paramKind, `Could not fetch ${mentionKind} \`${resolvedId}\` for \`${paramName}\`.`), this.mentionKind = mentionKind, this.resolvedId = resolvedId;
717
+ this.name = "FetchFailedError";
718
+ }
719
+ };
720
+ var NoServerContextError = class extends CommandValidationError {
721
+ static {
722
+ __name(this, "NoServerContextError");
723
+ }
724
+ constructor(paramName, paramKind) {
725
+ super(paramName, paramKind, `Cannot fetch role for \`${paramName}\` outside of a server.`);
726
+ this.name = "NoServerContextError";
727
+ }
728
+ };
729
+
730
+ // src/di/Container.ts
731
+ var import_reflect_metadata10 = require("reflect-metadata");
732
+ var StoatxContainer = class {
733
+ static {
734
+ __name(this, "StoatxContainer");
735
+ }
736
+ instances = /* @__PURE__ */ new Map();
737
+ /**
738
+ * Resolves a class instance, injecting any required dependencies.
739
+ */
740
+ resolve(target) {
741
+ if (this.instances.has(target)) {
742
+ return this.instances.get(target);
743
+ }
744
+ const paramTypes = Reflect.getMetadata("design:paramtypes", target) || [];
745
+ const injections = paramTypes.map((param) => {
746
+ if (!param) {
747
+ throw new Error(`[Stoatx DI] Cannot resolve dependency for ${target.name}. Ensure all injected services are decorated with @Injectable().`);
748
+ }
749
+ return this.resolve(param);
750
+ });
751
+ const instance = new target(...injections);
752
+ this.instances.set(target, instance);
753
+ return instance;
754
+ }
755
+ };
527
756
 
528
757
  // src/handler.ts
529
758
  var DefaultCooldownManager = class {
@@ -567,6 +796,7 @@ var StoatxHandler = class {
567
796
  }
568
797
  commandsDir;
569
798
  discoveryOptions;
799
+ // After
570
800
  prefixResolver;
571
801
  owners;
572
802
  registry;
@@ -574,20 +804,20 @@ var StoatxHandler = class {
574
804
  disableMentionPrefix;
575
805
  client;
576
806
  flagPrefix;
807
+ globalGuards;
808
+ container = new StoatxContainer();
577
809
  constructor(options) {
578
810
  this.client = options.client;
579
811
  this.commandsDir = options.commandsDir;
580
812
  this.discoveryOptions = options.discovery;
581
813
  this.prefixResolver = options.prefix;
582
814
  this.owners = new Set(options.owners ?? []);
583
- this.registry = new CommandRegistry(options.extensions);
815
+ this.registry = new CommandRegistry(this.container, options.extensions);
584
816
  this.disableMentionPrefix = options.disableMentionPrefix ?? false;
585
817
  this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
586
818
  this.flagPrefix = options.flagPrefix || "-";
819
+ this.globalGuards = options.globalGuards ?? [];
587
820
  }
588
- /**
589
- * Initialize the handler - load all commands
590
- */
591
821
  async init() {
592
822
  if (this.commandsDir) {
593
823
  await this.registry.loadFromDirectory(this.commandsDir);
@@ -596,9 +826,6 @@ var StoatxHandler = class {
596
826
  }
597
827
  this.attachEvents();
598
828
  }
599
- /**
600
- * Attach registered events to the client
601
- */
602
829
  attachEvents() {
603
830
  const events = this.registry.getEvents();
604
831
  for (const eventDef of events) {
@@ -617,12 +844,9 @@ var StoatxHandler = class {
617
844
  }
618
845
  }
619
846
  }
620
- /**
621
- * Parses raw string arguments into positional args and key-value options
622
- */
623
- parseCommandOptions(rawArgs) {
847
+ parseRawInput(rawArgs) {
624
848
  const args = [];
625
- const options = {};
849
+ const flags = {};
626
850
  for (let i = 0; i < rawArgs.length; i++) {
627
851
  const arg = rawArgs[i];
628
852
  if (arg === void 0) continue;
@@ -633,10 +857,10 @@ var StoatxHandler = class {
633
857
  }
634
858
  const nextArg = rawArgs[i + 1];
635
859
  if (nextArg !== void 0 && !nextArg.startsWith(this.flagPrefix)) {
636
- options[key] = nextArg;
860
+ flags[key] = nextArg;
637
861
  i++;
638
862
  } else {
639
- options[key] = true;
863
+ flags[key] = true;
640
864
  }
641
865
  } else {
642
866
  args.push(arg);
@@ -644,78 +868,62 @@ var StoatxHandler = class {
644
868
  }
645
869
  return {
646
870
  args,
647
- options
871
+ flags
648
872
  };
649
873
  }
650
- /**
651
- * Parse a raw message into command context
652
- */
653
874
  async parseMessage(rawContent, message, meta) {
654
- const prefix = await this.resolvePrefix(meta.serverId);
655
- let usedPrefix = prefix;
875
+ const prefixes = await this.resolvePrefix(meta.serverId);
876
+ let usedPrefix = "";
656
877
  let withoutPrefix = "";
657
- if (rawContent.startsWith(prefix)) {
658
- withoutPrefix = rawContent.slice(prefix.length).trim();
659
- usedPrefix = prefix;
660
- } else if (!this.disableMentionPrefix && rawContent.match(/^<@!?[\w]+>/)) {
661
- const mentionMatch = rawContent.match(/^<@!?([\w]+)>\s*/);
878
+ const matchedPrefix = prefixes.find((p) => rawContent.startsWith(p));
879
+ if (matchedPrefix !== void 0) {
880
+ usedPrefix = matchedPrefix;
881
+ withoutPrefix = rawContent.slice(matchedPrefix.length).trim();
882
+ } else if (!this.disableMentionPrefix && rawContent.match(/^<@!?\w+>/)) {
883
+ const mentionMatch = rawContent.match(/^<@!?(\w+)>\s*/);
662
884
  if (mentionMatch) {
663
885
  const mentionedId = mentionMatch[1];
664
886
  const botId = this.client.user?.id;
665
887
  if (botId && mentionedId === botId) {
666
888
  usedPrefix = mentionMatch[0];
667
889
  withoutPrefix = rawContent.slice(mentionMatch[0].length).trim();
668
- } else {
669
890
  }
670
891
  }
671
892
  }
672
- if (!withoutPrefix) {
673
- return null;
893
+ if (!withoutPrefix) return null;
894
+ const parts = withoutPrefix.split(/\s+/);
895
+ const part1 = parts[0];
896
+ const part2 = parts[1];
897
+ if (!part1) return null;
898
+ let commandKey = part1.toLowerCase();
899
+ let remainingParts = parts.slice(1);
900
+ if (part2 && this.registry.has(`${part1}:${part2}`)) {
901
+ commandKey = `${part1}:${part2}`.toLowerCase();
902
+ remainingParts = parts.slice(2);
674
903
  }
675
- const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
676
- if (!commandName) {
677
- return null;
678
- }
679
- const { args, options } = this.parseCommandOptions(rawArgs);
904
+ const { args, flags } = this.parseRawInput(remainingParts);
680
905
  return {
681
906
  client: this.client,
682
907
  content: rawContent,
683
908
  authorId: meta.authorId,
684
909
  channelId: meta.channelId,
685
910
  serverId: meta.serverId,
686
- args,
687
- options,
688
911
  prefix: usedPrefix,
689
- commandName: commandName.toLowerCase(),
912
+ commandName: commandKey,
690
913
  reply: meta.reply,
691
- message
914
+ message,
915
+ _rawArgs: args,
916
+ _rawFlags: flags
692
917
  };
693
918
  }
694
- /**
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
- */
705
919
  async handle(message) {
706
- if (!message.channel || !message.author || !message.content) {
707
- return false;
708
- }
709
- if (message.author.bot) {
710
- return false;
711
- }
920
+ if (!message.channel || !message.author || !message.content) return false;
921
+ if (message.author.bot) return false;
712
922
  const rawContent = message.content;
713
923
  const authorId = message.author.id;
714
924
  const channelId = message.channel.id;
715
925
  const serverId = message.server?.id;
716
- const reply = /* @__PURE__ */ __name(async (content) => {
717
- return await message.channel.send(content);
718
- }, "reply");
926
+ const reply = /* @__PURE__ */ __name(async (content) => await message.channel.send(content), "reply");
719
927
  await this.handleMessage(rawContent, message, {
720
928
  authorId,
721
929
  channelId,
@@ -724,44 +932,20 @@ var StoatxHandler = class {
724
932
  });
725
933
  return true;
726
934
  }
727
- /**
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
- */
743
935
  async handleMessage(rawContent, message, meta) {
744
936
  const ctx = await this.parseMessage(rawContent, message, meta);
745
- if (!ctx) {
746
- return;
747
- }
937
+ if (!ctx) return;
748
938
  await this.execute(ctx);
749
939
  }
750
- /**
751
- * Execute a command with the given context
752
- */
753
940
  async execute(ctx) {
754
941
  const registered = this.registry.get(ctx.commandName);
755
- if (!registered) {
756
- return false;
757
- }
942
+ if (!registered) return false;
758
943
  const { instance, metadata, methodName, classConstructor } = registered;
759
- console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
760
944
  if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
761
945
  await ctx.reply("This command is owner-only.");
762
946
  return false;
763
947
  }
764
- if (metadata.permissions) {
948
+ if (metadata.permissions.length > 0) {
765
949
  const server = ctx.message.server;
766
950
  const member = server ? await server.members.fetch(ctx.authorId) : null;
767
951
  if (!member || !member.permissions.has(metadata.permissions)) {
@@ -774,9 +958,16 @@ var StoatxHandler = class {
774
958
  return false;
775
959
  }
776
960
  }
777
- const guards = Reflect.getMetadata("stoatx:command:guards", classConstructor) || [];
778
- for (const guardClass of guards) {
779
- const guardInstance = new guardClass();
961
+ const globalGuards = this.globalGuards;
962
+ const classGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, classConstructor) || [];
963
+ const methodGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, instance, methodName) || [];
964
+ const allGuards = [
965
+ ...globalGuards,
966
+ ...classGuards,
967
+ ...methodGuards
968
+ ];
969
+ for (const guardClass of allGuards) {
970
+ const guardInstance = this.container.resolve(guardClass);
780
971
  if (typeof guardInstance.run === "function") {
781
972
  const guardResult = await guardInstance.run(ctx);
782
973
  if (!guardResult) {
@@ -789,190 +980,6 @@ var StoatxHandler = class {
789
980
  }
790
981
  }
791
982
  }
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
- }
976
983
  if (!await this.cooldownManager.check(ctx, metadata)) {
977
984
  const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
978
985
  if (typeof instance.onCooldown === "function") {
@@ -982,42 +989,152 @@ var StoatxHandler = class {
982
989
  }
983
990
  return false;
984
991
  }
992
+ const resolvedParams = await this.resolveParams(metadata.params, ctx, instance);
993
+ if (resolvedParams === null) return false;
985
994
  try {
986
995
  if (metadata.cooldown > 0) {
987
996
  await this.cooldownManager.set(ctx, metadata);
988
997
  }
989
- await instance[methodName](ctx);
998
+ await instance[methodName](...resolvedParams);
990
999
  return true;
991
1000
  } catch (error) {
992
1001
  if (typeof instance.onError === "function") {
993
1002
  await instance.onError(ctx, error);
994
1003
  } else {
995
1004
  console.error(`[Stoatx] Error in command ${metadata.name}:`, error);
1005
+ await ctx.reply("Something went wrong. Please try again later.");
996
1006
  }
997
1007
  return false;
998
1008
  }
999
1009
  }
1000
1010
  /**
1001
- * Get the command registry
1011
+ * Report a validation error to the instance via onValidationError → onError → default reply
1002
1012
  */
1013
+ async reportValidationError(instance, ctx, error) {
1014
+ if (typeof instance.onValidationError === "function") {
1015
+ await instance.onValidationError(ctx, error);
1016
+ } else if (typeof instance.onError === "function") {
1017
+ await instance.onError(ctx, error);
1018
+ } else {
1019
+ await ctx.reply(error.message);
1020
+ }
1021
+ return null;
1022
+ }
1023
+ async resolveParams(params, ctx, instance) {
1024
+ const resolved = new Array(params.length);
1025
+ let argCursor = 0;
1026
+ for (const param of params) {
1027
+ if (param.kind === "ctx") {
1028
+ resolved[param.index] = ctx;
1029
+ continue;
1030
+ }
1031
+ if (param.kind === "arg") {
1032
+ const rawValue = ctx._rawArgs[argCursor++];
1033
+ if (rawValue === void 0) {
1034
+ if (param.required) {
1035
+ const paramName = param.name ?? `arg[${param.index}]`;
1036
+ return this.reportValidationError(instance, ctx, new MissingArgumentError(paramName));
1037
+ }
1038
+ resolved[param.index] = void 0;
1039
+ continue;
1040
+ }
1041
+ const value = await this.resolveValue(rawValue, param, ctx, instance, "arg");
1042
+ if (value === null) return null;
1043
+ resolved[param.index] = value;
1044
+ continue;
1045
+ }
1046
+ if (param.kind === "option") {
1047
+ const rawValue = ctx._rawFlags[param.name];
1048
+ if (rawValue === void 0) {
1049
+ if (param.required) {
1050
+ return this.reportValidationError(instance, ctx, new MissingOptionError(param.name, this.flagPrefix));
1051
+ }
1052
+ resolved[param.index] = void 0;
1053
+ continue;
1054
+ }
1055
+ const value = await this.resolveValue(String(rawValue), param, ctx, instance, "option");
1056
+ if (value === null) return null;
1057
+ resolved[param.index] = value;
1058
+ }
1059
+ }
1060
+ return resolved;
1061
+ }
1062
+ async resolveValue(rawValue, param, ctx, instance, kind) {
1063
+ const paramName = kind === "arg" ? param.name ?? `arg[${param.index}]` : param.name;
1064
+ switch (param.resolvedType) {
1065
+ case "string":
1066
+ return String(rawValue);
1067
+ case "number": {
1068
+ const num = Number(rawValue);
1069
+ if (isNaN(num)) {
1070
+ return this.reportValidationError(instance, ctx, new InvalidTypeError(paramName, kind, "a number", rawValue));
1071
+ }
1072
+ return num;
1073
+ }
1074
+ case "boolean":
1075
+ return rawValue === "false" ? false : Boolean(rawValue);
1076
+ case "user": {
1077
+ const match = rawValue.match(/^(?:<@!?)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
1078
+ if (!match) {
1079
+ return this.reportValidationError(instance, ctx, new InvalidMentionError(paramName, kind, "user", rawValue));
1080
+ }
1081
+ const userId = match[1];
1082
+ if (param.fetch) {
1083
+ try {
1084
+ return await this.client.users.fetch(userId);
1085
+ } catch {
1086
+ return this.reportValidationError(instance, ctx, new FetchFailedError(paramName, kind, "user", userId));
1087
+ }
1088
+ }
1089
+ return this.client.users.cache.get(userId) ?? userId;
1090
+ }
1091
+ case "channel": {
1092
+ const match = rawValue.match(/^(?:<#)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
1093
+ if (!match) {
1094
+ return this.reportValidationError(instance, ctx, new InvalidMentionError(paramName, kind, "channel", rawValue));
1095
+ }
1096
+ const channelId = match[1];
1097
+ if (param.fetch) {
1098
+ try {
1099
+ return await this.client.channels.fetch(channelId);
1100
+ } catch {
1101
+ return this.reportValidationError(instance, ctx, new FetchFailedError(paramName, kind, "channel", channelId));
1102
+ }
1103
+ }
1104
+ return this.client.channels.cache.get(channelId) ?? channelId;
1105
+ }
1106
+ case "role": {
1107
+ const match = rawValue.match(/^(?:<@&)?([0-7][0-9A-HJKMNP-TV-Z]{25})>?$/i);
1108
+ if (!match) {
1109
+ return this.reportValidationError(instance, ctx, new InvalidMentionError(paramName, kind, "role", rawValue));
1110
+ }
1111
+ const roleId = match[1];
1112
+ if (param.fetch) {
1113
+ const server = ctx.message.server;
1114
+ if (!server) {
1115
+ return this.reportValidationError(instance, ctx, new NoServerContextError(paramName, kind));
1116
+ }
1117
+ try {
1118
+ return await server.roles.fetch(roleId);
1119
+ } catch {
1120
+ return this.reportValidationError(instance, ctx, new FetchFailedError(paramName, kind, "role", roleId));
1121
+ }
1122
+ }
1123
+ return ctx.message.server?.roles.cache.get(roleId) ?? roleId;
1124
+ }
1125
+ default:
1126
+ return rawValue;
1127
+ }
1128
+ }
1003
1129
  getRegistry() {
1004
1130
  return this.registry;
1005
1131
  }
1006
- /**
1007
- * Get a command by name or alias
1008
- */
1009
1132
  getCommand(name) {
1010
1133
  return this.registry.get(name);
1011
1134
  }
1012
- /**
1013
- * Get all commands
1014
- */
1015
1135
  getCommands() {
1016
1136
  return this.registry.getAll();
1017
1137
  }
1018
- /**
1019
- * Reload all commands
1020
- */
1021
1138
  async reload() {
1022
1139
  this.registry.clear();
1023
1140
  if (this.cooldownManager.clear) {
@@ -1029,40 +1146,28 @@ var StoatxHandler = class {
1029
1146
  }
1030
1147
  await this.registry.autoDiscover(this.discoveryOptions);
1031
1148
  }
1032
- /**
1033
- * Check if a user is an owner
1034
- */
1035
1149
  isOwner(userId) {
1036
1150
  return this.owners.has(userId);
1037
1151
  }
1038
- /**
1039
- * Add an owner
1040
- */
1041
1152
  addOwner(userId) {
1042
1153
  this.owners.add(userId);
1043
1154
  }
1044
- /**
1045
- * Remove an owner
1046
- */
1047
1155
  removeOwner(userId) {
1048
1156
  this.owners.delete(userId);
1049
1157
  }
1050
- /**
1051
- * Resolve the prefix for a context
1052
- */
1053
1158
  async resolvePrefix(serverId) {
1054
- if (typeof this.prefixResolver === "function") {
1055
- return this.prefixResolver({
1056
- serverId
1057
- });
1058
- }
1059
- return this.prefixResolver;
1159
+ const result = typeof this.prefixResolver === "function" ? await this.prefixResolver({
1160
+ serverId
1161
+ }) : this.prefixResolver;
1162
+ return Array.isArray(result) ? result : [
1163
+ result
1164
+ ];
1060
1165
  }
1061
1166
  };
1062
1167
 
1063
1168
  // src/client.ts
1064
- var import_client = require("@stoatx/client");
1065
- var Client = class extends import_client.Client {
1169
+ var import_client2 = require("@stoatx/client");
1170
+ var Client = class extends import_client2.Client {
1066
1171
  static {
1067
1172
  __name(this, "Client");
1068
1173
  }
@@ -1087,16 +1192,29 @@ var Client = class extends import_client.Client {
1087
1192
  __reExport(index_exports, require("@stoatx/client"), module.exports);
1088
1193
  // Annotate the CommonJS export names for ESM import in node:
1089
1194
  0 && (module.exports = {
1195
+ Arg,
1090
1196
  Client,
1197
+ CommandGroup,
1091
1198
  CommandRegistry,
1092
1199
  CommandValidationError,
1093
1200
  DefaultCooldownManager,
1201
+ FetchFailedError,
1094
1202
  Guard,
1203
+ Injectable,
1204
+ InvalidMentionError,
1205
+ InvalidTypeError,
1095
1206
  METADATA_KEYS,
1207
+ MissingArgumentError,
1208
+ MissingOptionError,
1209
+ NoServerContextError,
1096
1210
  On,
1097
1211
  Once,
1212
+ Option,
1213
+ PARAM_TYPE_MAP,
1098
1214
  SimpleCommand,
1099
1215
  Stoat,
1216
+ StoatxError,
1217
+ SubCommand,
1100
1218
  buildSimpleCommandMetadata,
1101
1219
  getEventsMetadata,
1102
1220
  getGuards,