vibesurf 0.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of vibesurf might be problematic. Click here for more details.

Files changed (70) hide show
  1. vibe_surf/__init__.py +12 -0
  2. vibe_surf/_version.py +34 -0
  3. vibe_surf/agents/__init__.py +0 -0
  4. vibe_surf/agents/browser_use_agent.py +1106 -0
  5. vibe_surf/agents/prompts/__init__.py +1 -0
  6. vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
  7. vibe_surf/agents/report_writer_agent.py +360 -0
  8. vibe_surf/agents/vibe_surf_agent.py +1632 -0
  9. vibe_surf/backend/__init__.py +0 -0
  10. vibe_surf/backend/api/__init__.py +3 -0
  11. vibe_surf/backend/api/activity.py +243 -0
  12. vibe_surf/backend/api/config.py +740 -0
  13. vibe_surf/backend/api/files.py +322 -0
  14. vibe_surf/backend/api/models.py +257 -0
  15. vibe_surf/backend/api/task.py +300 -0
  16. vibe_surf/backend/database/__init__.py +13 -0
  17. vibe_surf/backend/database/manager.py +129 -0
  18. vibe_surf/backend/database/models.py +164 -0
  19. vibe_surf/backend/database/queries.py +922 -0
  20. vibe_surf/backend/database/schemas.py +100 -0
  21. vibe_surf/backend/llm_config.py +182 -0
  22. vibe_surf/backend/main.py +137 -0
  23. vibe_surf/backend/migrations/__init__.py +16 -0
  24. vibe_surf/backend/migrations/init_db.py +303 -0
  25. vibe_surf/backend/migrations/seed_data.py +236 -0
  26. vibe_surf/backend/shared_state.py +601 -0
  27. vibe_surf/backend/utils/__init__.py +7 -0
  28. vibe_surf/backend/utils/encryption.py +164 -0
  29. vibe_surf/backend/utils/llm_factory.py +225 -0
  30. vibe_surf/browser/__init__.py +8 -0
  31. vibe_surf/browser/agen_browser_profile.py +130 -0
  32. vibe_surf/browser/agent_browser_session.py +416 -0
  33. vibe_surf/browser/browser_manager.py +296 -0
  34. vibe_surf/browser/utils.py +790 -0
  35. vibe_surf/browser/watchdogs/__init__.py +0 -0
  36. vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
  37. vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
  38. vibe_surf/chrome_extension/background.js +558 -0
  39. vibe_surf/chrome_extension/config.js +48 -0
  40. vibe_surf/chrome_extension/content.js +284 -0
  41. vibe_surf/chrome_extension/dev-reload.js +47 -0
  42. vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
  43. vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
  44. vibe_surf/chrome_extension/icons/logo.png +0 -0
  45. vibe_surf/chrome_extension/manifest.json +53 -0
  46. vibe_surf/chrome_extension/popup.html +134 -0
  47. vibe_surf/chrome_extension/scripts/api-client.js +473 -0
  48. vibe_surf/chrome_extension/scripts/main.js +491 -0
  49. vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
  50. vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
  51. vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
  52. vibe_surf/chrome_extension/sidepanel.html +347 -0
  53. vibe_surf/chrome_extension/styles/animations.css +471 -0
  54. vibe_surf/chrome_extension/styles/components.css +670 -0
  55. vibe_surf/chrome_extension/styles/main.css +2307 -0
  56. vibe_surf/chrome_extension/styles/settings.css +1100 -0
  57. vibe_surf/cli.py +357 -0
  58. vibe_surf/controller/__init__.py +0 -0
  59. vibe_surf/controller/file_system.py +53 -0
  60. vibe_surf/controller/mcp_client.py +68 -0
  61. vibe_surf/controller/vibesurf_controller.py +616 -0
  62. vibe_surf/controller/views.py +37 -0
  63. vibe_surf/llm/__init__.py +21 -0
  64. vibe_surf/llm/openai_compatible.py +237 -0
  65. vibesurf-0.1.0.dist-info/METADATA +97 -0
  66. vibesurf-0.1.0.dist-info/RECORD +70 -0
  67. vibesurf-0.1.0.dist-info/WHEEL +5 -0
  68. vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
  69. vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
  70. vibesurf-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,558 @@
