stoatx 0.7.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
1
4
  // src/decorators/Stoat.ts
2
5
  import "reflect-metadata";
3
6
 
@@ -11,6 +14,9 @@ var METADATA_KEYS = {
11
14
 
12
15
  // src/decorators/store.ts
13
16
  var DecoratorStore = class _DecoratorStore {
17
+ static {
18
+ __name(this, "DecoratorStore");
19
+ }
14
20
  static instance;
15
21
  /** Stoat classes with their SimpleCommand methods */
16
22
  stoatClasses = /* @__PURE__ */ new Map();
@@ -27,8 +33,8 @@ var DecoratorStore = class _DecoratorStore {
27
33
  return _DecoratorStore.instance;
28
34
  }
29
35
  /**
30
- * Register a @Stoat decorated class
31
- */
36
+ * Register a @Stoat decorated class
37
+ */
32
38
  registerStoatClass(classConstructor) {
33
39
  if (!this.stoatClasses.has(classConstructor)) {
34
40
  const instance = new classConstructor();
@@ -36,40 +42,40 @@ var DecoratorStore = class _DecoratorStore {
36
42
  }
37
43
  }
38
44
  /**
39
- * Get all registered Stoat classes with their instances
40
- */
45
+ * Get all registered Stoat classes with their instances
46
+ */
41
47
  getStoatClasses() {
42
48
  return this.stoatClasses;
43
49
  }
44
50
  /**
45
- * Add a registered command
46
- */
51
+ * Add a registered command
52
+ */
47
53
  addCommand(command) {
48
54
  this.commands.push(command);
49
55
  }
50
56
  /**
51
- * Get all registered commands
52
- */
57
+ * Get all registered commands
58
+ */
53
59
  getCommands() {
54
60
  return this.commands;
55
61
  }
56
62
  /**
57
- * Clear all registered classes (useful for testing)
58
- */
63
+ * Clear all registered classes (useful for testing)
64
+ */
59
65
  clear() {
60
66
  this.stoatClasses.clear();
61
67
  this.commands = [];
62
68
  this.initialized = false;
63
69
  }
64
70
  /**
65
- * Mark as initialized
66
- */
71
+ * Mark as initialized
72
+ */
67
73
  markInitialized() {
68
74
  this.initialized = true;
69
75
  }
70
76
  /**
71
- * Check if initialized
72
- */
77
+ * Check if initialized
78
+ */
73
79
  isInitialized() {
74
80
  return this.initialized;
75
81
  }
@@ -83,9 +89,11 @@ function Stoat() {
83
89
  decoratorStore.registerStoatClass(target);
84
90
  };
85
91
  }
92
+ __name(Stoat, "Stoat");
86
93
  function isStoatClass(target) {
87
94
  return Reflect.getMetadata(METADATA_KEYS.IS_STOAT_CLASS, target) === true;
88
95
  }
96
+ __name(isStoatClass, "isStoatClass");
89
97
 
90
98
  // src/decorators/SimpleCommand.ts
91
99
  import "reflect-metadata";
@@ -101,9 +109,11 @@ function SimpleCommand(options = {}) {
101
109
  return descriptor;
102
110
  };
103
111
  }
112
+ __name(SimpleCommand, "SimpleCommand");
104
113
  function getSimpleCommands(target) {
105
114
  return Reflect.getMetadata(METADATA_KEYS.SIMPLE_COMMANDS, target) || [];
106
115
  }
116
+ __name(getSimpleCommands, "getSimpleCommands");
107
117
 
108
118
  // src/decorators/Guard.ts
109
119
  import "reflect-metadata";
@@ -114,9 +124,11 @@ function Guard(guardClass) {
114
124
  Reflect.defineMetadata(METADATA_KEYS.GUARDS, existingGuards, target);
115
125
  };
116
126
  }
127
+ __name(Guard, "Guard");
117
128
  function getGuards(target) {
118
129
  return Reflect.getMetadata(METADATA_KEYS.GUARDS, target) || [];
119
130
  }
