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