vimcord 1.0.38 → 1.0.40

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.cjs CHANGED
@@ -69,6 +69,7 @@ __export(index_exports, {
69
69
  StatusType: () => StatusType,
70
70
  Vimcord: () => Vimcord,
71
71
  VimcordCLI: () => VimcordCLI,
72
+ VimcordErrorHandler: () => VimcordErrorHandler,
72
73
  __zero: () => __zero,
73
74
  cleanMention: () => cleanMention,
74
75
  clientLoggerFactory: () => clientLoggerFactory,
@@ -85,6 +86,7 @@ __export(index_exports, {
85
86
  createStaffConfig: () => createStaffConfig,
86
87
  createToolsConfig: () => createToolsConfig,
87
88
  createVimcordStatusConfig: () => createVimcordStatusConfig,
89
+ deepMerge: () => deepMerge,
88
90
  defineClientOptions: () => defineClientOptions,
89
91
  defineGlobalToolsConfig: () => defineGlobalToolsConfig,
90
92
  defineVimcordConfig: () => defineVimcordConfig,
@@ -100,7 +102,6 @@ __export(index_exports, {
100
102
  getFirstMentionId: () => getFirstMentionId,
101
103
  getMessageMention: () => getMessageMention,
102
104
  getPackageJson: () => getPackageJson,
103
- getProcessDir: () => getProcessDir,
104
105
  globalToolsConfig: () => globalToolsConfig,
105
106
  importModulesFromDir: () => importModulesFromDir,
106
107
  isMentionOrSnowflake: () => isMentionOrSnowflake,
@@ -244,8 +245,30 @@ function validateCommandPermissions(permissions, client, user, command) {
244
245
  return { validated: true };
245
246
  }
246
247
 
248
+ // src/utils/mergeUtils.ts
249
+ function isPlainObject(value) {
250
+ if (typeof value !== "object" || value === null) return false;
251
+ if (Array.isArray(value)) return false;
252
+ return Object.prototype.toString.call(value) === "[object Object]";
253
+ }
254
+ function deepMerge(target, ...sources) {
255
+ for (const source of sources) {
256
+ if (!source) continue;
257
+ for (const key in source) {
258
+ if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
259
+ const sourceValue = source[key];
260
+ const targetValue = target[key];
261
+ if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
262
+ deepMerge(targetValue, sourceValue);
263
+ } else if (sourceValue !== void 0) {
264
+ target[key] = sourceValue;
265
+ }
266
+ }
267
+ }
268
+ return target;
269
+ }
270
+
247
271
  // src/builders/baseCommand.builder.ts
248
- var import_lodash = __toESM(require("lodash"));
249
272
  var import_node_crypto = require("crypto");
250
273
  var BaseCommandBuilder = class {
251
274
  uuid = (0, import_node_crypto.randomUUID)();
@@ -259,8 +282,7 @@ var BaseCommandBuilder = class {
259
282
  [1 /* Guild */]: /* @__PURE__ */ new Map(),
260
283
  [2 /* Channel */]: /* @__PURE__ */ new Map()
261
284
  };
262
- /** * Mapping of CommandTypes to their respective config keys in the Vimcord client
263
- */
285
+ /** Mapping of CommandTypes to their respective config keys in the Vimcord client */
264
286
  typeConfigMapping = {
265
287
  [0 /* Slash */]: "slashCommands",
266
288
  [1 /* Prefix */]: "prefixCommands",
@@ -285,7 +307,7 @@ var BaseCommandBuilder = class {
285
307
  resolveConfig(client) {
286
308
  const typeKey = this.typeConfigMapping[this.commandType];
287
309
  const typeSpecificGlobals = client.config?.[typeKey] || {};
288
- return import_lodash.default.merge({}, typeSpecificGlobals, this.options);
310
+ return deepMerge({}, typeSpecificGlobals, this.options);
289
311
  }
290
312
  /**
291
313
  * Executes the command lifecycle.
@@ -403,12 +425,12 @@ var BaseCommandBuilder = class {
403
425
  }
404
426
  /** Merge new permission requirements into the existing ones */
405
427
  setPermissions(perms) {
406
- this.options.permissions = import_lodash.default.merge(this.options.permissions || {}, perms);
428
+ this.options.permissions = deepMerge(this.options.permissions || {}, perms);
407
429
  return this;
408
430
  }
409
- /** Add custom logic checks that run before execution */
410
- addConditions(...conditions) {
411
- this.options.conditions = [...this.options.conditions || [], ...conditions];
431
+ /** Set the custom conditions that must be met for this command to execute */
432
+ setConditions(conditions) {
433
+ this.options.conditions = conditions;
412
434
  return this;
413
435
  }
414
436
  /** Set the primary command execution logic */
@@ -416,79 +438,21 @@ var BaseCommandBuilder = class {
416
438
  this.options.execute = fn;
417
439
  return this;
418
440
  }
419
- /** * Set the custom conditions that must be met for this command to execute
420
- */
421
- setConditions(conditions) {
422
- this.options.conditions = conditions;
423
- return this;
424
- }
425
- /** * Set the command metadata configuration
426
- */
441
+ /** Set the command metadata configuration */
427
442
  setMetadata(metadata) {
428
- this.options.metadata = import_lodash.default.merge(this.options.metadata || {}, metadata);
443
+ this.options.metadata = deepMerge(this.options.metadata || {}, metadata);
429
444
  return this;
430
445
  }
431
- /** * Set the rate limiting options for this command
432
- */
446
+ /** Set the rate limiting options for this command */
433
447
  setRateLimit(options) {
434
448
  this.options.rateLimit = options;
435
449
  this.validateBaseConfig();
436
450
  return this;
437
451
  }
438
- /** * Set whether to log whenever this command is executed
439
- * @defaultValue true
440
- */
441
- setLogExecution(log) {
442
- this.options.logExecution = log;
443
- return this;
444
- }
445
- /** * Set the function to execute before the main command logic
446
- */
447
- setBeforeExecute(callback) {
448
- this.options.beforeExecute = callback;
449
- return this;
450
- }
451
- /** * Set the function to execute after successful command execution
452
- */
453
- setAfterExecute(callback) {
454
- this.options.afterExecute = callback;
455
- return this;
456
- }
457
- /** * Set the function to execute when the required permissions are not met
458
- */
459
- setOnMissingPermissions(callback) {
460
- this.options.onMissingPermissions = callback;
461
- return this;
462
- }
463
- /** * Set the function to execute when the required conditions are not met
464
- */
465
- setOnConditionsNotMet(callback) {
466
- this.options.onConditionsNotMet = callback;
467
- return this;
468
- }
469
- /** * Set the function to execute when this command is used when its disabled
470
- */
471
- setOnUsedWhenDisabled(callback) {
472
- this.options.onUsedWhenDisabled = callback;
473
- return this;
474
- }
475
- /** * Set the function to execute when the rate limit is hit
476
- */
477
- setOnRateLimit(callback) {
478
- this.options.onRateLimit = callback;
479
- return this;
480
- }
481
- /** * Set a custom error handler for this command
482
- */
483
- setOnError(callback) {
484
- this.options.onError = callback;
485
- return this;
486
- }
487
452
  };
488
453
 
489
454
  // src/builders/contextCommand.builder.ts
490
455
  var import_discord2 = require("discord.js");
491
- var import_lodash2 = __toESM(require("lodash"));
492
456
  var ContextCommandBuilder = class extends BaseCommandBuilder {
493
457
  builder;
494
458
  constructor(config) {
@@ -516,14 +480,6 @@ var ContextCommandBuilder = class extends BaseCommandBuilder {
516
480
  this.validateBuilder();
517
481
  return this;
518
482
  }
519
- setDeferReply(defer) {
520
- this.options.deferReply = defer;
521
- return this;
522
- }
523
- setDeployment(deployment) {
524
- this.options.deployment = import_lodash2.default.merge(this.options.deployment || {}, deployment);
525
- return this;
526
- }
527
483
  setExecute(fn) {
528
484
  const originalExecute = fn;
529
485
  this.options.execute = async (client, interaction) => {
@@ -538,7 +494,6 @@ var ContextCommandBuilder = class extends BaseCommandBuilder {
538
494
 
539
495
  // src/builders/event.builder.ts
540
496
  var import_node_crypto2 = require("crypto");
541
- var import_lodash3 = __toESM(require("lodash"));
542
497
 
543
498
  // src/tools/Logger.ts
544
499
  var import_chalk = __toESM(require("chalk"));
@@ -799,77 +754,14 @@ var EventBuilder = class _EventBuilder {
799
754
  onError: this.onError
800
755
  };
801
756
  }
802
- setEvent(event) {
803
- this.event = event;
804
- return this;
805
- }
806
- setName(name) {
807
- this.name = name;
808
- return this;
809
- }
810
757
  setEnabled(enabled) {
811
758
  this.enabled = enabled;
812
759
  return this;
813
760
  }
814
- enable() {
815
- return this.setEnabled(true);
816
- }
817
- disable() {
818
- return this.setEnabled(false);
819
- }
820
- setOnce(once = true) {
821
- this.once = once;
822
- return this;
823
- }
824
- setPriority(priority) {
825
- this.priority = priority;
826
- return this;
827
- }
828
- addCondition(condition) {
829
- if (!this.conditions) this.conditions = [];
830
- this.conditions.push(condition);
831
- return this;
832
- }
833
- setConditions(conditions) {
834
- this.conditions = conditions;
835
- return this;
836
- }
837
- setMetadata(metadata) {
838
- this.metadata = import_lodash3.default.merge(this.metadata, metadata);
839
- return this;
840
- }
841
- setDeployment(deployment) {
842
- this.deployment = import_lodash3.default.merge(this.deployment, deployment);
843
- return this;
844
- }
845
- setRateLimit(options) {
846
- this.rateLimit = options;
847
- return this;
848
- }
849
- setBeforeExecute(beforeExecute) {
850
- this.beforeExecute = beforeExecute;
851
- return this;
852
- }
853
761
  setExecute(execute) {
854
762
  this.execute = execute;
855
763
  return this;
856
764
  }
857
- setAfterExecute(afterExecute) {
858
- this.afterExecute = afterExecute;
859
- return this;
860
- }
861
- setOnError(onError) {
862
- this.onError = onError;
863
- return this;
864
- }
865
- getRateLimitInfo() {
866
- if (!this.rateLimit) return null;
867
- return { ...this.rateLimitData, isLimited: this.isRateLimited(false) };
868
- }
869
- resetRateLimit() {
870
- this.rateLimitData = { executions: 0, timestamp: 0 };
871
- return this;
872
- }
873
765
  isRateLimited(updateExecutions = true) {
874
766
  if (!this.rateLimit) return false;
875
767
  const now = Date.now();
@@ -942,39 +834,9 @@ var PrefixCommandBuilder = class extends BaseCommandBuilder {
942
834
  throw new Error(`[Vimcord] PrefixCommandBuilder: Command name is required.`);
943
835
  }
944
836
  }
945
- // --- Fluent API (Prefix Specific Only) ---
946
- /**
947
- * Set the primary name of the command.
948
- */
949
- setName(name) {
950
- this.options.name = name;
951
- return this;
952
- }
953
- /**
954
- * Set or replace the command aliases.
955
- */
956
- setAliases(aliases) {
957
- this.options.aliases = aliases;
958
- return this;
959
- }
960
- /**
961
- * Add additional aliases without clearing existing ones.
962
- */
963
- addAliases(...aliases) {
964
- this.options.aliases = [...this.options.aliases || [], ...aliases];
965
- return this;
966
- }
967
- /**
968
- * Set the command description.
969
- */
970
- setDescription(description) {
971
- this.options.description = description;
972
- return this;
973
- }
974
837
  // --- Overrides ---
975
838
  /**
976
839
  * Override setExecute to ensure handleExecution remains the entry point.
977
- * This is the only base method we need to redefine.
978
840
  */
979
841
  setExecute(fn) {
980
842
  const originalExecute = fn;
@@ -995,7 +857,6 @@ var PrefixCommandBuilder = class extends BaseCommandBuilder {
995
857
 
996
858
  // src/builders/slashCommand.builder.ts
997
859
  var import_discord3 = require("discord.js");
998
- var import_lodash4 = __toESM(require("lodash"));
999
860
  var SlashCommandBuilder = class extends BaseCommandBuilder {
1000
861
  builder;
1001
862
  routes = /* @__PURE__ */ new Map();
@@ -1036,19 +897,6 @@ var SlashCommandBuilder = class extends BaseCommandBuilder {
1036
897
  this.validateBuilder();
1037
898
  return this;
1038
899
  }
1039
- setDeferReply(defer) {
1040
- this.options.deferReply = defer;
1041
- return this;
1042
- }
1043
- setDeployment(deployment) {
1044
- this.options.deployment = import_lodash4.default.merge(this.options.deployment || {}, deployment);
1045
- return this;
1046
- }
1047
- setRoutes(...routes) {
1048
- this.routes.clear();
1049
- this.addRoutes(...routes);
1050
- return this;
1051
- }
1052
900
  addRoutes(...routes) {
1053
901
  if (!this.options.routes) this.options.routes = [];
1054
902
  for (const route of routes) {
@@ -1060,10 +908,6 @@ var SlashCommandBuilder = class extends BaseCommandBuilder {
1060
908
  }
1061
909
  return this;
1062
910
  }
1063
- setUnknownRouteHandler(handler) {
1064
- this.options.onUnknownRouteHandler = handler;
1065
- return this;
1066
- }
1067
911
  setExecute(fn) {
1068
912
  const originalExecute = fn;
1069
913
  this.options.execute = async (client, interaction) => {
@@ -1080,7 +924,7 @@ var SlashCommandBuilder = class extends BaseCommandBuilder {
1080
924
  }
1081
925
  };
1082
926
 
1083
- // src/modules/builtins/context-command.builtins.ts
927
+ // src/modules/builtins/contextCommand.builtin.ts
1084
928
  var contextCommandHandler = new EventBuilder({
1085
929
  event: "interactionCreate",
1086
930
  name: "ContextCommandHandler",
@@ -1102,7 +946,7 @@ var contextCommandHandler = new EventBuilder({
1102
946
  }
1103
947
  });
1104
948
 
1105
- // src/modules/builtins/prefix-command.builtins.ts
949
+ // src/modules/builtins/prefixCommand.builtin.ts
1106
950
  var import_discord4 = require("discord.js");
1107
951
  var prefixCommandHandler = new EventBuilder({
1108
952
  event: "messageCreate",
@@ -1144,7 +988,7 @@ var prefixCommandHandler = new EventBuilder({
1144
988
  }
1145
989
  });
1146
990
 
1147
- // src/modules/builtins/slash-command.builtins.ts
991
+ // src/modules/builtins/slashCommand.builtin.ts
1148
992
  var slashCommandHandler = new EventBuilder({
1149
993
  event: "interactionCreate",
1150
994
  name: "SlashCommandHandler",
@@ -1166,14 +1010,204 @@ var slashCommandHandler = new EventBuilder({
1166
1010
  }
1167
1011
  });
1168
1012
 
1013
+ // src/client/vimcord.cli.ts
1014
+ var import_node_readline = require("readline");
1015
+ var import_qznt = require("qznt");
1016
+ var VimcordCLI = class _VimcordCLI {
1017
+ static mode = "off";
1018
+ static setMode(mode) {
1019
+ if (_VimcordCLI.mode === mode) return;
1020
+ _VimcordCLI.mode = mode;
1021
+ if (mode === "on") {
1022
+ CLI.logger.log(`~ Type ${CLI.options.prefix}help to view available commands`);
1023
+ } else {
1024
+ CLI.logger.log(`~ [MODE] Now set to "${mode}"`);
1025
+ }
1026
+ }
1027
+ rl;
1028
+ options;
1029
+ commands = /* @__PURE__ */ new Map();
1030
+ logger = new Logger({ prefixEmoji: "\u{1F680}", prefix: "CLI", showTimestamp: false });
1031
+ constructor(options) {
1032
+ this.options = options;
1033
+ this.rl = (0, import_node_readline.createInterface)({
1034
+ input: process.stdin,
1035
+ output: process.stdout,
1036
+ terminal: false
1037
+ });
1038
+ this.rl.on("line", (line) => {
1039
+ if (_VimcordCLI.mode !== "on") return;
1040
+ const { isCommand, commandName, content, args } = this.parseLine(line);
1041
+ if (!isCommand) return;
1042
+ const command = this.commands.get(commandName);
1043
+ if (!command) {
1044
+ const nearestMatches = Array.from(this.commands.keys()).filter(
1045
+ (cmd) => cmd.toLowerCase().includes(commandName.toLowerCase())
1046
+ );
1047
+ return this.logger.error(
1048
+ `Unknown command '${commandName}'${nearestMatches.length ? `. Did you mean ${nearestMatches.length > 1 ? `[${nearestMatches.map((m) => `'${this.options.prefix}${m}'`).join(", ")}]` : `'${this.options.prefix}${nearestMatches[0]}'`}?` : ""}`
1049
+ );
1050
+ }
1051
+ command.fn(args, content);
1052
+ });
1053
+ }
1054
+ parseLine(line) {
1055
+ if (line.startsWith(this.options.prefix)) {
1056
+ line = line.slice(this.options.prefix.length);
1057
+ } else {
1058
+ return { isCommand: false };
1059
+ }
1060
+ const args = line.split(" ").map((s) => s.trim());
1061
+ const commandName = args.shift();
1062
+ return { isCommand: true, commandName, content: args.join(" "), args };
1063
+ }
1064
+ getClientInstance(line) {
1065
+ const clientIndex = import_qznt.$.str.getFlag(line, "--client", 1) || import_qznt.$.str.getFlag(line, "-c", 1);
1066
+ if (clientIndex) {
1067
+ const idx = Number(clientIndex);
1068
+ if (isNaN(idx)) {
1069
+ CLI.logger.error(`'${clientIndex}' is not a valid number`);
1070
+ return void 0;
1071
+ }
1072
+ const client = useClient(idx);
1073
+ if (!client) {
1074
+ CLI.logger.error("Client instance not found");
1075
+ return void 0;
1076
+ }
1077
+ return client;
1078
+ } else {
1079
+ const client = useClient(0);
1080
+ if (!client) {
1081
+ CLI.logger.error("Client instance not found");
1082
+ return void 0;
1083
+ }
1084
+ return client;
1085
+ }
1086
+ }
1087
+ addCommand(commandName, description, fn) {
1088
+ this.commands.set(commandName, { description, fn });
1089
+ }
1090
+ removeCommand(commandName) {
1091
+ if (!this.commands.has(commandName)) return false;
1092
+ this.commands.delete(commandName);
1093
+ return true;
1094
+ }
1095
+ };
1096
+ var CLI = new VimcordCLI({ prefix: "/" });
1097
+ CLI.addCommand("help", "View information about a command, or the available CLI options", (args) => {
1098
+ const prefix = CLI.options.prefix;
1099
+ const helpList = {};
1100
+ for (const cmd of CLI.commands.entries()) {
1101
+ const commandName = cmd[0];
1102
+ const commandDescription = cmd[1].description;
1103
+ helpList[`${prefix}${commandName}`] = `~ ${commandDescription}`;
1104
+ }
1105
+ CLI.logger.table("(help)", helpList);
1106
+ });
1107
+ CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
1108
+ const client = CLI.getClientInstance(content);
1109
+ if (!client) return;
1110
+ const mode = args[0]?.toLowerCase() || "";
1111
+ if (!["guild", "global"].includes(mode)) {
1112
+ return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
1113
+ }
1114
+ let guildIds = (import_qznt.$.str.getFlag(content, "--guilds", 1) || import_qznt.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
1115
+ if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
1116
+ switch (mode) {
1117
+ case "guild":
1118
+ CLI.logger.info("Registering guild commands...");
1119
+ await client.commands.registerGuild({ guilds: guildIds });
1120
+ break;
1121
+ case "global":
1122
+ CLI.logger.info("Registering global commands...");
1123
+ await client.commands.registerGlobal();
1124
+ break;
1125
+ }
1126
+ });
1127
+ CLI.addCommand("unregister", "Unregister app commands globally, or per guild", async (args, content) => {
1128
+ const client = CLI.getClientInstance(content);
1129
+ if (!client) return;
1130
+ const mode = args[0]?.toLowerCase() || "";
1131
+ if (!["guild", "global"].includes(mode)) {
1132
+ return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
1133
+ }
1134
+ let guildIds = (import_qznt.$.str.getFlag(content, "--guilds", 1) || import_qznt.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
1135
+ if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
1136
+ switch (mode) {
1137
+ case "guild":
1138
+ CLI.logger.info("Unregistering guild commands...");
1139
+ await client.commands.unregisterGuild({ guilds: guildIds });
1140
+ break;
1141
+ case "global":
1142
+ CLI.logger.info("Unregistering global commands...");
1143
+ await client.commands.unregisterGlobal();
1144
+ break;
1145
+ }
1146
+ });
1147
+ CLI.addCommand("stats", "View statistics about a client instance", (args, content) => {
1148
+ const client = CLI.getClientInstance(content);
1149
+ if (!client) return;
1150
+ CLI.logger.table(`(stats) ~ ${client.config.app.name}`, {
1151
+ "Guilds:": import_qznt.$.format.number(client.guilds.cache.size),
1152
+ "Ping:": `${client.ws.ping || 0}ms`,
1153
+ "Uptime:": `${import_qznt.$.math.secs(client.uptime || 0)}s`,
1154
+ "Process Uptime:": `${Math.floor(process.uptime())}s`,
1155
+ "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
1156
+ });
1157
+ });
1158
+ CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
1159
+ const client = CLI.getClientInstance(content);
1160
+ if (!client) return;
1161
+ const mode = (args[0] || "slash").toLowerCase();
1162
+ switch (mode) {
1163
+ case "slash": {
1164
+ const commands = Array.from(client.commands.slash.commands.values());
1165
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
1166
+ const tableData = {};
1167
+ for (const cmd of commands) {
1168
+ tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
1169
+ }
1170
+ return CLI.logger.table(`(cmds) ~ slash (${import_qznt.$.format.number(commands.length)})`, tableData);
1171
+ }
1172
+ case "prefix": {
1173
+ const commands = Array.from(client.commands.prefix.commands.values());
1174
+ commands.sort((a, b) => {
1175
+ const nameA = a.toConfig().name;
1176
+ const nameB = b.toConfig().name;
1177
+ return nameA.localeCompare(nameB);
1178
+ });
1179
+ const tableData = {};
1180
+ const defaultPrefix = client.config.prefixCommands.defaultPrefix;
1181
+ for (const cmd of commands) {
1182
+ const config = cmd.toConfig();
1183
+ const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
1184
+ tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
1185
+ }
1186
+ return CLI.logger.table(`(cmds) ~ prefix (${import_qznt.$.format.number(commands.length)})`, tableData);
1187
+ }
1188
+ case "ctx": {
1189
+ const commands = Array.from(client.commands.context.commands.values());
1190
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
1191
+ const tableData = {};
1192
+ for (const cmd of commands) {
1193
+ const type = cmd.builder.type === 2 ? "User" : "Msg";
1194
+ tableData[`[${type}] ${cmd.builder.name}`] = "";
1195
+ }
1196
+ return CLI.logger.table(`(cmds) ~ ctx (${import_qznt.$.format.number(commands.length)})`, tableData);
1197
+ }
1198
+ default:
1199
+ return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
1200
+ }
1201
+ });
1202
+
1169
1203
  // src/tools/BetterEmbed.ts
1170
1204
  var import_discord6 = require("discord.js");
1171
1205
 
1172
- // src/utils/config.factory.ts
1173
- var import_lodash5 = __toESM(require("lodash"));
1206
+ // src/utils/configFactory.ts
1174
1207
  function createConfigFactory(defaultConfig6, validate) {
1175
1208
  return (options = {}, existing) => {
1176
- const result = import_lodash5.default.merge({}, defaultConfig6, existing, options);
1209
+ const base = existing ? { ...existing } : { ...defaultConfig6 };
1210
+ const result = deepMerge(base, options);
1177
1211
  validate?.(result);
1178
1212
  return result;
1179
1213
  };
@@ -1667,7 +1701,7 @@ var BetterEmbed = class _BetterEmbed {
1667
1701
  }
1668
1702
  };
1669
1703
 
1670
- // src/utils/command-error.utils.ts
1704
+ // src/modules/builtins/commandError.builtin.ts
1671
1705
  var import_discord7 = require("discord.js");
1672
1706
  async function sendCommandErrorEmbed(client, error, guild, messageOrInteraction) {
1673
1707
  if (!client.features.enableCommandErrorMessage) return null;
@@ -1724,8 +1758,8 @@ ${error.stack}`), {
1724
1758
  return msg;
1725
1759
  }
1726
1760
 
1727
- // src/client/error-handler.ts
1728
- var ErrorHandler = class {
1761
+ // src/client/vimcord.errorHandler.ts
1762
+ var VimcordErrorHandler = class {
1729
1763
  client;
1730
1764
  constructor(client) {
1731
1765
  this.client = client;
@@ -1818,7 +1852,7 @@ var clientLoggerFactory = (client) => new Logger({ prefixEmoji: "\u26A1", prefix
1818
1852
  }
1819
1853
  });
1820
1854
 
1821
- // src/utils/process.utils.ts
1855
+ // src/utils/processUtils.ts
1822
1856
  var import_node_fs = require("fs");
1823
1857
  var import_node_path = require("path");
1824
1858
  function getPackageJson() {
@@ -1861,6 +1895,7 @@ var createPrefixCommandConfig = createConfigFactory(defaultConfig3);
1861
1895
 
1862
1896
  // src/configs/slashCommand.config.ts
1863
1897
  var defaultConfig4 = {
1898
+ enabled: true,
1864
1899
  logExecution: true
1865
1900
  };
1866
1901
  var createSlashCommandConfig = createConfigFactory(defaultConfig4);
@@ -1953,22 +1988,28 @@ function createClient(options, features, config) {
1953
1988
  // src/modules/command.manager.ts
1954
1989
  var import_discord8 = require("discord.js");
1955
1990
 
1956
- // src/utils/import.utils.ts
1991
+ // src/utils/importUtils.ts
1957
1992
  var import_node_path2 = __toESM(require("path"));
1958
- var import_qznt = require("qznt");
1993
+ var import_qznt2 = require("qznt");
1959
1994
  function getProcessDir() {
1960
1995
  const mainPath = process.argv[1];
1961
1996
  if (!mainPath) return "";
1962
1997
  return import_node_path2.default.dirname(mainPath);
1963
1998
  }
1999
+ function testFilenameSuffix(filename, suffix) {
2000
+ if (!suffix) return filename.endsWith(".ts") || filename.endsWith(".js");
2001
+ if (Array.isArray(suffix)) {
2002
+ return suffix.some((s) => filename.endsWith(`${s}.ts`) || filename.endsWith(`${s}.js`));
2003
+ } else {
2004
+ return filename.endsWith(`${suffix}.ts`) || filename.endsWith(`${suffix}.js`);
2005
+ }
2006
+ }
1964
2007
  async function importModulesFromDir(dir, suffix) {
1965
2008
  const cwd = getProcessDir();
1966
2009
  const MODULE_RELATIVE_PATH = import_node_path2.default.join(cwd, dir);
1967
2010
  const MODULE_LOG_PATH = dir;
1968
- const files = import_qznt.$.fs.readDir(MODULE_RELATIVE_PATH).filter((fn) => fn.endsWith(`${suffix ? `${suffix}` : ""}.js`) || fn.endsWith(`${suffix ? `${suffix}` : ""}.ts`));
1969
- if (!files.length) {
1970
- return [];
1971
- }
2011
+ const files = import_qznt2.$.fs.readDir(MODULE_RELATIVE_PATH).filter((filename) => testFilenameSuffix(filename, suffix));
2012
+ if (!files.length) return [];
1972
2013
  const modules = await Promise.all(
1973
2014
  files.map(async (fn) => {
1974
2015
  let _path = import_node_path2.default.join(MODULE_RELATIVE_PATH, fn);
@@ -1991,7 +2032,7 @@ async function importModulesFromDir(dir, suffix) {
1991
2032
  return filteredModules;
1992
2033
  }
1993
2034
 
1994
- // src/modules/base-module.importer.ts
2035
+ // src/modules/importers/baseModule.importer.ts
1995
2036
  var ModuleImporter = class {
1996
2037
  client;
1997
2038
  constructor(client) {
@@ -2001,9 +2042,8 @@ var ModuleImporter = class {
2001
2042
  if (set) this.items.clear();
2002
2043
  const dirs = Array.isArray(dir) ? dir : [dir];
2003
2044
  const modules = [];
2004
- const effectiveSuffix = Array.isArray(suffix) ? suffix[0] : suffix ?? this.itemSuffix;
2005
2045
  for (const _dir of dirs) {
2006
- const results = await importModulesFromDir(_dir, effectiveSuffix ?? void 0);
2046
+ const results = await importModulesFromDir(_dir, suffix ?? this.itemSuffix);
2007
2047
  modules.push(...results.map(({ module: module2 }) => module2.default));
2008
2048
  }
2009
2049
  for (const module2 of modules) {
@@ -2452,7 +2492,6 @@ async function fetchRole(guild, roleId) {
2452
2492
 
2453
2493
  // src/types/status.ts
2454
2494
  var import_discord10 = require("discord.js");
2455
- var import_lodash6 = __toESM(require("lodash"));
2456
2495
  var StatusType = /* @__PURE__ */ ((StatusType2) => {
2457
2496
  StatusType2["DND"] = "dnd";
2458
2497
  StatusType2["Idle"] = "idle";
@@ -2475,12 +2514,12 @@ var defaultPresence = {
2475
2514
  }
2476
2515
  };
2477
2516
  function createVimcordStatusConfig(options = {}) {
2478
- return import_lodash6.default.merge(defaultPresence, options);
2517
+ return deepMerge({ ...defaultPresence }, options);
2479
2518
  }
2480
2519
 
2481
2520
  // src/modules/status.manager.ts
2482
2521
  var import_node_events = __toESM(require("events"));
2483
- var import_qznt2 = require("qznt");
2522
+ var import_qznt3 = require("qznt");
2484
2523
  var StatusManager = class {
2485
2524
  client;
2486
2525
  logger;
@@ -2515,14 +2554,14 @@ var StatusManager = class {
2515
2554
  return client;
2516
2555
  }
2517
2556
  async formatActivityName(name) {
2518
- name = name.replace("$USER_COUNT", import_qznt2.$.format.number(this.client.users.cache.size)).replace("$GUILD_COUNT", import_qznt2.$.format.number(this.client.guilds.cache.size)).replace(
2557
+ name = name.replace("$USER_COUNT", import_qznt3.$.format.number(this.client.users.cache.size)).replace("$GUILD_COUNT", import_qznt3.$.format.number(this.client.guilds.cache.size)).replace(
2519
2558
  "$INVITE",
2520
2559
  this.client.config.staff.guild.inviteUrl ? this.client.config.staff.guild.inviteUrl : "<STAFF_INVITE_URL_NOT_SET>"
2521
2560
  );
2522
2561
  if (name.includes("$STAFF_GUILD_MEMBER_COUNT")) {
2523
2562
  await fetchGuild(this.client, this.client.config.staff.guild.id).then((guild) => {
2524
2563
  if (!guild) return name = name.replace("$STAFF_GUILD_MEMBER_COUNT", "<STAFF_GUILD_NOT_FOUND>");
2525
- name = name.replace("$STAFF_GUILD_MEMBER_COUNT", import_qznt2.$.format.number(guild.members.cache.size));
2564
+ name = name.replace("$STAFF_GUILD_MEMBER_COUNT", import_qznt3.$.format.number(guild.members.cache.size));
2526
2565
  }).catch((err) => this.logger.error("Failed to fetch the staff guild", err));
2527
2566
  }
2528
2567
  return name;
@@ -2537,7 +2576,7 @@ var StatusManager = class {
2537
2576
  async statusRotationTask(clientStatus) {
2538
2577
  let activity;
2539
2578
  if (clientStatus.randomize && Array.isArray(clientStatus.activity)) {
2540
- activity = import_qznt2.$.rnd.choice(clientStatus.activity, { not: this.lastActivity });
2579
+ activity = import_qznt3.$.rnd.choice(clientStatus.activity, { not: this.lastActivity });
2541
2580
  this.lastActivity = activity;
2542
2581
  } else {
2543
2582
  const activityIndex = (this.lastActivityIndex + 1) % clientStatus.activity.length;
@@ -2551,7 +2590,7 @@ var StatusManager = class {
2551
2590
  if (!clientStatus.interval) throw new Error("Cannot create client activity interval without interval time");
2552
2591
  this.task?.stop();
2553
2592
  this.task = null;
2554
- this.task = new import_qznt2.$.Loop(() => this.statusRotationTask(clientStatus), import_qznt2.$.math.ms(clientStatus.interval), true);
2593
+ this.task = new import_qznt3.$.Loop(() => this.statusRotationTask(clientStatus), import_qznt3.$.math.ms(clientStatus.interval), true);
2555
2594
  this.start();
2556
2595
  }
2557
2596
  start() {
@@ -2601,196 +2640,6 @@ var StatusManager = class {
2601
2640
  }
2602
2641
  };
2603
2642
 
2604
- // src/utils/vimcord.cli.ts
2605
- var import_node_readline = require("readline");
2606
- var import_qznt3 = require("qznt");
2607
- var VimcordCLI = class _VimcordCLI {
2608
- static mode = "off";
2609
- static setMode(mode) {
2610
- if (_VimcordCLI.mode === mode) return;
2611
- _VimcordCLI.mode = mode;
2612
- if (mode === "on") {
2613
- CLI.logger.log(`~ Type ${CLI.options.prefix}help to view available commands`);
2614
- } else {
2615
- CLI.logger.log(`~ [MODE] Now set to "${mode}"`);
2616
- }
2617
- }
2618
- rl;
2619
- options;
2620
- commands = /* @__PURE__ */ new Map();
2621
- logger = new Logger({ prefixEmoji: "\u{1F680}", prefix: "CLI", showTimestamp: false });
2622
- constructor(options) {
2623
- this.options = options;
2624
- this.rl = (0, import_node_readline.createInterface)({
2625
- input: process.stdin,
2626
- output: process.stdout,
2627
- terminal: false
2628
- });
2629
- this.rl.on("line", (line) => {
2630
- if (_VimcordCLI.mode !== "on") return;
2631
- const { isCommand, commandName, content, args } = this.parseLine(line);
2632
- if (!isCommand) return;
2633
- const command = this.commands.get(commandName);
2634
- if (!command) {
2635
- const nearestMatches = Array.from(this.commands.keys()).filter(
2636
- (cmd) => cmd.toLowerCase().includes(commandName.toLowerCase())
2637
- );
2638
- return this.logger.error(
2639
- `Unknown command '${commandName}'${nearestMatches.length ? `. Did you mean ${nearestMatches.length > 1 ? `[${nearestMatches.map((m) => `'${this.options.prefix}${m}'`).join(", ")}]` : `'${this.options.prefix}${nearestMatches[0]}'`}?` : ""}`
2640
- );
2641
- }
2642
- command.fn(args, content);
2643
- });
2644
- }
2645
- parseLine(line) {
2646
- if (line.startsWith(this.options.prefix)) {
2647
- line = line.slice(this.options.prefix.length);
2648
- } else {
2649
- return { isCommand: false };
2650
- }
2651
- const args = line.split(" ").map((s) => s.trim());
2652
- const commandName = args.shift();
2653
- return { isCommand: true, commandName, content: args.join(" "), args };
2654
- }
2655
- getClientInstance(line) {
2656
- const clientIndex = import_qznt3.$.str.getFlag(line, "--client", 1) || import_qznt3.$.str.getFlag(line, "-c", 1);
2657
- if (clientIndex) {
2658
- const idx = Number(clientIndex);
2659
- if (isNaN(idx)) {
2660
- CLI.logger.error(`'${clientIndex}' is not a valid number`);
2661
- return void 0;
2662
- }
2663
- const client = useClient(idx);
2664
- if (!client) {
2665
- CLI.logger.error("Client instance not found");
2666
- return void 0;
2667
- }
2668
- return client;
2669
- } else {
2670
- const client = useClient(0);
2671
- if (!client) {
2672
- CLI.logger.error("Client instance not found");
2673
- return void 0;
2674
- }
2675
- return client;
2676
- }
2677
- }
2678
- addCommand(commandName, description, fn) {
2679
- this.commands.set(commandName, { description, fn });
2680
- }
2681
- removeCommand(commandName) {
2682
- if (!this.commands.has(commandName)) return false;
2683
- this.commands.delete(commandName);
2684
- return true;
2685
- }
2686
- };
2687
- var CLI = new VimcordCLI({ prefix: "/" });
2688
- CLI.addCommand("help", "View information about a command, or the available CLI options", (args) => {
2689
- const prefix = CLI.options.prefix;
2690
- const helpList = {};
2691
- for (const cmd of CLI.commands.entries()) {
2692
- const commandName = cmd[0];
2693
- const commandDescription = cmd[1].description;
2694
- helpList[`${prefix}${commandName}`] = `~ ${commandDescription}`;
2695
- }
2696
- CLI.logger.table("(help)", helpList);
2697
- });
2698
- CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
2699
- const client = CLI.getClientInstance(content);
2700
- if (!client) return;
2701
- const mode = args[0]?.toLowerCase() || "";
2702
- if (!["guild", "global"].includes(mode)) {
2703
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
2704
- }
2705
- let guildIds = (import_qznt3.$.str.getFlag(content, "--guilds", 1) || import_qznt3.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
2706
- if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
2707
- switch (mode) {
2708
- case "guild":
2709
- CLI.logger.info("Registering guild commands...");
2710
- await client.commands.registerGuild({ guilds: guildIds });
2711
- break;
2712
- case "global":
2713
- CLI.logger.info("Registering global commands...");
2714
- await client.commands.registerGlobal();
2715
- break;
2716
- }
2717
- });
2718
- CLI.addCommand("unregister", "Unregister app commands globally, or per guild", async (args, content) => {
2719
- const client = CLI.getClientInstance(content);
2720
- if (!client) return;
2721
- const mode = args[0]?.toLowerCase() || "";
2722
- if (!["guild", "global"].includes(mode)) {
2723
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
2724
- }
2725
- let guildIds = (import_qznt3.$.str.getFlag(content, "--guilds", 1) || import_qznt3.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
2726
- if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
2727
- switch (mode) {
2728
- case "guild":
2729
- CLI.logger.info("Unregistering guild commands...");
2730
- await client.commands.unregisterGuild({ guilds: guildIds });
2731
- break;
2732
- case "global":
2733
- CLI.logger.info("Unregistering global commands...");
2734
- await client.commands.unregisterGlobal();
2735
- break;
2736
- }
2737
- });
2738
- CLI.addCommand("stats", "View statistics about a client instance", (args, content) => {
2739
- const client = CLI.getClientInstance(content);
2740
- if (!client) return;
2741
- CLI.logger.table(`(stats) ~ ${client.config.app.name}`, {
2742
- "Guilds:": import_qznt3.$.format.number(client.guilds.cache.size),
2743
- "Ping:": `${client.ws.ping || 0}ms`,
2744
- "Uptime:": `${import_qznt3.$.math.secs(client.uptime || 0)}s`,
2745
- "Process Uptime:": `${Math.floor(process.uptime())}s`,
2746
- "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
2747
- });
2748
- });
2749
- CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
2750
- const client = CLI.getClientInstance(content);
2751
- if (!client) return;
2752
- const mode = (args[0] || "slash").toLowerCase();
2753
- switch (mode) {
2754
- case "slash": {
2755
- const commands = Array.from(client.commands.slash.commands.values());
2756
- commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
2757
- const tableData = {};
2758
- for (const cmd of commands) {
2759
- tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
2760
- }
2761
- return CLI.logger.table(`(cmds) ~ slash (${import_qznt3.$.format.number(commands.length)})`, tableData);
2762
- }
2763
- case "prefix": {
2764
- const commands = Array.from(client.commands.prefix.commands.values());
2765
- commands.sort((a, b) => {
2766
- const nameA = a.toConfig().name;
2767
- const nameB = b.toConfig().name;
2768
- return nameA.localeCompare(nameB);
2769
- });
2770
- const tableData = {};
2771
- const defaultPrefix = client.config.prefixCommands.defaultPrefix;
2772
- for (const cmd of commands) {
2773
- const config = cmd.toConfig();
2774
- const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
2775
- tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
2776
- }
2777
- return CLI.logger.table(`(cmds) ~ prefix (${import_qznt3.$.format.number(commands.length)})`, tableData);
2778
- }
2779
- case "ctx": {
2780
- const commands = Array.from(client.commands.context.commands.values());
2781
- commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
2782
- const tableData = {};
2783
- for (const cmd of commands) {
2784
- const type = cmd.builder.type === 2 ? "User" : "Msg";
2785
- tableData[`[${type}] ${cmd.builder.name}`] = "";
2786
- }
2787
- return CLI.logger.table(`(cmds) ~ ctx (${import_qznt3.$.format.number(commands.length)})`, tableData);
2788
- }
2789
- default:
2790
- return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
2791
- }
2792
- });
2793
-
2794
2643
  // src/client/Vimcord.ts
2795
2644
  var import_discord11 = require("discord.js");
2796
2645
  var import_dotenv = require("dotenv");
@@ -2881,7 +2730,7 @@ var Vimcord = class _Vimcord extends import_discord11.Client {
2881
2730
  this.clientOptions = options;
2882
2731
  this.features = features;
2883
2732
  this.config = defineVimcordConfig(config);
2884
- this.error = new ErrorHandler(this);
2733
+ this.error = new VimcordErrorHandler(this);
2885
2734
  if (this.features.useGlobalErrorHandlers) {
2886
2735
  this.error.setupGlobalHandlers();
2887
2736
  }
@@ -3067,10 +2916,30 @@ var Vimcord = class _Vimcord extends import_discord11.Client {
3067
2916
  }
3068
2917
  };
3069
2918
 
3070
- // src/db/mongo/mongo-schema.builder.ts
3071
- var import_mongoose2 = require("mongoose");
3072
- var import_node_crypto4 = require("crypto");
3073
- var import_qznt6 = require("qznt");
2919
+ // src/client/error.handler.ts
2920
+ var ErrorHandler = class {
2921
+ client;
2922
+ constructor(client) {
2923
+ this.client = client;
2924
+ }
2925
+ /** Handles command errors - sends error embed to user, then rethrows. */
2926
+ async handleCommandError(error, guild, messageOrInteraction) {
2927
+ await sendCommandErrorEmbed(this.client, error, guild, messageOrInteraction);
2928
+ throw error;
2929
+ }
2930
+ /** Handles internal Vimcord errors - logs with [Vimcord] prefix. */
2931
+ handleVimcordError(error, context) {
2932
+ this.client.logger.error(`[Vimcord] [${context}]`, error);
2933
+ }
2934
+ /** Sets up global process error handlers. */
2935
+ setupGlobalHandlers() {
2936
+ process.on("uncaughtException", (err) => this.handleVimcordError(err, "Uncaught Exception"));
2937
+ process.on("unhandledRejection", (err) => this.handleVimcordError(err, "Unhandled Rejection"));
2938
+ process.on("exit", (code) => this.client.logger.debug(`Process exited with code ${code}`));
2939
+ this.client.on("error", (err) => this.handleVimcordError(err, "Client Error"));
2940
+ this.client.on("shardError", (err) => this.handleVimcordError(err, "Client Shard Error"));
2941
+ }
2942
+ };
3074
2943
 
3075
2944
  // src/db/mongo/mongo.database.ts
3076
2945
  var import_mongoose = __toESM(require("mongoose"));
@@ -3201,7 +3070,10 @@ var MongoDatabase = class _MongoDatabase {
3201
3070
  }
3202
3071
  };
3203
3072
 
3204
- // src/db/mongo/mongo-schema.builder.ts
3073
+ // src/db/mongo/mongoSchema.builder.ts
3074
+ var import_mongoose2 = require("mongoose");
3075
+ var import_node_crypto4 = require("crypto");
3076
+ var import_qznt6 = require("qznt");
3205
3077
  try {
3206
3078
  import("mongoose");
3207
3079
  } catch {
@@ -4595,6 +4467,7 @@ async function prompt(handler, options, sendOptions) {
4595
4467
  StatusType,
4596
4468
  Vimcord,
4597
4469
  VimcordCLI,
4470
+ VimcordErrorHandler,
4598
4471
  __zero,
4599
4472
  cleanMention,
4600
4473
  clientLoggerFactory,
@@ -4611,6 +4484,7 @@ async function prompt(handler, options, sendOptions) {
4611
4484
  createStaffConfig,
4612
4485
  createToolsConfig,
4613
4486
  createVimcordStatusConfig,
4487
+ deepMerge,
4614
4488
  defineClientOptions,
4615
4489
  defineGlobalToolsConfig,
4616
4490
  defineVimcordConfig,
@@ -4626,7 +4500,6 @@ async function prompt(handler, options, sendOptions) {
4626
4500
  getFirstMentionId,
4627
4501
  getMessageMention,
4628
4502
  getPackageJson,
4629
- getProcessDir,
4630
4503
  globalToolsConfig,
4631
4504
  importModulesFromDir,
4632
4505
  isMentionOrSnowflake,