1
+ // Background Script - VibeSurf Extension
2
+ // Handles extension lifecycle, side panel management, and cross-context communication
3
+
4
+ // Import configuration using importScripts for service worker
5
+ try {
6
+ importScripts('config.js');
7
+ console.log('[VibeSurf] Configuration loaded');
8
+ } catch (error) {
9
+ console.error('[VibeSurf] Failed to load configuration:', error);
10
+ }
11
+
12
+ class VibeSurfBackground {
13
+ constructor() {
14
+ this.isInitialized = false;
15
+ this.setupEventListeners();
16
+ this.initDevMode();
17
+ }
18
+
19
+ initDevMode() {
20
+ // Enable auto-reload in development mode
21
+ try {
22
+ const manifest = chrome.runtime.getManifest();
23
+ const isDevelopment = !('update_url' in manifest);
24
+
25
+ if (isDevelopment) {
26
+ // Simple reload check every 3 seconds
27
+ setInterval(() => {
28
+ fetch(chrome.runtime.getURL('manifest.json'))
29
+ .then(() => {
30
+ // File accessible, extension is working
31
+ })
32
+ .catch(() => {
33
+ // If we can't access our own files, extension might need reload
34
+ });
35
+ }, 3000);
36
+ }
37
+ } catch (error) {
38
+ // Ignore errors in dev mode setup
39
+ }
40
+ }
41
+
42
+ setupEventListeners() {
43
+ // Extension installation and startup
44
+ chrome.runtime.onInstalled.addListener(this.handleInstalled.bind(this));
45
+ chrome.runtime.onStartup.addListener(this.handleStartup.bind(this));
46
+
47
+ // Action button click (toolbar icon)
48
+ chrome.action.onClicked.addListener(this.handleActionClick.bind(this));
49
+
50
+ // Context menu setup (backup method)
51
+ chrome.runtime.onInstalled.addListener(() => {
52
+ chrome.contextMenus.create({
53
+ id: 'open-vibesurf',
54
+ title: 'Open VibeSurf Panel',
55
+ contexts: ['action']
56
+ });
57
+ });
58
+
59
+ chrome.contextMenus.onClicked.addListener((info, tab) => {
60
+ if (info.menuItemId === 'open-vibesurf') {
61
+ this.handleActionClick(tab);
62
+ }
63
+ });
64
+
65
+ // Message handling between contexts
66
+ chrome.runtime.onMessage.addListener(this.handleMessage.bind(this));
67
+
68
+ // Tab updates for context awareness
69
+ chrome.tabs.onActivated.addListener(this.handleTabActivated.bind(this));
70
+ chrome.tabs.onUpdated.addListener(this.handleTabUpdated.bind(this));
71
+
72
+ }
73
+
74
+ async handleInstalled(details) {
75
+
76
+ try {
77
+ // Check Chrome version and API availability
78
+
79
+ // Initialize default settings
80
+ await this.initializeSettings();
81
+
82
+ // Set default badge
83
+ await chrome.action.setBadgeText({ text: '' });
84
+ await chrome.action.setBadgeBackgroundColor({ color: '#007acc' });
85
+
86
+ // Show welcome notification on fresh install
87
+ if (details.reason === 'install') {
88
+ await this.showWelcomeNotification();
89
+ }
90
+
91
+ this.isInitialized = true;
92
+ } catch (error) {
93
+ console.error('[VibeSurf] Initialization failed:', error);
94
+ }
95
+ }
96
+
97
+ async handleStartup() {
98
+ await this.initializeSettings();
99
+ this.isInitialized = true;
100
+ }
101
+
102
+ async handleActionClick(tab) {
103
+
104
+ try {
105
+ // Check if sidePanel API is available
106
+ if (chrome.sidePanel && chrome.sidePanel.open) {
107
+ // Open side panel for the current tab
108
+ await chrome.sidePanel.open({ tabId: tab.id });
109
+
110
+ // Update badge to indicate active state
111
+ await chrome.action.setBadgeText({ text: '●', tabId: tab.id });
112
+ await chrome.action.setBadgeBackgroundColor({ color: '#007acc', tabId: tab.id });
113
+
114
+ } else {
115
+
116
+ // Use test panel first
117
+ await chrome.tabs.create({
118
+ url: chrome.runtime.getURL('test-panel.html'),
119
+ index: tab.index + 1
120
+ });
121
+
122
+ }
123
+
124
+ // Store current tab info for context
125
+ await chrome.storage.local.set({
126
+ currentTab: {
127
+ id: tab.id,
128
+ url: tab.url,
129
+ title: tab.title,
130
+ timestamp: Date.now()
131
+ }
132
+ });
133
+
134
+ } catch (error) {
135
+ console.error('[VibeSurf] Failed to open side panel:', error);
136
+
137
+ // Show notification with helpful message
138
+ await chrome.notifications.create({
139
+ type: 'basic',
140
+ iconUrl: chrome.runtime.getURL('icons/icon48.png') || '',
141
+ title: 'VibeSurf',
142
+ message: 'Side panel failed. Please update Chrome to the latest version or try right-clicking the extension icon.'
143
+ });
144
+
145
+ // Fallback: try to open in new tab
146
+ try {
147
+ await chrome.tabs.create({
148
+ url: chrome.runtime.getURL('sidepanel.html'),
149
+ index: tab.index + 1
150
+ });
151
+ } catch (fallbackError) {
152
+ console.error('[VibeSurf] Fallback also failed:', fallbackError);
153
+ }
154
+ }
155
+ }
156
+
157
+ handleMessage(message, sender, sendResponse) {
158
+
159
+ // Handle async messages properly
160
+ (async () => {
161
+ try {
162
+ let result;
163
+
164
+ switch (message.type) {
165
+ case 'GET_CURRENT_TAB':
166
+ result = await this.getCurrentTabInfo();
167
+ break;
168
+
169
+ case 'UPDATE_BADGE':
170
+ result = await this.updateBadge(message.data);
171
+ break;
172
+
173
+ case 'SHOW_NOTIFICATION':
174
+ result = await this.showNotification(message.data);
175
+ break;
176
+
177
+ case 'HEALTH_CHECK':
178
+ result = { status: 'healthy', timestamp: Date.now() };
179
+ break;
180
+
181
+ case 'GET_BACKEND_STATUS':
182
+ result = await this.checkBackendStatus(message.data?.backendUrl);
183
+ break;
184
+
185
+ case 'STORE_SESSION_DATA':
186
+ result = await this.storeSessionData(message.data);
187
+ break;
188
+
189
+ case 'GET_SESSION_DATA':
190
+ result = await this.getSessionData(message.data?.sessionId);
191
+ break;
192
+
193
+ case 'OPEN_FILE_URL':
194
+ result = await this.openFileUrl(message.data?.fileUrl);
195
+ break;
196
+
197
+ case 'OPEN_FILE_SYSTEM':
198
+ result = await this.openFileSystem(message.data?.filePath);
199
+ break;
200
+
201
+ default:
202
+ console.warn('[VibeSurf] Unknown message type:', message.type);
203
+ result = { error: 'Unknown message type' };
204
+ }
205
+
206
+ sendResponse(result);
207
+
208
+ } catch (error) {
209
+ console.error('[VibeSurf] Message handling error:', error);
210
+ sendResponse({ error: error.message });
211
+ }
212
+ })();
213
+
214
+ // Return true to indicate async response
215
+ return true;
216
+ }
217
+
218
+ async handleTabActivated(activeInfo) {
219
+ // Update current tab context when user switches tabs
220
+ const tab = await chrome.tabs.get(activeInfo.tabId);
221
+
222
+ await chrome.storage.local.set({
223
+ currentTab: {
224
+ id: tab.id,
225
+ url: tab.url,
226
+ title: tab.title,
227
+ timestamp: Date.now()
228
+ }
229
+ });
230
+ }
231
+
232
+ async handleTabUpdated(tabId, changeInfo, tab) {
233
+ // Update context when tab URL changes
234
+ if (changeInfo.url) {
235
+ const { currentTab } = await chrome.storage.local.get('currentTab');
236
+
237
+ if (currentTab && currentTab.id === tabId) {
238
+ await chrome.storage.local.set({
239
+ currentTab: {
240
+ ...currentTab,
241
+ url: tab.url,
242
+ title: tab.title,
243
+ timestamp: Date.now()
244
+ }
245
+ });
246
+ }
247
+ }
248
+ }
249
+
250
+ async initializeSettings() {
251
+ // Load configuration (use self instead of window in service worker)
252
+ const config = self.VIBESURF_CONFIG || {};
253
+
254
+ const defaultSettings = {
255
+ backendUrl: config.BACKEND_URL || 'http://localhost:9335',
256
+ defaultSessionPrefix: config.DEFAULT_SESSION_PREFIX || 'vibesurf_',
257
+ notifications: config.NOTIFICATIONS || {
258
+ enabled: true,
259
+ taskComplete: true,
260
+ taskError: true
261
+ },
262
+ ui: config.UI || {
263
+ theme: 'auto',
264
+ autoScroll: true,
265
+ compactMode: false
266
+ },
267
+ debug: config.DEBUG || false
268
+ };
269
+
270
+ const { settings } = await chrome.storage.local.get('settings');
271
+
272
+ if (!settings) {
273
+ await chrome.storage.local.set({ settings: defaultSettings });
274
+ } else {
275
+ // Merge with defaults for any missing keys
276
+ const mergedSettings = { ...defaultSettings, ...settings };
277
+ await chrome.storage.local.set({ settings: mergedSettings });
278
+ }
279
+ }
280
+
281
+ async getCurrentTabInfo() {
282
+ try {
283
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
284
+ return {
285
+ id: tab.id,
286
+ url: tab.url,
287
+ title: tab.title,
288
+ favIconUrl: tab.favIconUrl
289
+ };
290
+ } catch (error) {
291
+ console.error('[VibeSurf] Failed to get current tab:', error);
292
+ return null;
293
+ }
294
+ }
295
+
296
+ async updateBadge(data) {
297
+ const { text, color, tabId } = data;
298
+
299
+ if (text !== undefined) {
300
+ await chrome.action.setBadgeText({ text, tabId });
301
+ }
302
+
303
+ if (color) {
304
+ await chrome.action.setBadgeBackgroundColor({ color, tabId });
305
+ }
306
+
307
+ return { success: true };
308
+ }
309
+
310
+ async showNotification(data) {
311
+ const { title, message, type = 'info', iconUrl = 'icons/icon48.png' } = data;
312
+
313
+ // Map custom types to valid Chrome notification types
314
+ const validType = ['basic', 'image', 'list', 'progress'].includes(type) ? type : 'basic';
315
+
316
+ const notificationId = await chrome.notifications.create({
317
+ type: validType,
318
+ iconUrl,
319
+ title: title || 'VibeSurf',
320
+ message
321
+ });
322
+
323
+ return { notificationId };
324
+ }
325
+
326
+ async showWelcomeNotification() {
327
+ await chrome.notifications.create({
328
+ type: 'basic',
329
+ iconUrl: 'icons/icon48.png',
330
+ title: 'Welcome to VibeSurf!',
331
+ message: 'Click the VibeSurf icon in the toolbar to start automating your browsing tasks.'
332
+ });
333
+ }
334
+
335
+ async checkBackendStatus(backendUrl = null) {
336
+ // Use configuration file value as default (use self instead of window in service worker)
337
+ const config = self.VIBESURF_CONFIG || {};
338
+ backendUrl = backendUrl || config.BACKEND_URL || 'http://localhost:9335';
339
+ try {
340
+ const response = await fetch(`${backendUrl}/health`, {
341
+ method: 'GET',
342
+ headers: {
343
+ 'Content-Type': 'application/json',
344
+ },
345
+ signal: AbortSignal.timeout(5000) // 5 second timeout
346
+ });
347
+
348
+ if (response.ok) {
349
+ const data = await response.json();
350
+ return {
351
+ status: 'connected',
352
+ backend: data,
353
+ timestamp: Date.now()
354
+ };
355
+ } else {
356
+ return {
357
+ status: 'error',
358
+ error: `HTTP ${response.status}`,
359
+ timestamp: Date.now()
360
+ };
361
+ }
362
+ } catch (error) {
363
+ console.error('[VibeSurf] Backend health check failed:', error);
364
+ return {
365
+ status: 'disconnected',
366
+ error: error.message,
367
+ timestamp: Date.now()
368
+ };
369
+ }
370
+ }
371
+
372
+ async storeSessionData(data) {
373
+ const { sessionId, ...sessionData } = data;
374
+ const key = `session_${sessionId}`;
375
+
376
+ // Store session data
377
+ await chrome.storage.local.set({
378
+ [key]: {
379
+ ...sessionData,
380
+ lastUpdated: Date.now()
381
+ }
382
+ });
383
+
384
+ // Update sessions list
385
+ const { sessionsList = [] } = await chrome.storage.local.get('sessionsList');
386
+
387
+ if (!sessionsList.includes(sessionId)) {
388
+ sessionsList.unshift(sessionId); // Add to beginning
389
+
390
+ // Keep only last 50 sessions
391
+ if (sessionsList.length > 50) {
392
+ const removedSessions = sessionsList.splice(50);
393
+
394
+ // Clean up old session data
395
+ const keysToRemove = removedSessions.map(id => `session_${id}`);
396
+ await chrome.storage.local.remove(keysToRemove);
397
+ }
398
+
399
+ await chrome.storage.local.set({ sessionsList });
400
+ }
401
+
402
+ return { success: true };
403
+ }
404
+
405
+ async getSessionData(sessionId) {
406
+ try {
407
+ if (!sessionId) {
408
+ // Return all sessions
409
+ const { sessionsList = [] } = await chrome.storage.local.get('sessionsList');
410
+ const sessionKeys = sessionsList.map(id => `session_${id}`);
411
+
412
+ if (sessionKeys.length === 0) {
413
+ console.log('[VibeSurf] No sessions found in storage');
414
+ return { sessions: [] };
415
+ }
416
+
417
+ const sessionsData = await chrome.storage.local.get(sessionKeys);
418
+ const sessions = sessionsList
419
+ .map(id => {
420
+ const data = sessionsData[`session_${id}`];
421
+ if (data) {
422
+ return {
423
+ sessionId: id,
424
+ ...data
425
+ };
426
+ }
427
+ return null;
428
+ })
429
+ .filter(session => session !== null); // Remove null entries
430
+
431
+ return { sessions };
432
+ } else {
433
+ // Return specific session
434
+ const { [`session_${sessionId}`]: sessionData } = await chrome.storage.local.get(`session_${sessionId}`);
435
+
436
+ if (sessionData) {
437
+ return { sessionData };
438
+ } else {
439
+ return { sessionData: null, error: 'Session not found in storage' };
440
+ }
441
+ }
442
+ } catch (error) {
443
+ console.error('[VibeSurf] Error retrieving session data:', error);
444
+ return { sessionData: null, error: error.message };
445
+ }
446
+ }
447
+
448
+ async openFileUrl(fileUrl) {
449
+ if (!fileUrl) {
450
+ return { success: false, error: 'No file URL provided' };
451
+ }
452
+
453
+ try {
454
+
455
+ // Try to create a new tab with the file URL
456
+ const tab = await chrome.tabs.create({
457
+ url: fileUrl,
458
+ active: true
459
+ });
460
+
461
+ if (tab && tab.id) {
462
+ return { success: true, tabId: tab.id };
463
+ } else {
464
+ return { success: false, error: 'Failed to create tab' };
465
+ }
466
+
467
+ } catch (error) {
468
+ console.error('[VibeSurf] Error opening file URL:', error);
469
+ return {
470
+ success: false,
471
+ error: error.message || 'Unknown error opening file'
472
+ };
473
+ }
474
+ }
475
+
476
+ async openFileSystem(filePath) {
477
+ if (!filePath) {
478
+ return { success: false, error: 'No file path provided' };
479
+ }
480
+
481
+ try {
482
+
483
+ // For macOS, we can try using shell command via executeScript in content script
484
+ // This is a workaround since Chrome extensions can't directly execute system commands
485
+
486
+ // Try to create a temporary download link approach
487
+ const fileUrl = filePath.startsWith('/') ? `file://${filePath}` : `file:///${filePath}`;
488
+
489
+ // Method 1: Try to open in new tab first (might work on some systems)
490
+ try {
491
+ const tab = await chrome.tabs.create({
492
+ url: fileUrl,
493
+ active: false
494
+ });
495
+
496
+ // Check if tab was created successfully
497
+ if (tab && tab.id) {
498
+
499
+ // Close the tab after a short delay (system should have picked it up)
500
+ setTimeout(async () => {
501
+ try {
502
+ await chrome.tabs.remove(tab.id);
503
+ } catch (e) {
504
+ // Tab might already be closed by system
505
+ }
506
+ }, 1000);
507
+
508
+ return { success: true, method: 'system_tab', tabId: tab.id };
509
+ }
510
+ } catch (tabError) {
511
+ }
512
+
513
+ // Method 2: Try using the downloads API to force system open
514
+ try {
515
+ // Create a data URL that triggers download/open
516
+ const response = await fetch(fileUrl);
517
+ if (response.ok) {
518
+ return { success: true, method: 'accessible', filePath };
519
+ }
520
+ } catch (fetchError) {
521
+ }
522
+
523
+ // If all methods fail
524
+ return {
525
+ success: false,
526
+ error: 'Unable to open file with system default application',
527
+ suggestion: 'Try copying the file path and opening manually'
528
+ };
529
+
530
+ } catch (error) {
531
+ console.error('[VibeSurf] Error in openFileSystem:', error);
532
+ return {
533
+ success: false,
534
+ error: error.message || 'Unknown error opening file'
535
+ };
536
+ }
537
+ }
538
+
539
+ // Cleanup method for extension unload
540
+ async cleanup() {
541
+
542
+ // Clear any active badges
543
+ await chrome.action.setBadgeText({ text: '' });
544
+
545
+ // Could add other cleanup tasks here
546
+ }
547
+ }
548
+
549
+ // Initialize background service
550
+ const vibeSurfBackground = new VibeSurfBackground();
551
+
552
+ // Handle extension unload
553
+ chrome.runtime.onSuspend.addListener(() => {
554
+ vibeSurfBackground.cleanup();
555
+ });
556
+
557
+ // Export for potential use in tests or other contexts
558
+ self.VibeSurfBackground = VibeSurfBackground;
@@ -0,0 +1,48 @@
1
+ // VibeSurf Extension Configuration
2
+ // Change BACKEND_URL here to update all backend connections synchronously
3
+
4
+ const VIBESURF_CONFIG = {
5
+ // Backend server configuration
6
+ BACKEND_URL: 'http://127.0.0.1:9335',
7
+
8
+ // API related configuration
9
+ API_PREFIX: '/api',
10
+ DEFAULT_TIMEOUT: 30000,
11
+ RETRY_ATTEMPTS: 3,
12
+ RETRY_DELAY: 1000,
13
+
14
+ // Session configuration
15
+ DEFAULT_SESSION_PREFIX: '',
16
+
17
+ // Notification configuration
18
+ NOTIFICATIONS: {
19
+ enabled: true,
20
+ taskComplete: true,
21
+ taskError: true
22
+ },
23
+
24
+ // UI configuration
25
+ UI: {
26
+ theme: 'auto',
27
+ autoScroll: true,
28
+ compactMode: false
29
+ },
30
+
31
+ // Debug mode
32
+ DEBUG: false
33
+ };
34
+
35
+ // Export configuration for use in other files
36
+ if (typeof window !== 'undefined') {
37
+ window.VIBESURF_CONFIG = VIBESURF_CONFIG;
38
+ }
39
+
40
+ // Service worker environment (background script)
41
+ if (typeof self !== 'undefined' && typeof window === 'undefined') {
42
+ self.VIBESURF_CONFIG = VIBESURF_CONFIG;
43
+ }
44
+
45
+ // Node.js environment compatibility (if needed)
46
+ if (typeof module !== 'undefined' && module.exports) {
47
+ module.exports = VIBESURF_CONFIG;
48
+ }