stoatx 0.7.6 → 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,8 +463,24 @@ var CommandRegistry = class _CommandRegistry {
427
463
 
428
464
  // src/handler.ts
429
465
  import "reflect-metadata";
430
- import { Client as StoatClient } from "@stoatx/client";
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
431
480
  var DefaultCooldownManager = class {
481
+ static {
482
+ __name(this, "DefaultCooldownManager");
483
+ }
432
484
  cooldowns = /* @__PURE__ */ new Map();
433
485
  check(ctx, metadata) {
434
486
  if (metadata.cooldown <= 0) return true;
@@ -460,23 +512,10 @@ var DefaultCooldownManager = class {
460
512
  this.cooldowns.clear();
461
513
  }
462
514
  };
463
- var Client = class extends StoatClient {
464
- handler;
465
- constructor(options) {
466
- super();
467
- this.handler = new StoatxHandler({ ...options, client: this });
468
- this.on("messageCreate", async (message) => {
469
- await this.handler.handle(message);
470
- });
471
- }
472
- /**
473
- * Initialize the StoatxHandler commands
474
- */
475
- async initCommands() {
476
- await this.handler.init();
477
- }
478
- };
479
515
  var StoatxHandler = class {
516
+ static {
517
+ __name(this, "StoatxHandler");
518
+ }
480
519
  commandsDir;
481
520
  discoveryOptions;
482
521
  prefixResolver;
@@ -485,6 +524,7 @@ var StoatxHandler = class {
485
524
  cooldownManager;
486
525
  disableMentionPrefix;
487
526
  client;
527
+ flagPrefix;
488
528
  constructor(options) {
489
529
  this.client = options.client;
490
530
  this.commandsDir = options.commandsDir;
@@ -494,10 +534,11 @@ var StoatxHandler = class {
494
534
  this.registry = new CommandRegistry(options.extensions);
495
535
  this.disableMentionPrefix = options.disableMentionPrefix ?? false;
496
536
  this.cooldownManager = options.cooldownManager ?? new DefaultCooldownManager();
537
+ this.flagPrefix = options.flagPrefix || "-";
497
538
  }
498
539
  /**
499
- * Initialize the handler - load all commands
500
- */
540
+ * Initialize the handler - load all commands
541
+ */
501
542
  async init() {
502
543
  if (this.commandsDir) {
503
544
  await this.registry.loadFromDirectory(this.commandsDir);
@@ -507,21 +548,18 @@ var StoatxHandler = class {
507
548
  this.attachEvents();
508
549
  }
509
550
  /**
510
- * Attach registered events to the client
511
- */
551
+ * Attach registered events to the client
552
+ */
512
553
  attachEvents() {
513
554
  const events = this.registry.getEvents();
514
555
  for (const eventDef of events) {
515
- const handler = async (...args) => {
556
+ const handler = /* @__PURE__ */ __name(async (...args) => {
516
557
  try {
517
558
  await eventDef.instance[eventDef.methodName](...args, this.client);
518
559
  } catch (error) {
519
- console.error(
520
- `[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`,
521
- error
522
- );
560
+ console.error(`[Stoatx] Event Handler Error in @${eventDef.type === "on" ? "On" : "Once"}('${eventDef.event}'):`, error);
523
561
  }
524
- };
562
+ }, "handler");
525
563
  const eventName = eventDef.event;
526
564
  if (eventDef.type === "once") {
527
565
  this.client.once(eventName, handler);
@@ -531,8 +569,38 @@ var StoatxHandler = class {
531
569
  }
532
570
  }
533
571
  /**
534
- * Parse a raw message into command context
535
- */
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
+ */
536
604
  async parseMessage(rawContent, message, meta) {
537
605
  const prefix = await this.resolvePrefix(meta.serverId);
538
606
  let usedPrefix = prefix;
@@ -555,10 +623,11 @@ var StoatxHandler = class {
555
623
  if (!withoutPrefix) {
556
624
  return null;
557
625
  }
558
- const [commandName, ...args] = withoutPrefix.split(/\s+/);
626
+ const [commandName, ...rawArgs] = withoutPrefix.split(/\s+/);
559
627
  if (!commandName) {
560
628
  return null;
561
629
  }
630
+ const { args, options } = this.parseCommandOptions(rawArgs);
562
631
  return {
563
632
  client: this.client,
564
633
  content: rawContent,
@@ -566,6 +635,7 @@ var StoatxHandler = class {
566
635
  channelId: meta.channelId,
567
636
  serverId: meta.serverId,
568
637
  args,
638
+ options,
569
639
  prefix: usedPrefix,
570
640
  commandName: commandName.toLowerCase(),
571
641
  reply: meta.reply,
@@ -573,16 +643,16 @@ var StoatxHandler = class {
573
643
  };
574
644
  }
575
645
  /**
576
- * Handle a message object using the configured message adapter
577
- *
578
- * @example
579
- * ```ts
580
- * // With message adapter configured
581
- * client.on('messageCreate', (message) => {
582
- * handler.handle(message);
583
- * });
584
- * ```
585
- */
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
+ */
586
656
  async handle(message) {
587
657
  if (!message.channel || !message.author || !message.content) {
588
658
  return false;
@@ -594,9 +664,9 @@ var StoatxHandler = class {
594
664
  const authorId = message.author.id;
595
665
  const channelId = message.channel.id;
596
666
  const serverId = message.server?.id;
597
- const reply = async (content) => {
667
+ const reply = /* @__PURE__ */ __name(async (content) => {
598
668
  return await message.channel.send(content);
599
- };
669
+ }, "reply");
600
670
  await this.handleMessage(rawContent, message, {
601
671
  authorId,
602
672
  channelId,
@@ -606,21 +676,21 @@ var StoatxHandler = class {
606
676
  return true;
607
677
  }
608
678
  /**
609
- * Handle a raw message string with metadata
610
- *
611
- * @example
612
- * ```ts
613
- * // Manual usage without message adapter
614
- * client.on('messageCreate', (message) => {
615
- * handler.handleMessage(message.content, message, {
616
- * authorId: message.author.id,
617
- * channelId: message.channel.id,
618
- * serverId: message.server?.id,
619
- * reply: (content) => message.channel.sendMessage(content),
620
- * });
621
- * });
622
- * ```
623
- */
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
+ */
624
694
  async handleMessage(rawContent, message, meta) {
625
695
  const ctx = await this.parseMessage(rawContent, message, meta);
626
696
  if (!ctx) {
@@ -629,14 +699,15 @@ var StoatxHandler = class {
629
699
  await this.execute(ctx);
630
700
  }
631
701
  /**
632
- * Execute a command with the given context
633
- */
702
+ * Execute a command with the given context
703
+ */
634
704
  async execute(ctx) {
635
705
  const registered = this.registry.get(ctx.commandName);
636
706
  if (!registered) {
637
707
  return false;
638
708
  }
639
709
  const { instance, metadata, methodName, classConstructor } = registered;
710
+ console.log(`[Debug] Metadata options for ${ctx.commandName}:`, metadata.options);
640
711
  if (metadata.ownerOnly && !this.owners.has(ctx.authorId)) {
641
712
  await ctx.reply("This command is owner-only.");
642
713
  return false;
@@ -669,6 +740,190 @@ var StoatxHandler = class {
669
740
  }
670
741
  }
671
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
+ }
672
927
  if (!await this.cooldownManager.check(ctx, metadata)) {
673
928
  const remaining = await this.cooldownManager.getRemaining(ctx, metadata);
674
929
  if (typeof instance.onCooldown === "function") {
@@ -694,26 +949,26 @@ var StoatxHandler = class {
694
949
  }
695
950
  }
696
951
  /**
697
- * Get the command registry
698
- */
952
+ * Get the command registry
953
+ */
699
954
  getRegistry() {
700
955
  return this.registry;
701
956
  }
702
957
  /**
703
- * Get a command by name or alias
704
- */
958
+ * Get a command by name or alias
959
+ */
705
960
  getCommand(name) {
706
961
  return this.registry.get(name);
707
962
  }
708
963
  /**
709
- * Get all commands
710
- */
964
+ * Get all commands
965
+ */
711
966
  getCommands() {
712
967
  return this.registry.getAll();
713
968
  }
714
969
  /**
715
- * Reload all commands
716
- */
970
+ * Reload all commands
971
+ */
717
972
  async reload() {
718
973
  this.registry.clear();
719
974
  if (this.cooldownManager.clear) {
@@ -726,39 +981,65 @@ var StoatxHandler = class {
726
981
  await this.registry.autoDiscover(this.discoveryOptions);
727
982
  }
728
983
  /**
729
- * Check if a user is an owner
730
- */
984
+ * Check if a user is an owner
985
+ */
731
986
  isOwner(userId) {
732
987
  return this.owners.has(userId);
733
988
  }
734
989
  /**
735
- * Add an owner
736
- */
990
+ * Add an owner
991
+ */
737
992
  addOwner(userId) {
738
993
  this.owners.add(userId);
739
994
  }
740
995
  /**
741
- * Remove an owner
742
- */
996
+ * Remove an owner
997
+ */
743
998
  removeOwner(userId) {
744
999
  this.owners.delete(userId);
745
1000
  }
746
1001
  /**
747
- * Resolve the prefix for a context
748
- */
1002
+ * Resolve the prefix for a context
1003
+ */
749
1004
  async resolvePrefix(serverId) {
750
1005
  if (typeof this.prefixResolver === "function") {
751
- return this.prefixResolver({ serverId });
1006
+ return this.prefixResolver({
1007
+ serverId
1008
+ });
752
1009
  }
753
1010
  return this.prefixResolver;
754
1011
  }
755
1012
  };
756
1013
 
1014
+ // src/client.ts
1015
+ import { Client as StoatClient } from "@stoatx/client";
1016
+ var Client = class extends StoatClient {
1017
+ static {
1018
+ __name(this, "Client");
1019
+ }
1020
+ handler;
1021
+ constructor(options) {
1022
+ super(options);
1023
+ this.handler = new StoatxHandler({
1024
+ ...options,
1025
+ client: this
1026
+ });
1027
+ }
1028
+ async login(token) {
1029
+ await this.handler.init();
1030
+ return super.login(token);
1031
+ }
1032
+ async executeCommand(message) {
1033
+ await this.handler.handle(message);
1034
+ }
1035
+ };
1036
+
757
1037
  // src/index.ts
758
1038
  export * from "@stoatx/client";
759
1039
  export {
760
1040
  Client,
761
1041
  CommandRegistry,
1042
+ CommandValidationError,
762
1043
  DefaultCooldownManager,
763
1044
  Guard,
764
1045
  METADATA_KEYS,