mani-calc 1.2.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Settings Manager Module
3
+ * Handles app settings, themes, hotkeys, and auto-start
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ class SettingsManager {
11
+ constructor() {
12
+ this.settingsDir = path.join(os.homedir(), '.mani-calc');
13
+ this.settingsFile = path.join(this.settingsDir, 'settings.json');
14
+
15
+ // Default settings
16
+ this.defaults = {
17
+ hotkey: 'Alt+Space',
18
+ theme: 'dark',
19
+ autoStart: false,
20
+ soundEnabled: true,
21
+ historyLimit: 100,
22
+ clipboardEnabled: true,
23
+ showHints: true,
24
+ compactMode: false,
25
+ customAccentColor: '#00D9FF',
26
+ animationsEnabled: true,
27
+ position: 'center', // center, top, bottom
28
+ opacity: 0.95
29
+ };
30
+
31
+ this.settings = { ...this.defaults };
32
+ this.load();
33
+ }
34
+
35
+ /**
36
+ * Ensure settings directory exists
37
+ */
38
+ ensureDir() {
39
+ if (!fs.existsSync(this.settingsDir)) {
40
+ fs.mkdirSync(this.settingsDir, { recursive: true });
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Load settings from file
46
+ */
47
+ load() {
48
+ try {
49
+ if (fs.existsSync(this.settingsFile)) {
50
+ const data = fs.readFileSync(this.settingsFile, 'utf8');
51
+ const saved = JSON.parse(data);
52
+ this.settings = { ...this.defaults, ...saved };
53
+ }
54
+ } catch (error) {
55
+ console.error('Failed to load settings:', error.message);
56
+ this.settings = { ...this.defaults };
57
+ }
58
+ return this.settings;
59
+ }
60
+
61
+ /**
62
+ * Save settings to file
63
+ */
64
+ save() {
65
+ try {
66
+ this.ensureDir();
67
+ fs.writeFileSync(this.settingsFile, JSON.stringify(this.settings, null, 2));
68
+ return true;
69
+ } catch (error) {
70
+ console.error('Failed to save settings:', error.message);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get a setting
77
+ */
78
+ get(key) {
79
+ return this.settings[key] ?? this.defaults[key];
80
+ }
81
+
82
+ /**
83
+ * Set a setting
84
+ */
85
+ set(key, value) {
86
+ this.settings[key] = value;
87
+ this.save();
88
+ return value;
89
+ }
90
+
91
+ /**
92
+ * Get all settings
93
+ */
94
+ getAll() {
95
+ return { ...this.settings };
96
+ }
97
+
98
+ /**
99
+ * Reset to defaults
100
+ */
101
+ reset() {
102
+ this.settings = { ...this.defaults };
103
+ this.save();
104
+ return this.settings;
105
+ }
106
+
107
+ /**
108
+ * Configure auto-start on Windows boot
109
+ */
110
+ async setAutoStart(enabled) {
111
+ const { exec } = require('child_process');
112
+ const { promisify } = require('util');
113
+ const execAsync = promisify(exec);
114
+
115
+ try {
116
+ const appPath = process.execPath;
117
+ const startupFolder = path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
118
+ const shortcutPath = path.join(startupFolder, 'Mani-Calc.lnk');
119
+
120
+ if (enabled) {
121
+ // Create shortcut using PowerShell
122
+ const psCommand = `
123
+ $WshShell = New-Object -comObject WScript.Shell
124
+ $Shortcut = $WshShell.CreateShortcut("${shortcutPath.replace(/\\/g, '\\\\')}")
125
+ $Shortcut.TargetPath = "npx"
126
+ $Shortcut.Arguments = "electron ${path.join(__dirname, '..', 'ui', 'main-electron.js').replace(/\\/g, '\\\\')}"
127
+ $Shortcut.WorkingDirectory = "${path.join(__dirname, '..', '..').replace(/\\/g, '\\\\')}"
128
+ $Shortcut.Description = "Mani-Calc Overlay"
129
+ $Shortcut.Save()
130
+ `;
131
+ await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`);
132
+ this.set('autoStart', true);
133
+ return { success: true, message: 'Auto-start enabled! Mani-Calc will start with Windows.' };
134
+ } else {
135
+ // Remove shortcut
136
+ if (fs.existsSync(shortcutPath)) {
137
+ fs.unlinkSync(shortcutPath);
138
+ }
139
+ this.set('autoStart', false);
140
+ return { success: true, message: 'Auto-start disabled.' };
141
+ }
142
+ } catch (error) {
143
+ return { success: false, message: `Failed: ${error.message}` };
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Get available themes
149
+ */
150
+ getThemes() {
151
+ return [
152
+ { id: 'dark', name: 'Dark Mode', colors: { bg: '#1e1e1e', accent: '#00D9FF' } },
153
+ { id: 'light', name: 'Light Mode', colors: { bg: '#ffffff', accent: '#0066cc' } },
154
+ { id: 'midnight', name: 'Midnight Blue', colors: { bg: '#0a1628', accent: '#4a9eff' } },
155
+ { id: 'forest', name: 'Forest Green', colors: { bg: '#1a2f1a', accent: '#4caf50' } },
156
+ { id: 'sunset', name: 'Sunset Orange', colors: { bg: '#2d1a1a', accent: '#ff6b35' } },
157
+ { id: 'purple', name: 'Royal Purple', colors: { bg: '#1a1a2e', accent: '#9c27b0' } },
158
+ { id: 'neon', name: 'Neon Glow', colors: { bg: '#0d0d0d', accent: '#39ff14' } },
159
+ { id: 'ocean', name: 'Deep Ocean', colors: { bg: '#0a1929', accent: '#00bcd4' } }
160
+ ];
161
+ }
162
+
163
+ /**
164
+ * Set theme
165
+ */
166
+ setTheme(themeId) {
167
+ const themes = this.getThemes();
168
+ const theme = themes.find(t => t.id === themeId);
169
+
170
+ if (theme) {
171
+ this.set('theme', themeId);
172
+ return theme;
173
+ }
174
+
175
+ return null;
176
+ }
177
+ }
178
+
179
+ module.exports = SettingsManager;
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Utilities Module
3
+ * Password Generator, Random Generator, Text Utils, Emoji Search, Encoding
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+
8
+ class UtilitiesModule {
9
+ constructor() {
10
+ // Emoji database
11
+ this.emojis = {
12
+ happy: ['😀', '😃', '😄', '😁', '😆', '😊', '🙂', '😇', '🥰', '😍'],
13
+ sad: ['😢', '😭', '😿', '😞', '😔', '🥺', '😥', '😰', '💔'],
14
+ love: ['❤️', '💕', '💖', '💗', '💘', '💝', '💞', '💓', '💟', '🥰', '😍', '😘'],
15
+ heart: ['❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '💕', '💖'],
16
+ angry: ['😠', '😡', '🤬', '👿', '💢', '😤', '🔥'],
17
+ laugh: ['😂', '🤣', '😆', '😹', '😁', '🤭', '😸'],
18
+ cool: ['😎', '🆒', '🤙', '👊', '✌️', '🕶️', '🧊'],
19
+ fire: ['🔥', '💥', '⚡', '✨', '💫', '🌟', '⭐'],
20
+ music: ['🎵', '🎶', '🎼', '🎹', '🎸', '🎺', '🥁', '🎷', '🎻'],
21
+ food: ['🍕', '🍔', '🍟', '🌭', '🍿', '🥗', '🍣', '🍜', '🍩', '🍪'],
22
+ drink: ['☕', '🍵', '🧃', '🥤', '🍺', '🍷', '🍸', '🧋', '🥛'],
23
+ animal: ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🦁'],
24
+ nature: ['🌸', '🌺', '🌻', '🌹', '🌷', '🌱', '🌲', '🌳', '🍀', '🌈'],
25
+ weather: ['☀️', '🌤️', '⛅', '🌧️', '⛈️', '🌩️', '❄️', '🌨️', '🌪️', '🌈'],
26
+ sport: ['⚽', '🏀', '🏈', '⚾', '🎾', '🏐', '🏉', '🎱', '🏓', '🏸'],
27
+ travel: ['✈️', '🚗', '🚕', '🚌', '🚂', '🚀', '🛸', '🚁', '⛵', '🗺️'],
28
+ work: ['💼', '📊', '📈', '💻', '🖥️', '⌨️', '🖱️', '📱', '📧', '📝'],
29
+ money: ['💰', '💵', '💴', '💶', '💷', '💎', '🏦', '💳', '🪙', '📈'],
30
+ celebrate: ['🎉', '🎊', '🥳', '🎈', '🎁', '🎂', '🍾', '🥂', '🎆', '🎇'],
31
+ thumbs: ['👍', '👎', '👊', '✊', '🤛', '🤜', '👏', '🙌', '🤝', '✋'],
32
+ hand: ['👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞'],
33
+ face: ['🙂', '😐', '🙄', '😏', '😬', '🤔', '🤨', '😑', '😶', '🫥'],
34
+ star: ['⭐', '🌟', '✨', '💫', '🌠', '⚡', '🔥', '💥', '✴️', '❇️'],
35
+ check: ['✅', '✓', '☑️', '✔️', '👍', '👌', '🆗', '💯'],
36
+ cross: ['❌', '✖️', '❎', '🚫', '⛔', '🔴', '👎'],
37
+ time: ['⏰', '⏱️', '⏲️', '🕐', '🕑', '🕒', '🕓', '🕔', '📅', '📆'],
38
+ arrow: ['➡️', '⬅️', '⬆️', '⬇️', '↗️', '↘️', '↙️', '↖️', '🔄', '🔃']
39
+ };
40
+
41
+ // Notes storage
42
+ this.notes = [];
43
+ }
44
+
45
+ /**
46
+ * Generate secure password
47
+ */
48
+ generatePassword(length = 12) {
49
+ const lowercase = 'abcdefghijklmnopqrstuvwxyz';
50
+ const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
51
+ const numbers = '0123456789';
52
+ const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
53
+ const allChars = lowercase + uppercase + numbers + symbols;
54
+
55
+ let password = '';
56
+
57
+ // Ensure at least one of each type
58
+ password += lowercase[Math.floor(Math.random() * lowercase.length)];
59
+ password += uppercase[Math.floor(Math.random() * uppercase.length)];
60
+ password += numbers[Math.floor(Math.random() * numbers.length)];
61
+ password += symbols[Math.floor(Math.random() * symbols.length)];
62
+
63
+ // Fill the rest
64
+ for (let i = password.length; i < length; i++) {
65
+ password += allChars[Math.floor(Math.random() * allChars.length)];
66
+ }
67
+
68
+ // Shuffle the password
69
+ return password.split('').sort(() => Math.random() - 0.5).join('');
70
+ }
71
+
72
+ /**
73
+ * Generate random number
74
+ */
75
+ randomNumber(min = 1, max = 100) {
76
+ return Math.floor(Math.random() * (max - min + 1)) + min;
77
+ }
78
+
79
+ /**
80
+ * Roll dice
81
+ */
82
+ rollDice(sides = 6) {
83
+ return Math.floor(Math.random() * sides) + 1;
84
+ }
85
+
86
+ /**
87
+ * Flip coin
88
+ */
89
+ flipCoin() {
90
+ return Math.random() < 0.5 ? 'Heads' : 'Tails';
91
+ }
92
+
93
+ /**
94
+ * Generate UUID
95
+ */
96
+ generateUUID() {
97
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
98
+ const r = Math.random() * 16 | 0;
99
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
100
+ return v.toString(16);
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Convert color formats
106
+ */
107
+ convertColor(input) {
108
+ const hexMatch = input.match(/^#?([0-9a-f]{6})$/i);
109
+ if (hexMatch) {
110
+ const hex = hexMatch[1];
111
+ const r = parseInt(hex.substr(0, 2), 16);
112
+ const g = parseInt(hex.substr(2, 2), 16);
113
+ const b = parseInt(hex.substr(4, 2), 16);
114
+ return {
115
+ hex: `#${hex.toUpperCase()}`,
116
+ rgb: `rgb(${r}, ${g}, ${b})`,
117
+ hsl: this.rgbToHsl(r, g, b)
118
+ };
119
+ }
120
+
121
+ const rgbMatch = input.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
122
+ if (rgbMatch) {
123
+ const r = parseInt(rgbMatch[1]);
124
+ const g = parseInt(rgbMatch[2]);
125
+ const b = parseInt(rgbMatch[3]);
126
+ const hex = '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('').toUpperCase();
127
+ return {
128
+ hex: hex,
129
+ rgb: `rgb(${r}, ${g}, ${b})`,
130
+ hsl: this.rgbToHsl(r, g, b)
131
+ };
132
+ }
133
+
134
+ return null;
135
+ }
136
+
137
+ rgbToHsl(r, g, b) {
138
+ r /= 255; g /= 255; b /= 255;
139
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
140
+ let h, s, l = (max + min) / 2;
141
+
142
+ if (max === min) {
143
+ h = s = 0;
144
+ } else {
145
+ const d = max - min;
146
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
147
+ switch (max) {
148
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
149
+ case g: h = ((b - r) / d + 2) / 6; break;
150
+ case b: h = ((r - g) / d + 4) / 6; break;
151
+ }
152
+ }
153
+
154
+ return `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
155
+ }
156
+
157
+ /**
158
+ * Text utilities
159
+ */
160
+ textUtils(operation, text) {
161
+ switch (operation) {
162
+ case 'upper':
163
+ return text.toUpperCase();
164
+ case 'lower':
165
+ return text.toLowerCase();
166
+ case 'reverse':
167
+ return text.split('').reverse().join('');
168
+ case 'count':
169
+ const chars = text.length;
170
+ const words = text.trim() ? text.trim().split(/\s+/).length : 0;
171
+ const lines = text.split('\n').length;
172
+ return { chars, words, lines };
173
+ case 'capitalize':
174
+ return text.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
175
+ case 'camelcase':
176
+ return text.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
177
+ case 'snakecase':
178
+ return text.toLowerCase().replace(/\s+/g, '_');
179
+ case 'kebabcase':
180
+ return text.toLowerCase().replace(/\s+/g, '-');
181
+ default:
182
+ return text;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Base64 encoding/decoding
188
+ */
189
+ base64Encode(text) {
190
+ return Buffer.from(text).toString('base64');
191
+ }
192
+
193
+ base64Decode(encoded) {
194
+ return Buffer.from(encoded, 'base64').toString('utf8');
195
+ }
196
+
197
+ /**
198
+ * Generate hash
199
+ */
200
+ generateHash(algorithm, text) {
201
+ return crypto.createHash(algorithm).update(text).digest('hex');
202
+ }
203
+
204
+ /**
205
+ * Search emojis
206
+ */
207
+ searchEmoji(keyword) {
208
+ const key = keyword.toLowerCase();
209
+ if (this.emojis[key]) {
210
+ return this.emojis[key];
211
+ }
212
+
213
+ // Search in all categories
214
+ const results = [];
215
+ for (const [category, emojis] of Object.entries(this.emojis)) {
216
+ if (category.includes(key)) {
217
+ results.push(...emojis);
218
+ }
219
+ }
220
+
221
+ return results.length > 0 ? results.slice(0, 10) : ['No emojis found'];
222
+ }
223
+
224
+ /**
225
+ * Get web search URL
226
+ */
227
+ getSearchUrl(engine, query) {
228
+ const engines = {
229
+ google: `https://www.google.com/search?q=${encodeURIComponent(query)}`,
230
+ youtube: `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`,
231
+ bing: `https://www.bing.com/search?q=${encodeURIComponent(query)}`,
232
+ duckduckgo: `https://duckduckgo.com/?q=${encodeURIComponent(query)}`,
233
+ wiki: `https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(query)}`,
234
+ wikipedia: `https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(query)}`,
235
+ github: `https://github.com/search?q=${encodeURIComponent(query)}`,
236
+ stackoverflow: `https://stackoverflow.com/search?q=${encodeURIComponent(query)}`,
237
+ amazon: `https://www.amazon.com/s?k=${encodeURIComponent(query)}`,
238
+ twitter: `https://twitter.com/search?q=${encodeURIComponent(query)}`,
239
+ reddit: `https://www.reddit.com/search/?q=${encodeURIComponent(query)}`,
240
+ maps: `https://www.google.com/maps/search/${encodeURIComponent(query)}`
241
+ };
242
+
243
+ return engines[engine.toLowerCase()] || engines.google;
244
+ }
245
+
246
+ /**
247
+ * Parse query and execute utility function
248
+ */
249
+ parse(query) {
250
+ const q = query.toLowerCase().trim();
251
+
252
+ // Password generator: "password", "password 16"
253
+ const passMatch = q.match(/^password(?:\s+(\d+))?$/);
254
+ if (passMatch) {
255
+ const length = passMatch[1] ? parseInt(passMatch[1]) : 12;
256
+ const password = this.generatePassword(Math.min(Math.max(length, 8), 64));
257
+ return {
258
+ result: password,
259
+ formatted: `🔐 Password: ${password}`,
260
+ type: 'password'
261
+ };
262
+ }
263
+
264
+ // Random number: "random", "random 1 100"
265
+ const randomMatch = q.match(/^random(?:\s+(\d+)\s+(\d+))?$/);
266
+ if (randomMatch) {
267
+ const min = randomMatch[1] ? parseInt(randomMatch[1]) : 1;
268
+ const max = randomMatch[2] ? parseInt(randomMatch[2]) : 100;
269
+ const num = this.randomNumber(min, max);
270
+ return {
271
+ result: num,
272
+ formatted: `🎲 Random (${min}-${max}): ${num}`,
273
+ type: 'random'
274
+ };
275
+ }
276
+
277
+ // Dice roll: "dice", "roll", "d6", "d20"
278
+ const diceMatch = q.match(/^(?:dice|roll|d(\d+))$/);
279
+ if (diceMatch) {
280
+ const sides = diceMatch[1] ? parseInt(diceMatch[1]) : 6;
281
+ const result = this.rollDice(sides);
282
+ return {
283
+ result: result,
284
+ formatted: `🎲 Dice (d${sides}): ${result}`,
285
+ type: 'dice'
286
+ };
287
+ }
288
+
289
+ // Coin flip: "coin", "flip"
290
+ if (q === 'coin' || q === 'flip' || q === 'flip coin') {
291
+ const result = this.flipCoin();
292
+ const emoji = result === 'Heads' ? '🪙' : '🔴';
293
+ return {
294
+ result: result,
295
+ formatted: `${emoji} Coin Flip: ${result}`,
296
+ type: 'coin'
297
+ };
298
+ }
299
+
300
+ // UUID: "uuid"
301
+ if (q === 'uuid' || q === 'guid') {
302
+ const uuid = this.generateUUID();
303
+ return {
304
+ result: uuid,
305
+ formatted: `🔑 UUID: ${uuid}`,
306
+ type: 'uuid'
307
+ };
308
+ }
309
+
310
+ // Color conversion: "#FF5733 to rgb", "rgb(255,0,0) to hex"
311
+ const colorMatch = query.match(/^(#?[0-9a-f]{6}|rgb\s*\([^)]+\))\s*(?:to\s+(\w+))?$/i);
312
+ if (colorMatch) {
313
+ const color = this.convertColor(colorMatch[1]);
314
+ if (color) {
315
+ const format = colorMatch[2]?.toLowerCase();
316
+ let result = format ? color[format] || color.hex : `${color.hex} | ${color.rgb} | ${color.hsl}`;
317
+ return {
318
+ result: result,
319
+ formatted: `🎨 Color: ${result}`,
320
+ type: 'color'
321
+ };
322
+ }
323
+ }
324
+
325
+ // Text utilities: "upper hello", "lower HELLO", "reverse hello"
326
+ const textMatch = q.match(/^(upper|lower|reverse|capitalize|camelcase|snakecase|kebabcase)\s+(.+)$/);
327
+ if (textMatch) {
328
+ const result = this.textUtils(textMatch[1], textMatch[2]);
329
+ return {
330
+ result: result,
331
+ formatted: `📝 ${textMatch[1]}: ${result}`,
332
+ type: 'text'
333
+ };
334
+ }
335
+
336
+ // Count: "count hello world"
337
+ const countMatch = q.match(/^count\s+(.+)$/);
338
+ if (countMatch) {
339
+ const stats = this.textUtils('count', countMatch[1]);
340
+ return {
341
+ result: stats,
342
+ formatted: `📊 Count: ${stats.chars} chars, ${stats.words} words`,
343
+ type: 'count'
344
+ };
345
+ }
346
+
347
+ // Base64 encode/decode
348
+ const base64Match = query.match(/^base64\s+(encode|decode)\s+(.+)$/i);
349
+ if (base64Match) {
350
+ const operation = base64Match[1].toLowerCase();
351
+ const text = base64Match[2];
352
+ const result = operation === 'encode' ? this.base64Encode(text) : this.base64Decode(text);
353
+ return {
354
+ result: result,
355
+ formatted: `🔤 Base64 ${operation}: ${result}`,
356
+ type: 'base64'
357
+ };
358
+ }
359
+
360
+ // Hash: "md5 hello", "sha256 hello"
361
+ const hashMatch = q.match(/^(md5|sha1|sha256|sha512)\s+(.+)$/);
362
+ if (hashMatch) {
363
+ const hash = this.generateHash(hashMatch[1], hashMatch[2]);
364
+ return {
365
+ result: hash,
366
+ formatted: `🔒 ${hashMatch[1].toUpperCase()}: ${hash}`,
367
+ type: 'hash'
368
+ };
369
+ }
370
+
371
+ // Emoji search: "emoji happy", "emoji heart"
372
+ const emojiMatch = q.match(/^emoji\s+(.+)$/);
373
+ if (emojiMatch) {
374
+ const emojis = this.searchEmoji(emojiMatch[1]);
375
+ return {
376
+ result: emojis.join(' '),
377
+ formatted: `😀 Emojis: ${emojis.join(' ')}`,
378
+ type: 'emoji'
379
+ };
380
+ }
381
+
382
+ // Web search: "google AI", "youtube music", "wiki einstein"
383
+ const searchEngines = ['google', 'youtube', 'bing', 'duckduckgo', 'wiki', 'wikipedia', 'github', 'stackoverflow', 'amazon', 'twitter', 'reddit', 'maps'];
384
+ for (const engine of searchEngines) {
385
+ if (q.startsWith(engine + ' ')) {
386
+ const searchQuery = query.slice(engine.length + 1);
387
+ const url = this.getSearchUrl(engine, searchQuery);
388
+ return {
389
+ result: url,
390
+ formatted: `🔍 Search ${engine}: ${searchQuery}`,
391
+ type: 'search',
392
+ url: url,
393
+ engine: engine
394
+ };
395
+ }
396
+ }
397
+
398
+ return null;
399
+ }
400
+ }
401
+
402
+ module.exports = UtilitiesModule;