131
+ __name(getGuards, "getGuards");
120
132
 
121
133
  // src/decorators/Events.ts
122
134
  import "reflect-metadata";
@@ -133,15 +145,19 @@ function createEventDecorator(event, type) {
133
145
  return descriptor;
134
146
  };
135
147
  }
148
+ __name(createEventDecorator, "createEventDecorator");
136
149
  function On(event) {
137
150
  return createEventDecorator(event, "on");
138
151
  }
152
+ __name(On, "On");
139
153
  function Once(event) {
140
154
  return createEventDecorator(event, "once");
141
155
  }
156
+ __name(Once, "Once");
142
157
  function getEventsMetadata(target) {
143
158
  return Reflect.getMetadata(METADATA_KEYS.EVENTS, target) || [];
144
159
  }
160
+ __name(getEventsMetadata, "getEventsMetadata");
145
161
 
146
162
  // src/decorators/utils.ts
147
163
  function buildSimpleCommandMetadata(options, methodName, category) {
@@ -152,11 +168,16 @@ function buildSimpleCommandMetadata(options, methodName, category) {
152
168
  permissions: options.permissions ?? [],
153
169
  category: options.category ?? category ?? "uncategorized",
154
170
  cooldown: options.cooldown ?? 0,
155
- ...options.cooldownStorage !== void 0 ? { cooldownStorage: options.cooldownStorage } : {},
171
+ ...options.cooldownStorage !== void 0 ? {
172
+ cooldownStorage: options.cooldownStorage
173
+ } : {},
156
174
  nsfw: options.nsfw ?? false,
157
- ownerOnly: options.ownerOnly ?? false
175
+ ownerOnly: options.ownerOnly ?? false,
176
+ options: options.options ?? [],
177
+ args: options.args ?? []
158
178
  };
159
179
  }
180
+ __name(buildSimpleCommandMetadata, "buildSimpleCommandMetadata");
160
181
 
161
182
  // src/registry.ts
162
183
  import * as path from "path";
@@ -164,6 +185,9 @@ import * as fs from "fs/promises";
164
185
  import { pathToFileURL } from "url";
165
186
  import { glob } from "tinyglobby";
