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