166
187
  var CommandRegistry = class _CommandRegistry {
188
+ static {
189
+ __name(this, "CommandRegistry");
190
+ }
167
191
  static DEFAULT_AUTO_DISCOVERY_IGNORES = [
168
192
  "**/node_modules/**",
169
193
  "**/.git/**",
@@ -176,23 +200,31 @@ var CommandRegistry = class _CommandRegistry {
176
200
  registeredEvents = [];
177
201
  extensions;
178
202
  processedStoatClasses = /* @__PURE__ */ new Set();
179
- constructor(extensions = [".js", ".mjs", ".cjs"]) {
203
+ constructor(extensions = [
204
+ ".js",
205
+ ".mjs",
206
+ ".cjs"
207
+ ]) {
180
208
  this.extensions = extensions;
181
209
  }
182
210
  /**
183
- * Get the number of registered commands
184
- */
211
+ * Get the number of registered commands
212
+ */
185
213
  get size() {
186
214
  return this.commands.size;
187
215
  }
188
216
  /**
189
- * Load commands from a directory using glob pattern matching
190
- */
217
+ * Load commands from a directory using glob pattern matching
218
+ */
191
219
  async loadFromDirectory(directory) {
192
220
  const patterns = this.extensions.map((ext) => path.join(directory, "**", `*${ext}`).replace(/\\/g, "/"));
193
221
  for (const pattern of patterns) {
194
222
  const files = await glob(pattern, {
195
- ignore: ["**/*.d.ts", "**/*.test.ts", "**/*.spec.ts"],
223
+ ignore: [
224
+ "**/*.d.ts",
225
+ "**/*.test.ts",
226
+ "**/*.spec.ts"
227
+ ],
196
228
  absolute: true
197
229
  });
198
230
  for (const file of files) {
@@ -202,19 +234,24 @@ var CommandRegistry = class _CommandRegistry {
202
234
  console.log(`[Stoatx] Loaded ${this.commands.size} command(s) and ${this.registeredEvents.length} event(s)`);
203
235
  }
204
236
  /**
205
- * Auto-discover command files across one or more roots.
206
- */
237
+ * Auto-discover command files across one or more roots.
238
+ */
207
239
  async autoDiscover(options = {}) {
208
- const roots = options.roots?.length ? options.roots : [process.cwd()];
240
+ const roots = options.roots?.length ? options.roots : [
241
+ process.cwd()
242
+ ];
209
243
  const includePatterns = options.include?.length ? options.include : this.getDefaultAutoDiscoveryPatterns();
210
- const patterns = roots.flatMap(
211
- (root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/"))
212
- );
244
+ const patterns = roots.flatMap((root) => includePatterns.map((pattern) => path.join(root, pattern).replace(/\\/g, "/")));
213
245
  const files = await glob(patterns, {
214
- ignore: [..._CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES, ...options.ignore ?? []],
246
+ ignore: [
247
+ ..._CommandRegistry.DEFAULT_AUTO_DISCOVERY_IGNORES,
248
+ ...options.ignore ?? []
249
+ ],
215
250
  absolute: true
216
251
  });
217
- const uniqueFiles = [...new Set(files)];
252
+ const uniqueFiles = [
253
+ ...new Set(files)
254
+ ];
218
255
  let candidateFiles = 0;
219
256
  for (const file of uniqueFiles) {
220
257
  if (!await this.isLikelyCommandModule(file)) {
@@ -241,8 +278,8 @@ var CommandRegistry = class _CommandRegistry {
241
278
  }
242
279
  }
243
280
  /**
244
- * Register a command instance
245
- */
281
+ * Register a command instance
282
+ */
246
283
  register(instance, metadata, classConstructor, methodName) {
247
284
  const name = metadata.name.toLowerCase();
248
285
  if (this.commands.has(name)) {
@@ -250,7 +287,12 @@ var CommandRegistry = class _CommandRegistry {
250
287
  return;
251
288
  }
252
289
  this.validateGuards(classConstructor, metadata.name);
253
- this.commands.set(name, { instance, metadata, methodName, classConstructor });
290
+ this.commands.set(name, {
291
+ instance,
292
+ metadata,
293
+ methodName,
294
+ classConstructor
295
+ });
254
296
  for (const alias of metadata.aliases) {
255
297
  const aliasLower = alias.toLowerCase();
256
298
  if (this.aliases.has(aliasLower) || this.commands.has(aliasLower)) {
@@ -261,41 +303,41 @@ var CommandRegistry = class _CommandRegistry {
261
303
  }
262
304
  }
263
305
  /**
264
- * Get a command by name or alias
265
- */
306
+ * Get a command by name or alias
307
+ */
266
308
  get(name) {
267
309
  const lowerName = name.toLowerCase();
268
310
  const resolvedName = this.aliases.get(lowerName) ?? lowerName;
269
311
  return this.commands.get(resolvedName);
270
312
  }
271
313
  /**
272
- * Check if a command exists
273
- */
314
+ * Check if a command exists
315
+ */
274
316
  has(name) {
275
317
  const lowerName = name.toLowerCase();
276
318
  return this.commands.has(lowerName) || this.aliases.has(lowerName);
277
319
  }
278
320
  /**
279
- * Get all registered commands
280
- */
321
+ * Get all registered commands
322
+ */
281
323
  getAll() {
282
324
  return Array.from(this.commands.values());
283
325
  }
284
326
  /**
285
- * Get all command metadata
286
- */
327
+ * Get all command metadata
328
+ */
287
329
  getAllMetadata() {
288
330
  return this.getAll().map((c) => c.metadata);
289
331
  }
290
332
  /**
291
- * Get all registered events
292
- */
333
+ * Get all registered events
334
+ */
293
335
  getEvents() {
294
336
  return this.registeredEvents;
295
337
  }
296
338
  /**
297
- * Get commands grouped by category
298
- */
339
+ * Get commands grouped by category
340
+ */
299
341
  getByCategory() {
300
342
  const categories = /* @__PURE__ */ new Map();
301
343
  for (const cmd of this.commands.values()) {
@@ -307,8 +349,8 @@ var CommandRegistry = class _CommandRegistry {
307
349
  return categories;
308
350
  }
309
351
  /**
310
- * Clear all commands
311
- */
352
+ * Clear all commands
353
+ */
312
354
  clear() {
313
355
  this.commands.clear();
314
356
  this.aliases.clear();
@@ -316,51 +358,47 @@ var CommandRegistry = class _CommandRegistry {
316
358
  this.processedStoatClasses.clear();
317
359
  }
318
360
  /**
319
- * Iterate over commands
320
- */
361
+ * Iterate over commands
362
+ */
321
363
  [Symbol.iterator]() {
322
364
  return this.commands.entries();
323
365
  }
324
366
  /**
325
- * Iterate over command values
326
- */
367
+ * Iterate over command values
368
+ */
327
369
  values() {
328
370
  return this.commands.values();
329
371
  }
330
372
  /**
331
- * Iterate over command names
332
- */
373
+ * Iterate over command names
374
+ */
333
375
  keys() {
334
376
  return this.commands.keys();
335
377
  }
336
378
  /**
337
- * Validate that all guards on a command implement the required methods
338
- * @param commandClass
339
- * @param commandName
340
- * @private
341
- */
379
+ * Validate that all guards on a command implement the required methods
380
+ * @param commandClass
381
+ * @param commandName
382
+ * @private
383
+ */
342
384
  validateGuards(commandClass, commandName) {
343
385
  const guards = Reflect.getMetadata("stoatx:command:guards", commandClass) || [];
344
386
  for (const GuardClass of guards) {
345
387
  const guardInstance = new GuardClass();
346
388
  if (typeof guardInstance.run !== "function") {
347
- console.error(
348
- `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`
349
- );
389
+ console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a run() method.`);
350
390
  process.exit(1);
351
391
  }
352
392
  if (typeof guardInstance.guardFail !== "function") {
353
- console.error(
354
- `[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`
355
- );
393
+ console.error(`[Stoatx] FATAL: Guard "${GuardClass.name}" on command "${commandName}" does not have a guardFail() method.`);
356
394
  console.error(`[Stoatx] All guards must implement guardFail() to handle failed checks.`);
357
395
  process.exit(1);
358
396
  }
359
397
  }
360
398
  }
361
399
  /**
362
- * Load commands from a single file
363
- */
400
+ * Load commands from a single file
401
+ */
364
402
  async loadFile(filePath, baseDir) {
365
403
  try {
366
404
  const knownStoatClasses = new Set(decoratorStore.getStoatClasses().keys());
@@ -382,9 +420,7 @@ var CommandRegistry = class _CommandRegistry {
382
420
  const events = getEventsMetadata(stoatClass);
383
421
  const category = this.getCategoryFromPath(filePath, baseDir);
384
422
  if (simpleCommands.length === 0 && events.length === 0) {
385
- console.warn(
386
- `[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`
387
- );
423
+ console.warn(`[Stoatx] Class ${stoatClass.name} is decorated with @Stoat but has no @SimpleCommand, @On or @Once methods. Skipping...`);
388
424
  this.processedStoatClasses.add(stoatClass);
389
425
  return;
390
426
  }
@@ -413,8 +449,8 @@ var CommandRegistry = class _CommandRegistry {
413
449
  this.processedStoatClasses.add(stoatClass);
414
450
  }
415
451
  /**
416
- * Derive category from file path relative to base directory
417
- */
452
+ * Derive category from file path relative to base directory
453
+ */
418
454
  getCategoryFromPath(filePath, baseDir) {
419
455
  const relative2 = path.relative(baseDir, filePath);
420
456
  const parts = relative2.split(path.sep);
@@ -427,7 +463,24 @@ var CommandRegistry = class _CommandRegistry {
427
463
 
428
464
  // src/handler.ts
429
465
  import "reflect-metadata";
466
+
467
+ // src/error.ts
468
+ var CommandValidationError = class extends Error {
469
+ static {
470
+ __name(this, "CommandValidationError");
471
+ }
472
+ optionName;
473
+ constructor(optionName, message) {
474
+ super(message), this.optionName = optionName;
475
+ this.name = "CommandValidationError";
476
+ }
477
+ };
478
+
479
+ // src/handler.ts
430
480
  var DefaultCooldownManager = class {
481
+ static {
482
+ __name(this, "DefaultCooldownManager");
483
+ }
431
484
  cooldowns = /* @__PURE__ */ new Map();
432
485
  check(ctx, metadata) {
433
486
  if (metadata.cooldown <= 0) return true;
@@ -460,6 +513,9 @@ var DefaultCooldownManager = class {
460
513
  }
461
514
  };
462
515
  var StoatxHandler = class {
516
+ static {
517
+ __name(this, "StoatxHandler");
518
+ }
463
519
  commandsDir;
464
520
  discoveryOptions;
465
521
  prefixResolver;
@@ -468,6 +524,7 @@ var StoatxHandler = class {
468
524
  cooldownManager;
469
525
  disableMentionPrefix;
470
526
  client;
527
+ flagPrefix;
471
528
  constructor(options) {
472
529
  this.client = options.client;
473
530
  this.commandsDir = options.commandsDir;
@@ -477,10 +534,11 @@ var StoatxHandler = class {
477
534
  this.registry = new CommandRegistry(options.extensions);
478
535
  this.disableMentionPrefix = options.disableMentionPrefix ?? false;
479
536
  this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
537
+ this.flagPrefix = options.flagPrefix || "-";
480
538
  }
481
539
  /**
482
- * Initialize the handler - load all commands
483
- */
540
+ * Initialize the handler - load all commands
541
+ */
484
542
  async init() {
485
543
  if (this.commandsDir) {
486
544
  await this.registry.loadFromDirectory(this.commandsDir);
@@ -490,21 +548,18 @@ var StoatxHandler = class {
490
548
  this.attachEvents();
491
549
  }
492
550
  /**
493
- * Attach registered events to the client
494
- */
551
+ * Attach registered events to the client
552
+ */
495
553
  attachEvents() {
496
554
  const events = this.registry.getEvents();
497
555
  for (const eventDef of events) {
498
- const handler = async (...args) => {
556
+ const handler = /* @__PURE__ */ __name(async (...args) => {
499
557
  try {
500
558
  await eventDef.instance[eventDef.methodName](...args, this.client);
501
559
  } catch (error) {
502
- console.error(
503
- `[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
504
- error
505
- );
560
+ console.error(`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`, error);
506
561
  }
507
- };
562
+ }, "handler");
508
563
  const eventName = eventDef.event;
509
564
  if (eventDef.type === "once") {
510
565
  this.client.once(eventName, handler);
@@ -514,8 +569,38 @@ var StoatxHandler = class {
514
569
  }
515
570
  }
516
571
  /**
517
- * Parse a raw message into command context
518
- */
572
+ * Parses raw string arguments into positional args and key-value options
573
+ */
574
+ parseCommandOptions(rawArgs) {
575
+ const args = [];
576
+ const options = {};
577
+ for (let i = 0; i < rawArgs.length; i++) {
578
+ const arg = rawArgs[i];
579
+ if (arg === void 0) continue;
580
+ if (arg.startsWith(this.flagPrefix)) {
581
+ let key = arg;
582
+ while (key.startsWith(this.flagPrefix)) {
583
+ key = key.slice(this.flagPrefix.length);
584
+ }
585
+ const nextArg = rawArgs[i + 1];
586
+ if (nextArg !== void 0 && !nextArg.startsWith(this.flagPrefix)) {
587
+ options[key] = nextArg;
588
+ i++;
589
+ } else {
590
+ options[key] = true;
591
+ }
592
+ } else {
593
+ args.push(arg);
594
+ }
595
+ }
596
+ return {
597
+ args,
598
+ options
599
+ };
600
+ }
601
+ /**
602
+ * Parse a raw message into command context
603
+ */
519
604
  async parseMessage(rawContent, message, meta) {
520
605
  const prefix = await this.resolvePrefix(meta.serverId);
521
606
  let usedPrefix = prefix;
@@ -538,10 +623,11 @@ var StoatxHandler = class {
538
623
  if (!withoutPrefix) {
539
624
  return null;
540
625
  }
541
- const [commandName, ...args] = withoutPrefix.split(/\s+/);
626
+ const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
542
627
  if (!commandName) {
543
628
  return null;
544
629
  }
630
+ const { args, options } = this.parseCommandOptions(rawArgs);
545
631
  return {
546
632
  client: this.client,
547
633
  content: rawContent,
@@ -549,6 +635,7 @@ var StoatxHandler = class {
549
635
  channelId: meta.channelId,
550
636
  serverId: meta.serverId,
551
637
  args,
638
+ options,
552
639
  prefix: usedPrefix,
553
640
  commandName: commandName.toLowerCase(),
554
641
  reply: meta.reply,
@@ -556,16 +643,16 @@ var StoatxHandler = class {
556
643
  };
557
644
  }
558
645
  /**
559
- * Handle a message object using the configured message adapter
560
- *
561
- * @example
562
- * ```ts
563
- * // With message adapter configured
564
- * client.on('messageCreate', (message) => {
565
- * handler.handle(message);
566
- * });
567
- * ```
568
- */
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
+ */
569
656
  async handle(message) {
570
657
  if (!message.channel || !message.author || !message.content) {
571
658
  return false;
@@ -577,9 +664,9 @@ var StoatxHandler = class {
577
664
  const authorId = message.author.id;
578
665
  const channelId = message.channel.id;
579
666
  const serverId = message.server?.id;
580
- const reply = async (content) => {
667
+ const reply = /* @__PURE__ */ __name(async (content) => {
581
668
  return await message.channel.send(content);
582
- };
669
+ }, "reply");
583
670
  await this.handleMessage(rawContent, message, {
584
671
  authorId,
585
672
  channelId,
@@ -589,21 +676,21 @@ var StoatxHandler = class {
589
676
  return true;
590
677
  }
591
678
  /**
592
- * Handle a raw message string with metadata
593
- *
594
- * @example
595
- * ```ts
596
- * // Manual usage without message adapter
597
- * client.on('messageCreate', (message) => {
598
- * handler.handleMessage(message.content, message, {
599
- * authorId: message.author.id,
600
- * channelId: message.channel.id,
601
- * serverId: message.server?.id,
602
- * reply: (content) => message.channel.sendMessage(content),
603
- * });
604
- * });
605
- * ```
606
- */
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
+ */
607
694
  async handleMessage(rawContent, message, meta) {
608
695
  const ctx = await this.parseMessage(rawContent, message, meta);
609
696
  if (!ctx) {
@@ -612,14 +699,15 @@ var StoatxHandler = class {
612
699
  await this.execute(ctx);
613
700
  }
614
701
  /**
615
- * Execute a command with the given context
616
- */
702
+ * Execute a command with the given context
703
+ */
617
704
  async execute(ctx) {
618
705
  const registered = this.registry.get(ctx.commandName);
619
706
  if (!registered) {
620
707
  return false;
621
708
  }
622
709
  const { instance, metadata, methodName, classConstructor } = registered;
710
+ console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
623
711
  if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
624
712
  await ctx.reply("This command is owner-only.");
625
713
  return false;
@@ -652,6 +740,190 @@ var StoatxHandler = class {
652
740
  }
653
741
  }
654
742
  }
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
+ }
655
927
  if (!await this.cooldownManager.check(ctx, metadata)) {
656
928
  const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
657
929
  if (typeof instance.onCooldown === "function") {
@@ -677,26 +949,26 @@ var StoatxHandler = class {
677
949
  }
678
950
  }
679
951
  /**
680
- * Get the command registry
681
- */
952
+ * Get the command registry
953
+ */
682
954
  getRegistry() {
683
955
  return this.registry;
684
956
  }
685
957
  /**
686
- * Get a command by name or alias
687
- */
958
+ * Get a command by name or alias
959
+ */
688
960
  getCommand(name) {
689
961
  return this.registry.get(name);
690
962
  }
691
963
  /**
692
- * Get all commands
693
- */
964
+ * Get all commands
965
+ */
694
966
  getCommands() {
695
967
  return this.registry.getAll();
696
968
  }
697
969
  /**
698
- * Reload all commands
699
- */
970
+ * Reload all commands
971
+ */
700
972
  async reload() {
701
973
  this.registry.clear();
702
974
  if (this.cooldownManager.clear) {
@@ -709,29 +981,31 @@ var StoatxHandler = class {
709
981
  await this.registry.autoDiscover(this.discoveryOptions);
710
982
  }
711
983
  /**
712
- * Check if a user is an owner
713
- */
984
+ * Check if a user is an owner
985
+ */
714
986
  isOwner(userId) {
715
987
  return this.owners.has(userId);
716
988
  }
717
989
  /**
718
- * Add an owner
719
- */
990
+ * Add an owner
991
+ */
720
992
  addOwner(userId) {
721
993
  this.owners.add(userId);
722
994
  }
723
995
  /**
724
- * Remove an owner
725
- */
996
+ * Remove an owner
997
+ */
726
998
  removeOwner(userId) {
727
999
  this.owners.delete(userId);
728
1000
  }
729
1001
  /**
730
- * Resolve the prefix for a context
731
- */
1002
+ * Resolve the prefix for a context
1003
+ */
732
1004
  async resolvePrefix(serverId) {
733
1005
  if (typeof this.prefixResolver === "function") {
734
- return this.prefixResolver({ serverId });
1006
+ return this.prefixResolver({
1007
+ serverId
1008
+ });
735
1009
  }
736
1010
  return this.prefixResolver;
737
1011
  }
@@ -740,16 +1014,23 @@ var StoatxHandler = class {
740
1014
  // src/client.ts
741
1015
  import { Client as StoatClient } from "@stoatx/client";
742
1016
  var Client = class extends StoatClient {
1017
+ static {
1018
+ __name(this, "Client");
1019
+ }
743
1020
  handler;
744
1021
  constructor(options) {
745
- super();
746
- this.handler = new StoatxHandler({ ...options, client: this });
747
- this.on("messageCreate", async (message) => {
748
- await this.handler.handle(message);
1022
+ super(options);
1023
+ this.handler = new StoatxHandler({
1024
+ ...options,
1025
+ client: this
749
1026
  });
750
1027
  }
751
- async initCommands() {
1028
+ async login(token) {
752
1029
  await this.handler.init();
1030
+ return super.login(token);
1031
+ }
1032
+ async executeCommand(message) {
1033
+ await this.handler.handle(message);
753
1034
  }
754
1035
  };
755
1036
 
@@ -758,6 +1039,7 @@ export * from "@stoatx/client";
758
1039
  export {
759
1040
  Client,
760
1041
  CommandRegistry,
1042
+ CommandValidationError,
761
1043
  DefaultCooldownManager,
762
1044
  Guard,
763
1045
  METADATA_KEYS,